diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..035651e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.{yml,yaml}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..87177b9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text=auto eol=lf +*.bat text eol=crlf +*.png binary +*.jpg binary +*.jpeg binary +*.webp binary +*.jar binary diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1bb91e6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +on: + push: + branches: + - main + - develop + pull_request: + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Make Gradle wrapper executable + run: chmod +x gradlew + + - name: Verify and assemble release + run: ./gradlew verifyFoundation :arabickit:assembleRelease --stacktrace + + - name: Upload release AAR + uses: actions/upload-artifact@v4 + with: + name: arabickit-release-aar + path: arabickit/build/outputs/aar/arabickit-release.aar + if-no-files-found: error diff --git a/.gitignore b/.gitignore index e5cbb64..c107772 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,34 @@ -# Gradle files +# Gradle .gradle/ -build/ +**/build/ -# Local configuration file (sdk path, etc) -local.properties - -# Log/OS Files -*.log - -# Android Studio generated files and folders +# Android Studio / IntelliJ +.idea/ +*.iml captures/ .externalNativeBuild/ .cxx/ -*.aab -*.apk -output-metadata.json -# IntelliJ -*.iml -.idea/ -misc.xml -deploymentTargetDropDown.xml -render.experimental.xml - -# Keystore files +# Local SDK and secrets +local.properties +.env *.jks *.keystore - -# Google Services (e.g. APIs or Firebase) google-services.json -# Android Profiling +# Build outputs +*.apk +*.aab +output-metadata.json + +# Logs / profiling +*.log *.hprof + +# OS +.DS_Store +Thumbs.db + +# ArabicKit local installer data +.arabickit-backups/ +.arabickit-install-logs/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..10a11ab --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog + +All notable changes to ArabicKit will be documented here. + +The format follows Keep a Changelog, and releases will follow Semantic Versioning after the first stable API is defined. + +## [Unreleased] + +## [0.1.0] - 2026-08-02 + +### Added + +- Android library foundation. +- Arabic normalization options. +- Arabic-aware normalized search. +- Three-way decimal numeral conversion. +- Dependency-free XML sample app. +- Unit tests, Lint verification, and GitHub Actions CI. +- English and Arabic project documentation. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..fa01866 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,7 @@ +# Code of Conduct + +ArabicKit contributors are expected to communicate respectfully, discuss code rather than people, welcome good-faith questions, and avoid harassment or discrimination. + +Maintainers may edit or remove abusive, off-topic, private, or security-sensitive content. Repeated harmful behavior can lead to participation restrictions. + +For private conduct reports, use the maintainer contact method listed in `SECURITY.md`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..05d275b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing to ArabicKit + +Thank you for helping improve Arabic text support on Android. + +## Before opening a pull request + +1. Create or reference an issue for non-trivial changes. +2. Branch from `develop`. +3. Keep each pull request focused. +4. Add tests for behavior changes. +5. Run: + +```powershell +.\gradlew.bat verifyFoundation +``` + +## Branch names + +- `feature/` +- `fix/` +- `docs/` +- `chore/` + +## Commit style + +Use concise imperative messages, for example: + +- `feat: add configurable hamza normalization` +- `fix: preserve non-decimal Arabic symbols` +- `docs: clarify search behavior` + +## API principles + +Arabic normalization can change meaning. New transformations must be configurable, documented, and covered by Arabic-language test cases. diff --git a/README.md b/README.md index df5440d..1fd112a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,72 @@ # ArabicKit -Production-ready Kotlin toolkit for Arabic text normalization, search, numerals, highlighting, and RTL utilities on Android. + +Production-ready Kotlin utilities for Arabic text normalization, search, and numeral conversion on Android. + +> ArabicKit `0.1.0` is the first public foundation release. The API is tested, but `0.x` releases may still evolve before the first stable release. + +## Current capabilities + +- Remove Arabic diacritics and Quranic annotation marks. +- Remove tatweel. +- Normalize alef variants and alef maksura. +- Normalize whitespace and Latin casing. +- Search through normalized Arabic text. +- Convert Western, Arabic-Indic, and Eastern Arabic-Indic digits. +- Java-friendly APIs through `@JvmStatic` and `@JvmOverloads`. +- A dependency-free Android sample app. +- Unit tests, Android Lint, and GitHub Actions CI. + +## Modules + +- `:arabickit` — the Android library. +- `:sample` — a small XML + ViewBinding demonstration app. + +## Local verification + +```bash +./gradlew verifyFoundation +``` + +On Windows: + +```powershell +.\gradlew.bat verifyFoundation +``` + +## Quick usage + +```kotlin +val normalized = ArabicNormalizer.normalize("إِنَّ الــلَّهَ غَفُورٌ") +// "ان الله غفور" + +val matches = ArabicSearch.contains( + text = "إِنَّ اللَّهَ غَفُورٌ", + query = "ان الله", +) +// true + +val western = ArabicNumerals.convert( + input = "الإصدار ٢٠٢٦", + target = ArabicNumeralSystem.WESTERN, +) +// "الإصدار 2026" +``` + +## Compatibility + +- Android minSdk 24 +- compileSdk 37 +- Java bytecode target 17 +- Kotlin support is provided by Android Gradle Plugin built-in Kotlin. + +## Project status + +ArabicKit is preparing the public `0.1.0` release. See [ROADMAP.md](ROADMAP.md) and [CHANGELOG.md](CHANGELOG.md). + +## Contributing + +Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening an issue or pull request. + +## License + +Apache License 2.0. See [LICENSE](LICENSE). diff --git a/README_AR.md b/README_AR.md new file mode 100644 index 0000000..f1454ec --- /dev/null +++ b/README_AR.md @@ -0,0 +1,33 @@ +# ArabicKit — العربية + +مكتبة Kotlin إنتاجية لمعالجة النص العربي والبحث وتحويل الأرقام داخل تطبيقات Android. + +> الإصدار `0.1.0` هو أول إصدار تأسيسي عام للمكتبة. الواجهات الحالية مختبرة، لكن إصدارات `0.x` قد تتطور قبل الوصول إلى أول إصدار مستقر. + +## الإمكانات الحالية + +- إزالة التشكيل وعلامات الضبط القرآنية. +- إزالة التطويل. +- توحيد أشكال الألف والألف المقصورة. +- تنظيم المسافات وحالة الأحرف اللاتينية. +- البحث بعد التطبيع. +- التحويل بين الأرقام الغربية والعربية والهندية الشرقية. +- واجهات سهلة للاستخدام من Kotlin وJava. +- تطبيق عينة مبني بـXML وViewBinding. +- اختبارات آلية وAndroid Lint وGitHub Actions. + +## التحقق محليًا + +على Windows: + +```powershell +.\gradlew.bat verifyFoundation +``` + +## حالة المشروع + +المشروع يجهّز الآن الإصدار العام الأول `0.1.0`. قد تتطور واجهات `0.x` قبل الوصول إلى الإصدار المستقر الأول. + +## الرخصة + +Apache License 2.0. diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..8636de6 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,30 @@ +# Roadmap + +## 0.1.0 — Foundation + +- [x] Deterministic normalization. +- [x] Arabic-aware contains and equality checks. +- [x] Numeral conversion. +- [x] Unit tests and CI. +- [x] API review. +- [ ] Publish the first public release. + +## 0.2.0 — Search ranges and highlighting + +- Original-text index mapping. +- Safe match ranges. +- Android `Spannable` highlighting. +- Multi-match support. + +## 0.3.0 — Advanced Arabic utilities + +- Configurable hamza handling. +- Tokenization helpers. +- Locale-safe sorting helpers. +- Performance benchmarks. + +## 1.0.0 — Stable API + +- Compatibility guarantees. +- Published Maven artifact. +- Expanded documentation and sample coverage. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..87a9619 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported versions + +Only the latest published ArabicKit release and the current `develop` branch receive security fixes during the `0.x` phase. + +## Reporting a vulnerability + +Do not open a public issue for a vulnerability or exposed secret. + +Use GitHub private vulnerability reporting when it is enabled for this repository. Include reproduction steps, affected versions, impact, and any suggested mitigation. + +Please allow the maintainer reasonable time to investigate before public disclosure. diff --git a/arabickit/build.gradle.kts b/arabickit/build.gradle.kts new file mode 100644 index 0000000..d15ad02 --- /dev/null +++ b/arabickit/build.gradle.kts @@ -0,0 +1,40 @@ +plugins { + id("com.android.library") +} + +group = "io.github.khalid567cpu" +version = "0.1.0" + +android { + namespace = "io.github.khalid567cpu.arabickit" + compileSdk = 37 + + defaultConfig { + minSdk = 24 + consumerProguardFiles("consumer-rules.pro") + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + buildFeatures { + buildConfig = false + } + + testOptions { + unitTests.isIncludeAndroidResources = false + } + + lint { + disable += "AndroidGradlePluginVersion" + abortOnError = true + warningsAsErrors = true + checkReleaseBuilds = true + } +} + +dependencies { + testImplementation("junit:junit:4.13.2") +} diff --git a/arabickit/consumer-rules.pro b/arabickit/consumer-rules.pro new file mode 100644 index 0000000..5bf25d0 --- /dev/null +++ b/arabickit/consumer-rules.pro @@ -0,0 +1 @@ +# ArabicKit currently exposes regular Kotlin APIs and requires no consumer keep rules. diff --git a/arabickit/src/main/AndroidManifest.xml b/arabickit/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8072ee0 --- /dev/null +++ b/arabickit/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/arabickit/src/main/kotlin/io/github/khalid567cpu/arabickit/ArabicNormalizationOptions.kt b/arabickit/src/main/kotlin/io/github/khalid567cpu/arabickit/ArabicNormalizationOptions.kt new file mode 100644 index 0000000..7ab8e86 --- /dev/null +++ b/arabickit/src/main/kotlin/io/github/khalid567cpu/arabickit/ArabicNormalizationOptions.kt @@ -0,0 +1,17 @@ +package io.github.khalid567cpu.arabickit + +/** + * Controls how [ArabicNormalizer] transforms text. + * + * Conservative defaults improve search without merging letters such as + * taa marbuta (ة) and haa (ه), which can alter meaning. + */ +public data class ArabicNormalizationOptions( + public val removeDiacritics: Boolean = true, + public val removeQuranicMarks: Boolean = true, + public val removeTatweel: Boolean = true, + public val normalizeAlef: Boolean = true, + public val normalizeAlefMaksura: Boolean = true, + public val normalizeWhitespace: Boolean = true, + public val lowercaseLatin: Boolean = true, +) diff --git a/arabickit/src/main/kotlin/io/github/khalid567cpu/arabickit/ArabicNormalizer.kt b/arabickit/src/main/kotlin/io/github/khalid567cpu/arabickit/ArabicNormalizer.kt new file mode 100644 index 0000000..ca3a02e --- /dev/null +++ b/arabickit/src/main/kotlin/io/github/khalid567cpu/arabickit/ArabicNormalizer.kt @@ -0,0 +1,46 @@ +package io.github.khalid567cpu.arabickit + +import java.util.Locale + +/** + * Deterministic Arabic text normalization for search and comparison. + */ +public object ArabicNormalizer { + private val arabicDiacritics = Regex("[\\u064B-\\u065F\\u0670]") + private val quranicAnnotationMarks = Regex("[\\u0610-\\u061A\\u06D6-\\u06ED]") + private val alefVariants = Regex("[\\u0622\\u0623\\u0625\\u0671]") + private val whitespace = Regex("\\s+") + + @JvmStatic + @JvmOverloads + public fun normalize( + input: CharSequence, + options: ArabicNormalizationOptions = ArabicNormalizationOptions(), + ): String { + var result = input.toString() + + if (options.removeDiacritics) { + result = result.replace(arabicDiacritics, "") + } + if (options.removeQuranicMarks) { + result = result.replace(quranicAnnotationMarks, "") + } + if (options.removeTatweel) { + result = result.replace("\u0640", "") + } + if (options.normalizeAlef) { + result = result.replace(alefVariants, "\u0627") + } + if (options.normalizeAlefMaksura) { + result = result.replace('\u0649', '\u064A') + } + if (options.normalizeWhitespace) { + result = result.replace(whitespace, " ").trim() + } + if (options.lowercaseLatin) { + result = result.lowercase(Locale.ROOT) + } + + return result + } +} diff --git a/arabickit/src/main/kotlin/io/github/khalid567cpu/arabickit/ArabicNumerals.kt b/arabickit/src/main/kotlin/io/github/khalid567cpu/arabickit/ArabicNumerals.kt new file mode 100644 index 0000000..c546f4f --- /dev/null +++ b/arabickit/src/main/kotlin/io/github/khalid567cpu/arabickit/ArabicNumerals.kt @@ -0,0 +1,48 @@ +package io.github.khalid567cpu.arabickit + +/** + * Supported decimal digit glyph sets. + */ +public enum class ArabicNumeralSystem { + WESTERN, + ARABIC_INDIC, + EASTERN_ARABIC_INDIC, +} + +/** + * Converts decimal digits while preserving every non-digit character. + */ +public object ArabicNumerals { + private const val WESTERN = "0123456789" + private const val ARABIC_INDIC = "٠١٢٣٤٥٦٧٨٩" + private const val EASTERN_ARABIC_INDIC = "۰۱۲۳۴۵۶۷۸۹" + + @JvmStatic + public fun convert( + input: CharSequence, + target: ArabicNumeralSystem, + ): String { + val targetDigits = when (target) { + ArabicNumeralSystem.WESTERN -> WESTERN + ArabicNumeralSystem.ARABIC_INDIC -> ARABIC_INDIC + ArabicNumeralSystem.EASTERN_ARABIC_INDIC -> EASTERN_ARABIC_INDIC + } + + return buildString(input.length) { + input.forEach { character -> + val digit = digitValue(character) + append(if (digit >= 0) targetDigits[digit] else character) + } + } + } + + private fun digitValue(character: Char): Int { + val westernIndex = WESTERN.indexOf(character) + if (westernIndex >= 0) return westernIndex + + val arabicIndicIndex = ARABIC_INDIC.indexOf(character) + if (arabicIndicIndex >= 0) return arabicIndicIndex + + return EASTERN_ARABIC_INDIC.indexOf(character) + } +} diff --git a/arabickit/src/main/kotlin/io/github/khalid567cpu/arabickit/ArabicSearch.kt b/arabickit/src/main/kotlin/io/github/khalid567cpu/arabickit/ArabicSearch.kt new file mode 100644 index 0000000..7edbe3c --- /dev/null +++ b/arabickit/src/main/kotlin/io/github/khalid567cpu/arabickit/ArabicSearch.kt @@ -0,0 +1,28 @@ +package io.github.khalid567cpu.arabickit + +/** + * Arabic-aware search helpers built on deterministic normalization. + */ +public object ArabicSearch { + @JvmStatic + @JvmOverloads + public fun contains( + text: CharSequence, + query: CharSequence, + options: ArabicNormalizationOptions = ArabicNormalizationOptions(), + ): Boolean { + val normalizedQuery = ArabicNormalizer.normalize(query, options) + if (normalizedQuery.isEmpty()) return false + + return ArabicNormalizer.normalize(text, options).contains(normalizedQuery) + } + + @JvmStatic + @JvmOverloads + public fun equalsNormalized( + first: CharSequence, + second: CharSequence, + options: ArabicNormalizationOptions = ArabicNormalizationOptions(), + ): Boolean = ArabicNormalizer.normalize(first, options) == + ArabicNormalizer.normalize(second, options) +} diff --git a/arabickit/src/test/kotlin/io/github/khalid567cpu/arabickit/ArabicNormalizerTest.kt b/arabickit/src/test/kotlin/io/github/khalid567cpu/arabickit/ArabicNormalizerTest.kt new file mode 100644 index 0000000..d26e64b --- /dev/null +++ b/arabickit/src/test/kotlin/io/github/khalid567cpu/arabickit/ArabicNormalizerTest.kt @@ -0,0 +1,23 @@ +package io.github.khalid567cpu.arabickit + +import org.junit.Assert.assertEquals +import org.junit.Test + +public class ArabicNormalizerTest { + @Test + public fun removesDiacriticsTatweelAndNormalizesAlef() { + val input = "إِنَّ الــلَّهَ غَفُورٌ" + + assertEquals("ان الله غفور", ArabicNormalizer.normalize(input)) + } + + @Test + public fun normalizesAlefMaksuraWithoutChangingTaaMarbuta() { + assertEquals("علي هدي ورحمة", ArabicNormalizer.normalize("عَلَى هُدًى وَرَحْمَةٍ")) + } + + @Test + public fun collapsesWhitespaceAndLowercasesLatin() { + assertEquals("arabic kit عربي", ArabicNormalizer.normalize(" Arabic KIT عربي ")) + } +} diff --git a/arabickit/src/test/kotlin/io/github/khalid567cpu/arabickit/ArabicNumeralsTest.kt b/arabickit/src/test/kotlin/io/github/khalid567cpu/arabickit/ArabicNumeralsTest.kt new file mode 100644 index 0000000..a45f194 --- /dev/null +++ b/arabickit/src/test/kotlin/io/github/khalid567cpu/arabickit/ArabicNumeralsTest.kt @@ -0,0 +1,22 @@ +package io.github.khalid567cpu.arabickit + +import org.junit.Assert.assertEquals +import org.junit.Test + +public class ArabicNumeralsTest { + @Test + public fun convertsMixedDigitsToArabicIndic() { + assertEquals( + "الإصدار ٢٠٢٦", + ArabicNumerals.convert("الإصدار 20۲۶", ArabicNumeralSystem.ARABIC_INDIC), + ) + } + + @Test + public fun convertsArabicDigitsToWestern() { + assertEquals( + "Page 123", + ArabicNumerals.convert("Page ١۲3", ArabicNumeralSystem.WESTERN), + ) + } +} diff --git a/arabickit/src/test/kotlin/io/github/khalid567cpu/arabickit/ArabicSearchTest.kt b/arabickit/src/test/kotlin/io/github/khalid567cpu/arabickit/ArabicSearchTest.kt new file mode 100644 index 0000000..8ee6ec5 --- /dev/null +++ b/arabickit/src/test/kotlin/io/github/khalid567cpu/arabickit/ArabicSearchTest.kt @@ -0,0 +1,17 @@ +package io.github.khalid567cpu.arabickit + +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +public class ArabicSearchTest { + @Test + public fun findsTextAcrossDiacriticsAndAlefVariants() { + assertTrue(ArabicSearch.contains("إِنَّ اللَّهَ غَفُورٌ", "ان الله")) + } + + @Test + public fun rejectsAnEmptyNormalizedQuery() { + assertFalse(ArabicSearch.contains("نص عربي", " ")) + } +} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..fc46430 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("com.android.application") version "9.3.0" apply false + id("com.android.library") version "9.3.0" apply false +} + +tasks.register("verifyFoundation") { + group = "verification" + description = "Runs ArabicKit unit tests, Android lint, and the sample debug build." + dependsOn( + ":arabickit:testDebugUnitTest", + ":arabickit:lintDebug", + ":sample:assembleDebug", + ":sample:lintDebug", + ) +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..fc94dd8 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx3072m -Dfile.encoding=UTF-8 +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true +android.useAndroidX=true +android.nonTransitiveRClass=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..d997cfc Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1a70468 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..32d36aa --- /dev/null +++ b/gradlew @@ -0,0 +1,26 @@ +#!/bin/sh + +############################################################################## +## +## Gradle start up script for POSIX generated by Gradle. +## +############################################################################## + +APP_HOME=$(cd "${0%/*}" && pwd -P) +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +DEFAULT_JVM_OPTS="-Xmx64m -Xms64m" + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +if [ -n "$JAVA_HOME" ] ; then + JAVACMD=$JAVA_HOME/bin/java +else + JAVACMD=java +fi + +exec "$JAVACMD" $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..5ef5c40 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,86 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set JAVA_HOME to match the location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%\bin\java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set JAVA_HOME to match the location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts new file mode 100644 index 0000000..c92371a --- /dev/null +++ b/sample/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + id("com.android.application") +} + +android { + namespace = "io.github.khalid567cpu.arabickit.sample" + compileSdk = 37 + + defaultConfig { + applicationId = "io.github.khalid567cpu.arabickit.sample" + minSdk = 24 + targetSdk = 37 + versionCode = 1 + versionName = "0.1.0" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + buildFeatures { + viewBinding = true + } + + lint { + abortOnError = true + warningsAsErrors = false + checkReleaseBuilds = true + disable += setOf( + "AndroidGradlePluginVersion", + "DataExtractionRules", + "MissingApplicationIcon", + "Autofill", + ) + } +} + +dependencies { + implementation(project(":arabickit")) +} \ No newline at end of file diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e2b4f1f --- /dev/null +++ b/sample/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/sample/src/main/kotlin/io/github/khalid567cpu/arabickit/sample/MainActivity.kt b/sample/src/main/kotlin/io/github/khalid567cpu/arabickit/sample/MainActivity.kt new file mode 100644 index 0000000..f960108 --- /dev/null +++ b/sample/src/main/kotlin/io/github/khalid567cpu/arabickit/sample/MainActivity.kt @@ -0,0 +1,36 @@ +package io.github.khalid567cpu.arabickit.sample + +import android.app.Activity +import android.os.Bundle +import io.github.khalid567cpu.arabickit.ArabicNormalizer +import io.github.khalid567cpu.arabickit.ArabicNumeralSystem +import io.github.khalid567cpu.arabickit.ArabicNumerals +import io.github.khalid567cpu.arabickit.ArabicSearch +import io.github.khalid567cpu.arabickit.sample.databinding.ActivityMainBinding + +public class MainActivity : Activity() { + private lateinit var binding: ActivityMainBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.inputText.setText("إِنَّ الــلَّهَ غَفُورٌ ٢٠٢٦") + binding.runButton.setOnClickListener { renderResult() } + renderResult() + } + + private fun renderResult() { + val input = binding.inputText.text.toString() + val normalized = ArabicNormalizer.normalize(input) + val westernDigits = ArabicNumerals.convert(input, ArabicNumeralSystem.WESTERN) + val matches = ArabicSearch.contains(input, "ان الله") + + binding.normalizedValue.text = normalized + binding.numeralsValue.text = westernDigits + binding.searchValue.text = getString( + if (matches) R.string.search_match else R.string.search_no_match, + ) + } +} diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..62f0e41 --- /dev/null +++ b/sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + +