Repository: yamin8000/freeDictionaryApp Branch: master Commit: ea9370b1bd97 Files: 274 Total size: 728.3 KB Directory structure: gitextract_bolm891h/ ├── .github/ │ └── workflows/ │ ├── android.yml │ └── apk.yml ├── .gitignore ├── .idea/ │ └── copyright/ │ ├── gplv3.xml │ └── profiles_settings.xml ├── LICENSE ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── io/ │ │ └── github/ │ │ └── yamin8000/ │ │ └── owl/ │ │ ├── core/ │ │ │ └── App.kt │ │ └── ui/ │ │ ├── BaseActivity.kt │ │ ├── MainActivity.kt │ │ ├── OverlayActivity.kt │ │ └── navigation/ │ │ └── Nav.kt │ └── res/ │ ├── drawable/ │ │ ├── ic_launcher_background.xml │ │ └── ic_launcher_foreground.xml │ ├── mipmap-anydpi-v26/ │ │ └── ic_launcher.xml │ ├── values/ │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ └── themes.xml │ └── values-night/ │ └── themes.xml ├── build.gradle.kts ├── common/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ ├── debug/ │ │ └── java/ │ │ └── io/ │ │ └── github/ │ │ └── yamin8000/ │ │ └── owl/ │ │ └── common/ │ │ └── CrudContentPreview.kt │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── io/ │ │ └── github/ │ │ └── yamin8000/ │ │ └── owl/ │ │ └── common/ │ │ ├── di/ │ │ │ └── CommonModule.kt │ │ ├── ui/ │ │ │ ├── components/ │ │ │ │ ├── AppText.kt │ │ │ │ ├── ClickableIcon.kt │ │ │ │ ├── DeleteMenu.kt │ │ │ │ ├── EmptyList.kt │ │ │ │ ├── HighlightText.kt │ │ │ │ ├── MySnackbar.kt │ │ │ │ ├── Ripple.kt │ │ │ │ ├── ScaffoldWithTitle.kt │ │ │ │ ├── Texts.kt │ │ │ │ └── crud/ │ │ │ │ ├── CrudContent.kt │ │ │ │ ├── CrudItem.kt │ │ │ │ ├── RemovableCard.kt │ │ │ │ └── RemoveAllContent.kt │ │ │ └── theme/ │ │ │ ├── Color.kt │ │ │ ├── Shape.kt │ │ │ ├── Sizes.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ │ └── util/ │ │ ├── Constants.kt │ │ ├── ContextUtils.kt │ │ ├── DateTimeUtils.kt │ │ ├── LocalTTS.kt │ │ ├── LocaleUtils.kt │ │ ├── StringUtils.kt │ │ ├── TTS.kt │ │ └── Utility.kt │ └── res/ │ └── raw/ │ └── empty_list.json ├── datastore/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── io/ │ └── github/ │ └── yamin8000/ │ └── owl/ │ └── datastore/ │ ├── data/ │ │ ├── datasource/ │ │ │ └── Datastore.kt │ │ └── repository/ │ │ ├── BasicDatastoreRepository.kt │ │ ├── FavouriteDatastoreRepository.kt │ │ ├── HistoryDatastoreRepository.kt │ │ └── SettingsDatastoreRepository.kt │ ├── di/ │ │ └── DatastoreModule.kt │ └── domain/ │ ├── model/ │ │ ├── SettingsKeys.kt │ │ └── ThemeType.kt │ ├── repository/ │ │ ├── BaseDatastoreRepository.kt │ │ ├── FavouriteRepository.kt │ │ ├── HistoryRepository.kt │ │ └── SettingsRepository.kt │ └── usecase/ │ ├── favourites/ │ │ ├── AddFavourite.kt │ │ ├── FavouriteUseCases.kt │ │ ├── GetAllFavourite.kt │ │ ├── RemoveAllFavourite.kt │ │ └── RemoveFavourite.kt │ ├── history/ │ │ ├── AddHistory.kt │ │ ├── GetAllHistory.kt │ │ ├── HistoryUseCases.kt │ │ ├── RemoveAllHistory.kt │ │ └── RemoveHistory.kt │ └── settings/ │ ├── GetStartingBlank.kt │ ├── GetTTS.kt │ ├── GetTheme.kt │ ├── GetVibration.kt │ ├── SetStartingBlank.kt │ ├── SetTTS.kt │ ├── SetTheme.kt │ ├── SetVibration.kt │ └── SettingUseCases.kt ├── fastlane/ │ └── metadata/ │ └── android/ │ └── en-US/ │ ├── changelogs/ │ │ ├── 34.txt │ │ ├── 35.txt │ │ ├── 36.txt │ │ ├── 38.txt │ │ ├── 42.txt │ │ ├── 43.txt │ │ └── 44.txt │ ├── full_description.txt │ ├── short_description.txt │ └── title.txt ├── feature_about/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── kotlin/ │ │ └── io/ │ │ └── github/ │ │ └── yamin8000/ │ │ └── owl/ │ │ └── feature_about/ │ │ ├── data/ │ │ │ ├── datasource/ │ │ │ │ └── remote/ │ │ │ │ ├── GithubAPIs.kt │ │ │ │ └── dto/ │ │ │ │ ├── ContributorDto.kt │ │ │ │ ├── ReleaseDto.kt │ │ │ │ └── RepositoryDto.kt │ │ │ └── repository/ │ │ │ └── GithubWebRepoRepository.kt │ │ ├── di/ │ │ │ └── AboutModule.kt │ │ ├── domain/ │ │ │ ├── Contributor.kt │ │ │ ├── ContributorType.kt │ │ │ ├── Release.kt │ │ │ ├── Repository.kt │ │ │ └── repository/ │ │ │ └── GithubRepoRepository.kt │ │ └── ui/ │ │ ├── About.kt │ │ ├── AboutAction.kt │ │ ├── AboutState.kt │ │ ├── AboutTab.kt │ │ ├── AboutViewModel.kt │ │ ├── UiContributor.kt │ │ ├── components/ │ │ │ ├── ContributionsBar.kt │ │ │ └── ContributorItem.kt │ │ └── tabs/ │ │ ├── AboutContributors.kt │ │ ├── AboutInfo.kt │ │ └── AboutLicense.kt │ └── res/ │ └── drawable/ │ ├── broken_image_48px.xml │ ├── ic_gplv3.xml │ └── image_48px.xml ├── feature_favourites/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── io/ │ └── github/ │ └── yamin8000/ │ └── owl/ │ └── feature_favourites/ │ ├── FavouriteEvent.kt │ ├── FavouriteState.kt │ ├── Favourites.kt │ └── FavouritesViewModel.kt ├── feature_history/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── io/ │ └── github/ │ └── yamin8000/ │ └── karlancer/ │ └── feature_history/ │ └── ui/ │ ├── History.kt │ ├── HistoryEvent.kt │ ├── HistoryState.kt │ └── HistoryViewModel.kt ├── feature_home/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ ├── debug/ │ │ └── java/ │ │ └── io/ │ │ └── github/ │ │ └── yamin8000/ │ │ └── owl/ │ │ └── feature_home/ │ │ └── ui/ │ │ └── Previews.kt │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── io/ │ │ └── github/ │ │ └── yamin8000/ │ │ └── owl/ │ │ └── feature_home/ │ │ ├── data/ │ │ │ └── repository/ │ │ │ └── TermSuggesterRepositoryImpl.kt │ │ ├── di/ │ │ │ ├── HomeModule.kt │ │ │ ├── HomeUseCases.kt │ │ │ └── HomeViewModelFactory.kt │ │ ├── domain/ │ │ │ ├── repository/ │ │ │ │ └── TermSuggesterRepository.kt │ │ │ └── usecase/ │ │ │ └── GetRandomWord.kt │ │ └── ui/ │ │ ├── Home.kt │ │ ├── HomeAction.kt │ │ ├── HomeState.kt │ │ ├── HomeViewModel.kt │ │ ├── components/ │ │ │ ├── HomeTopBar.kt │ │ │ ├── SearchList.kt │ │ │ └── bottom_app_bar/ │ │ │ ├── BottomAppBarDuringSearch.kt │ │ │ ├── HomeBottomBar.kt │ │ │ ├── NormalBottomAppBar.kt │ │ │ ├── RainbowWavyLinearProgress.kt │ │ │ └── SuggestionsChips.kt │ │ └── util/ │ │ ├── HomeSnackbarType.kt │ │ ├── ShareUtils.kt │ │ └── Utils.kt │ └── res/ │ └── raw/ │ └── basic2000.txt ├── feature_overlay/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── io/ │ └── github/ │ └── yamin8000/ │ └── owl/ │ └── feature_overlay/ │ ├── di/ │ │ └── OverlayViewModelFactory.kt │ └── ui/ │ ├── OverlayWindow.kt │ ├── OverlayWindowState.kt │ ├── OverlayWindowViewModel.kt │ └── components/ │ ├── ButtonsRow.kt │ └── SearchList.kt ├── feature_settings/ │ ├── .gitignore │ ├── build.gradle.kts │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── io/ │ └── github/ │ └── yamin8000/ │ └── owl/ │ └── feature_settings/ │ └── ui/ │ ├── Settings.kt │ ├── SettingsAction.kt │ ├── SettingsState.kt │ ├── SettingsViewModel.kt │ ├── components/ │ │ ├── GeneralSettings.kt │ │ ├── SettingsItem.kt │ │ ├── SettingsItemCard.kt │ │ ├── SwitchItem.kt │ │ ├── SwitchWithText.kt │ │ ├── theme/ │ │ │ ├── DynamicThemeNotice.kt │ │ │ ├── ThemeChangerDialog.kt │ │ │ └── ThemeSetting.kt │ │ └── tts/ │ │ ├── TtsLanguageItem.kt │ │ ├── TtsLanguageSetting.kt │ │ └── TtsLanguagesDialog.kt │ └── utils/ │ └── Utility.kt ├── gradle/ │ ├── libs.versions.toml │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── renovate.json ├── search/ │ ├── .gitignore │ ├── build.gradle.kts │ ├── schemas/ │ │ └── io.github.yamin8000.owl.search.data.datasource.local.AppDatabase/ │ │ ├── 1.json │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ ├── 5.json │ │ ├── 6.json │ │ ├── 7.json │ │ └── 8.json │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── io/ │ └── github/ │ └── yamin8000/ │ └── owl/ │ └── search/ │ ├── data/ │ │ ├── datasource/ │ │ │ ├── local/ │ │ │ │ ├── AppDatabase.kt │ │ │ │ ├── dao/ │ │ │ │ │ ├── AdvancedDao.kt │ │ │ │ │ ├── BaseDao.kt │ │ │ │ │ └── DAOs.kt │ │ │ │ └── entity/ │ │ │ │ ├── AntonymEntity.kt │ │ │ │ ├── DefinitionEntity.kt │ │ │ │ ├── EntryEntity.kt │ │ │ │ ├── MeaningEntity.kt │ │ │ │ ├── PhoneticEntity.kt │ │ │ │ ├── SynonymEntity.kt │ │ │ │ └── TermEntity.kt │ │ │ └── remote/ │ │ │ ├── FreeDictionaryAPI.kt │ │ │ └── dto/ │ │ │ ├── DefinitionDto.kt │ │ │ ├── EntryDto.kt │ │ │ ├── LicenseDto.kt │ │ │ ├── MeaningDto.kt │ │ │ └── PhoneticDto.kt │ │ └── repository/ │ │ ├── local/ │ │ │ ├── BaseRoomRepository.kt │ │ │ ├── DefinitionRoomRepository.kt │ │ │ ├── EntryRoomRepository.kt │ │ │ ├── MeaningRoomRepository.kt │ │ │ ├── PhoneticRoomRepository.kt │ │ │ └── TermRoomRepository.kt │ │ └── remote/ │ │ └── FreeDictionaryRetrofitApiRepository.kt │ ├── di/ │ │ ├── SearchDb.kt │ │ ├── SearchUseCases.kt │ │ └── SearchWeb.kt │ ├── domain/ │ │ ├── model/ │ │ │ ├── Definition.kt │ │ │ ├── Entry.kt │ │ │ ├── License.kt │ │ │ ├── Meaning.kt │ │ │ └── Phonetic.kt │ │ ├── repository/ │ │ │ ├── local/ │ │ │ │ ├── DefinitionRepository.kt │ │ │ │ ├── EntryRepository.kt │ │ │ │ ├── MeaningRepository.kt │ │ │ │ ├── PhoneticRepository.kt │ │ │ │ ├── TermRepository.kt │ │ │ │ └── util/ │ │ │ │ ├── BaseRepository.kt │ │ │ │ └── HasEntry.kt │ │ │ └── remote/ │ │ │ └── FreeDictionaryApiRepository.kt │ │ └── usecase/ │ │ ├── CacheWord.kt │ │ ├── CacheWordData.kt │ │ ├── GetCachedWord.kt │ │ ├── SearchFreeDictionary.kt │ │ └── WordCacheUseCases.kt │ └── ui/ │ └── components/ │ ├── MeaningCard.kt │ ├── WordCard.kt │ └── texts/ │ ├── PronunciationText.kt │ ├── WordDefinitionText.kt │ ├── WordExampleText.kt │ ├── WordText.kt │ └── WordTypeText.kt ├── settings.gradle.kts └── strings/ ├── .gitignore ├── build.gradle.kts └── src/ └── main/ ├── AndroidManifest.xml └── res/ ├── values/ │ └── strings.xml ├── values-fa/ │ └── strings.xml ├── values-hi/ │ └── strings.xml ├── values-hu/ │ └── strings.xml └── values-ja/ └── strings.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/android.yml ================================================ name: Android Build on: push: branches: [ "master" ] pull_request: branches: [ "master" ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up JDK 21 uses: actions/setup-java@v5 with: java-version: '21' distribution: 'temurin' cache: gradle - name: Cache Gradle and wrapper uses: actions/cache@v5 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} restore-keys: | ${{ runner.os }}-gradle- - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle run: ./gradlew build ================================================ FILE: .github/workflows/apk.yml ================================================ name: APK Build on: release: types: [ published ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up JDK 21 uses: actions/setup-java@v5 with: java-version: '21' distribution: 'temurin' cache: gradle - name: Cache Gradle and wrapper uses: actions/cache@v5 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} restore-keys: | ${{ runner.os }}-gradle- - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build debug APK run: ./gradlew assembleDebug - name: Upload APK uses: actions/upload-artifact@v7 with: name: freeDictionaryDebugApk path: app/build/outputs/apk/debug/*.apk ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/caches /.idea/libraries /.idea/modules.xml /.idea/workspace.xml /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store /build /captures .externalNativeBuild .cxx local.properties /.idea/inspectionProfiles/Project_Default.xml /.idea/.gitignore /.idea/.name /.idea/compiler.xml /.idea/gradle.xml /.idea/misc.xml /.idea/vcs.xml /app/src/main/java/io/github/yamin8000/owl/network/ApiKey.kt /app/release/ /.kotlin/ /.idea/AndroidProjectSystem.xml /.idea/deploymentTargetSelector.xml /.idea/deploymentTargetDropDown.xml /.idea/kotlinc.xml /.idea/migrations.xml /.idea/runConfigurations.xml /.idea/codeStyles/codeStyleConfig.xml /.idea/codeStyles/Project.xml /.idea/copilot.data.migration.agent.xml /.idea/copilot.data.migration.edit.xml /.idea/deviceManager.xml /.idea/copilot.data.migration.ask.xml /.idea/copilot.data.migration.ask2agent.xml /.idea/betterCommentsSettings.xml /.idea/runConfigurations/app.xml /.idea/markdown.xml ================================================ FILE: .idea/copyright/gplv3.xml ================================================ ================================================ FILE: .idea/copyright/profiles_settings.xml ================================================ ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================
Get it on F-Droid

freeDictionary

[![Android CI](https://github.com/yamin8000/freeDictionaryApp/actions/workflows/android.yml/badge.svg)](https://github.com/yamin8000/freeDictionaryApp/actions/workflows/android.yml) [![CodeFactor](https://www.codefactor.io/repository/github/yamin8000/freedictionaryapp/badge)](https://www.codefactor.io/repository/github/yamin8000/freedictionaryapp) **freeDictionary** is a simple Android application for [freeDictionaryAPI](https://dictionaryapi.dev/) a reincarnation of https://github.com/yamin8000/Owl2 [Get it on F-Droid](https://f-droid.org/packages/io.github.yamin8000.owl) ## Preview ### Featured on [TechDoc](https://www.youtube.com/watch?v=vlf0jEFHR74&t=59s)
preview preview preview
Dark Light OLED
More screenshots [here](./screenshots). ## Compatibility > [!important] > **SDK23+** or **Android 6.0+** ## Usage Just use the search input to search the word. ## Download - GitHub releases: [here](https://github.com/yamin8000/freeDictionaryApp/releases) - F-Droid: [here](https://f-droid.org/packages/io.github.yamin8000.owl) - IzzyOnDroid: [here](https://apt.izzysoft.de/fdroid/index/apk/io.github.yamin8000.owl) - Bazaar: [here](https://cafebazaar.ir/app/io.github.yamin8000.owl) ## Features - English to English dictionary - Definition of the word - Example of the word usage if available - Synonyms/Antonyms of the work if available - Pronunciation of the word, both IPA text and audio using TTS - Save searched data for offline uses - Search for a random word ## Tech Stack - Kotlin(JVM) - Compose UI - Material3/Material You - Clean Architecture - Dependency Injection with Hilt/Dagger - MVI / MVVM - Retrofit, Moshi with KSP, Coil - Datastore, Room with KSP ## License > [!important] > freeDictionaryApp is licensed under the **[GNU General Public License v3.0](./LICENSE)** > Permissions of this strong copyleft license are conditioned on making > available complete source code of licensed works and modifications, > which include larger works using a licensed work, under the same > license. Copyright and license notices must be preserved. Contributors > provide an express grant of patent rights. ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle.kts ================================================ import org.jetbrains.kotlin.gradle.dsl.JvmTarget /* * freeDictionaryApp/freeDictionaryApp.app * build.gradle.kts Copyrighted by Yamin Siahmargooei at 2024/5/9 * build.gradle.kts Last modified at 2024/5/5 * This file is part of freeDictionaryApp/freeDictionaryApp.app. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.app is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ plugins { alias(libs.plugins.android.application) alias(libs.plugins.jetbrains.kotlin.compose.plugin) alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.google.ksp) alias(libs.plugins.hilt) } private val appId = "io.github.yamin8000.owl" android { namespace = appId compileSdk = 36 defaultConfig { applicationId = appId minSdk = 23 targetSdk = 36 versionCode = 53 versionName = "1.7.10" vectorDrawables.useSupportLibrary = true base.archivesName = "$applicationId-v$versionCode-n$versionName" } buildTypes { release { proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) isMinifyEnabled = true isShrinkResources = true } debug { isMinifyEnabled = false isShrinkResources = false } } dependenciesInfo { includeInApk = false includeInBundle = false } compileOptions { isCoreLibraryDesugaringEnabled = true sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } } buildFeatures { compose = true buildConfig = true } packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" excludes += "/en/*" excludes += "/*.yml" } } } dependencies { //modules implementation(project(":common")) implementation(project(":strings")) implementation(project(":datastore")) implementation(project(":feature_home")) implementation(project(":feature_settings")) implementation(project(":feature_history")) implementation(project(":feature_favourites")) implementation(project(":feature_overlay")) implementation(project(":feature_about")) //core android/kotlin coreLibraryDesugaring(libs.desugar.jdk.libs) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.core.splashscreen) //compose implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.material) implementation(libs.androidx.compose.ui.tooling.preview) // debugImplementation(libs.androidx.compose.ui.tooling) implementation(libs.androidx.activity.compose) implementation(libs.androidx.compose.material.icons.extended) implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3.window.size) implementation(libs.androidx.lifecycle.runtime.compose) //navigation implementation(libs.androidx.navigation.compose) //hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) implementation(libs.hilt.lifecycle.compose) } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keep class io.github.yamin8000.owl.datastore.domain.model.** { ; } -keep class io.github.yamin8000.owl.search.domain.model.** { ; } -keep class io.github.yamin8000.owl.search.data.datasource.remote.dto.** { ; } # This is generated automatically by the Android Gradle plugin. -dontwarn org.bouncycastle.jsse.BCSSLParameters -dontwarn org.bouncycastle.jsse.BCSSLSocket -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider -dontwarn org.conscrypt.Conscrypt$Version -dontwarn org.conscrypt.Conscrypt -dontwarn org.conscrypt.ConscryptHostnameVerifier -dontwarn org.openjsse.javax.net.ssl.SSLParameters -dontwarn org.openjsse.javax.net.ssl.SSLSocket -dontwarn org.openjsse.net.ssl.OpenJSSE ##---------------Begin: proguard configuration for okhttp3 ---------- # JSR 305 annotations are for embedding nullability information. -dontwarn javax.annotation.** # A resource is loaded with a relative path so the package of this class must be preserved. -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. -dontwarn org.codehaus.mojo.animal_sniffer.* # OkHttp platform used only on JVM and when Conscrypt dependency is available. -dontwarn okhttp3.internal.platform.ConscryptPlatform ##---------------End: proguard configuration for okhttp3 ---------- ##---------------Begin: proguard configuration for okio ---------- # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. -dontwarn org.codehaus.mojo.animal_sniffer.* ##---------------End: proguard configuration for okio ---------- -keepnames class * implements android.os.Parcelable { public static final ** CREATOR; } # This is generated automatically by the Android Gradle plugin. -dontwarn java.lang.invoke.StringConcatFactory # Faker -dontwarn java.beans.BeanInfo -dontwarn java.beans.FeatureDescriptor -dontwarn java.beans.IntrospectionException -dontwarn java.beans.Introspector -dontwarn java.beans.PropertyDescriptor ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/io/github/yamin8000/owl/core/App.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.app.main * App.kt Copyrighted by Yamin Siahmargooei at 2024/8/17 * App.kt Last modified at 2024/8/17 * This file is part of freeDictionaryApp/freeDictionaryApp.app.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.app.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.app.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.core import android.app.Application import dagger.hilt.android.HiltAndroidApp @HiltAndroidApp internal class App : Application() ================================================ FILE: app/src/main/java/io/github/yamin8000/owl/ui/BaseActivity.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.app.main * BaseActivity.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * BaseActivity.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.app.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.app.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.app.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.ui import android.content.res.Configuration import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import dagger.hilt.android.AndroidEntryPoint import io.github.yamin8000.owl.common.ui.theme.AppTheme import io.github.yamin8000.owl.datastore.domain.model.ThemeType import io.github.yamin8000.owl.datastore.domain.usecase.settings.SettingUseCases import io.github.yamin8000.owl.common.util.log import kotlinx.coroutines.runBlocking import javax.inject.Inject @AndroidEntryPoint internal open class BaseActivity : ComponentActivity() { @Inject lateinit var settings: SettingUseCases var appTheme by mutableStateOf(ThemeType.System) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) appTheme = findTheme() } protected fun showContent( content: @Composable () -> Unit, ) { setContent { val isSystemInDarkTheme = (resources.configuration.uiMode .and(Configuration.UI_MODE_NIGHT_MASK)) == Configuration.UI_MODE_NIGHT_YES AppTheme( isDarkTheme = isDarkTheme(appTheme, isSystemInDarkTheme), isOledTheme = appTheme == ThemeType.Darker || appTheme == ThemeType.SystemDarker, isDynamicColor = appTheme == ThemeType.System || appTheme == ThemeType.SystemDarker, content = content ) } } private fun findTheme(): ThemeType { return try { runBlocking { settings.getTheme() } } catch (e: InterruptedException) { log(e.stackTraceToString()) ThemeType.System } } private fun isDarkTheme( theme: ThemeType, isSystemInDarkTheme: Boolean ) = when (theme) { ThemeType.Light -> false ThemeType.System -> isSystemInDarkTheme ThemeType.Dark, ThemeType.Darker, ThemeType.SystemDarker -> true } } ================================================ FILE: app/src/main/java/io/github/yamin8000/owl/ui/MainActivity.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.app.main * MainActivity.kt Copyrighted by Yamin Siahmargooei at 2024/8/18 * MainActivity.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.app.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.app.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.app.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.ui import android.annotation.SuppressLint import android.os.Bundle import androidx.activity.enableEdgeToEdge import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import io.github.yamin8000.karlancer.feature_history.ui.HistoryScreen import io.github.yamin8000.owl.BuildConfig import io.github.yamin8000.owl.datastore.domain.model.ThemeType import io.github.yamin8000.owl.feature_about.ui.AboutScreen import io.github.yamin8000.owl.feature_favourites.FavouritesScreen import io.github.yamin8000.owl.feature_home.di.HomeViewModelFactory import io.github.yamin8000.owl.feature_home.ui.HomeScreen import io.github.yamin8000.owl.feature_home.ui.HomeViewModel import io.github.yamin8000.owl.feature_settings.ui.SettingsScreen import io.github.yamin8000.owl.ui.navigation.Nav internal class MainActivity : BaseActivity() { private var intentSearch: String? = null @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @ExperimentalMaterial3Api override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() super.onCreate(savedInstanceState) enableEdgeToEdge() intentSearch = intent.getStringExtra("Search") showContent { Scaffold { MainNav(onThemeChanged = { appTheme = it }) } } } @Composable private fun MainNav( onThemeChanged: (ThemeType) -> Unit ) { val start = "${Nav.Route.Home}/{Search}" val navController = rememberNavController() val onBackClick: () -> Unit = remember { { navController.navigateUp() } } NavHost( navController = navController, startDestination = start, enterTransition = { fadeIn(animationSpec = tween(250)) + scaleIn( animationSpec = tween(250), initialScale = .9f ) }, exitTransition = { fadeOut(animationSpec = tween(250)) }, builder = { composable(start) { val argSearch = it.arguments?.getString("Search") HomeScreen( onNavigateToAbout = { navController.navigate(Nav.Route.About()) }, onNavigateToSettings = { navController.navigate(Nav.Route.Settings()) }, onNavigateToFavourites = { navController.navigate(Nav.Route.Favourites()) }, onNavigateToHistory = { navController.navigate(Nav.Route.History()) }, vm = hiltViewModel( creationCallback = { factory -> factory.create( intentSearch = intentSearch, navigationSearch = argSearch ) } ) ) } composable(Nav.Route.About.toString()) { AboutScreen( installedVersionName = BuildConfig.VERSION_NAME, onBackClick = onBackClick ) } composable(Nav.Route.Favourites()) { FavouritesScreen( onBackClick = onBackClick, onFavouritesItemClick = { navController.navigate("${Nav.Route.Home}/${it}") }, ) } composable(Nav.Route.History()) { HistoryScreen( onBackClick = onBackClick, onHistoryItemClick = { navController.navigate("${Nav.Route.Home}/${it}") } ) } composable(Nav.Route.Settings()) { SettingsScreen( onBackClick = onBackClick, onThemeChanged = onThemeChanged ) } } ) } } ================================================ FILE: app/src/main/java/io/github/yamin8000/owl/ui/OverlayActivity.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.app.main * FloatingActivity.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * FloatingActivity.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.app.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.app.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.app.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.ui import android.content.Intent import android.os.Bundle import androidx.core.os.bundleOf import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import io.github.yamin8000.owl.feature_overlay.di.OverlayViewModelFactory import io.github.yamin8000.owl.feature_overlay.ui.OverlayScreen import io.github.yamin8000.owl.feature_overlay.ui.OverlayWindowViewModel internal class OverlayActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val data = handleOutsideInputIntent() showContent { OverlayScreen( onDismissRequest = { finish() }, navigateToApp = { val intent = Intent(this, MainActivity::class.java) intent.putExtras(bundleOf("Search" to it)) startActivity(intent) finish() }, vm = hiltViewModel( creationCallback = { factory -> factory.create(intentSearch = data) } ) ) } } private fun handleOutsideInputIntent(): String? { return if (intent.type == "text/plain") { when (intent.action) { Intent.ACTION_TRANSLATE, Intent.ACTION_DEFINE, Intent.ACTION_SEND -> intent.getStringExtra( Intent.EXTRA_TEXT ) Intent.ACTION_PROCESS_TEXT -> intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT) else -> null } } else { null } } } ================================================ FILE: app/src/main/java/io/github/yamin8000/owl/ui/navigation/Nav.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.app.main * Nav.kt Copyrighted by Yamin Siahmargooei at 2024/8/24 * Nav.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.app.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.app.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.app.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.ui.navigation /** Navigation singleton */ object Nav { sealed interface Route { data object Home : Route data object History : Route data object Settings : Route data object About : Route data object Favourites : Route operator fun invoke(): String { return this.toString() } } sealed interface Arguments { data object Search : Arguments operator fun invoke(): String { return this.toString() } } } ================================================ FILE: app/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: app/src/main/res/drawable/ic_launcher_foreground.xml ================================================ ================================================ FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: app/src/main/res/values/colors.xml ================================================ ================================================ FILE: app/src/main/res/values/ic_launcher_background.xml ================================================ ================================================ FILE: app/src/main/res/values/themes.xml ================================================ ================================================ FILE: app/src/main/res/values-night/themes.xml ================================================ ================================================ FILE: build.gradle.kts ================================================ /* * freeDictionaryApp/freeDictionaryApp * build.gradle.kts Copyrighted by Yamin Siahmargooei at 2024/5/9 * build.gradle.kts Last modified at 2024/5/5 * This file is part of freeDictionaryApp/freeDictionaryApp. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false alias(libs.plugins.jetbrains.kotlin.compose.plugin) apply false alias(libs.plugins.google.ksp) apply false alias(libs.plugins.jetbrains.kotlin.jvm) apply false alias(libs.plugins.hilt) apply false alias(libs.plugins.androix.room) apply false } tasks.register("sortLibs") { doFirst { val tomlFile = File("$projectDir${File.separator}gradle${File.separator}libs.versions.toml") val lines = tomlFile.readLines() var versions = mutableListOf() var libraries = mutableListOf() var plugins = mutableListOf() var isVersion = false var isLibrary = false var isPlugin = false for (line in lines) { if (line.isNotBlank()) { if (line == "[versions]") { isVersion = true isLibrary = false isPlugin = false continue } if (line == "[libraries]") { isVersion = false isLibrary = true isPlugin = false continue } if (line == "[plugins]") { isVersion = false isLibrary = false isPlugin = true continue } if (isVersion) { versions.add(line) } if (isLibrary) { libraries.add(line) } if (isPlugin) { plugins.add(line) } } } versions = versions.sorted().toMutableList() libraries = libraries.sorted().toMutableList() plugins = plugins.sorted().toMutableList() tomlFile.writeText( buildString { appendLine("[versions]") versions.forEach { appendLine(it) } appendLine() appendLine("[libraries]") libraries.forEach { appendLine(it) } appendLine() appendLine("[plugins]") plugins.forEach { appendLine(it) } }.trim() ) } } ================================================ FILE: common/.gitignore ================================================ /build ================================================ FILE: common/build.gradle.kts ================================================ /* * freeDictionaryApp/freeDictionaryApp.common * build.gradle.kts Copyrighted by Yamin Siahmargooei at 2025/9/8 * build.gradle.kts Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.common. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.google.ksp) alias(libs.plugins.jetbrains.kotlin.compose.plugin) alias(libs.plugins.hilt) } android { namespace = "io.github.yamin8000.owl.common" compileSdk = 36 defaultConfig { minSdk = 23 } compileOptions { isCoreLibraryDesugaringEnabled = true sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } } buildFeatures { compose = true buildConfig = true } } composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_compiler") metricsDestination = layout.buildDirectory.dir("compose_compiler") } dependencies { api(project(":strings")) api(project(":datastore")) //core android/kotlin coreLibraryDesugaring(libs.desugar.jdk.libs) api(libs.androidx.core.ktx) api(libs.androidx.lifecycle.viewmodel.compose) api(libs.kotlinx.collections.immutable) //compose api(libs.androidx.compose.ui) api(libs.androidx.compose.material) api(libs.androidx.compose.ui.tooling.preview) debugApi(libs.androidx.compose.ui.tooling) api(libs.androidx.activity.compose) api(libs.androidx.compose.material.icons.extended) api(libs.androidx.compose.material3) api(libs.androidx.compose.material3.window.size) api(libs.androidx.lifecycle.runtime.compose) api(platform(libs.androidx.compose.bom)) androidTestImplementation(platform(libs.androidx.compose.bom)) //hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) implementation(libs.hilt.lifecycle.compose) //lottie implementation(libs.lottie.compose) debugApi(libs.datafaker) } ================================================ FILE: common/src/debug/java/io/github/yamin8000/owl/common/CrudContentPreview.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * CrudContentPreview.kt Copyrighted by Yamin Siahmargooei at 2025/10/26 * CrudContentPreview.kt Last modified at 2025/10/26 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewFontScale import androidx.compose.ui.tooling.preview.PreviewScreenSizes import io.github.yamin8000.owl.common.ui.components.crud.CrudContent import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import kotlinx.collections.immutable.toImmutableList import net.datafaker.Faker import kotlin.random.Random import kotlin.random.nextInt @PreviewScreenSizes @PreviewFontScale @Preview @Composable private fun CrudContentPreview() { PreviewTheme { val faker = Faker() val items = buildList { repeat(Random.nextInt(0..100)) { val random = Random.nextInt(0..4) val word = when (random) { 0 -> faker.word().noun() 1 -> faker.word().verb() 2 -> faker.word().adverb() 3 -> faker.word().adjective() 4 -> faker.word().interjection() else -> faker.word().preposition() } add(word) } } CrudContent( title = faker.lorem().word(), items = items.toImmutableList(), onBackClick = {}, onRemoveAll = {}, onRemoveSingle = {}, onItemClick = {} ) } } ================================================ FILE: common/src/main/AndroidManifest.xml ================================================ ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/di/CommonModule.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * CommonModule.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * CommonModule.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.di import android.app.Application import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import io.github.yamin8000.owl.common.util.TTS import io.github.yamin8000.owl.datastore.domain.usecase.settings.SettingUseCases import kotlinx.coroutines.runBlocking import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object CommonModule { @Provides @Singleton fun providesTts( app: Application, settingUseCases: SettingUseCases ): TTS { return runBlocking { settingUseCases.getTTS() return@runBlocking TTS( app, settingUseCases.getTTS() ) } } } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/AppText.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * AppText.kt Copyrighted by Yamin Siahmargooei at 2025/9/8 * AppText.kt Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit import io.github.yamin8000.owl.common.ui.theme.Samim import io.github.yamin8000.owl.common.util.LocaleUtils @Composable fun AppText( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, minLines: Int = 1, onTextLayout: (TextLayoutResult) -> Unit = {}, style: TextStyle = LocalTextStyle.current ) { val hasAnyRtlCharacter = remember(text) { text.any { LocaleUtils.isRtlChar(it) } } Text( text = text, modifier = modifier, color = color, fontSize = fontSize, fontStyle = fontStyle, fontWeight = fontWeight, fontFamily = if (hasAnyRtlCharacter) Samim else fontFamily, letterSpacing = letterSpacing, textDecoration = textDecoration, textAlign = textAlign, lineHeight = lineHeight, overflow = overflow, softWrap = softWrap, maxLines = maxLines, minLines = minLines, onTextLayout = onTextLayout, style = style.copy(textDirection = if (hasAnyRtlCharacter) TextDirection.Rtl else TextDirection.Ltr) ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/ClickableIcon.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * ClickableIcon.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * ClickableIcon.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Settings import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.tooling.preview.Preview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme @Preview(showBackground = true) @Composable private fun Preview() { PreviewTheme { ClickableIcon( imageVector = Icons.TwoTone.Settings, onClick = {} ) } } @Composable fun ClickableIcon( imageVector: ImageVector, onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, contentDescription: String? = null ) { val haptic = LocalHapticFeedback.current val interactionSource = remember { MutableInteractionSource() } val pressed by interactionSource.collectIsPressedAsState() val rotation by animateFloatAsState(targetValue = if (pressed) 15f else 0f) IconButton( modifier = modifier.graphicsLayer { rotationZ = rotation }, enabled = enabled, interactionSource = interactionSource, onClick = { haptic.performHapticFeedback(HapticFeedbackType.ContextClick) onClick() }, content = { Icon( imageVector = imageVector, contentDescription = contentDescription, ) } ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/DeleteMenu.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * DeleteMenu.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * DeleteMenu.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Delete import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.strings.R @MyPreview @Composable private fun Preview() { PreviewTheme { Surface( modifier = Modifier .fillMaxSize() .padding(Sizes.xxLarge), content = { DeleteMenu( item = "Item", expanded = true, onDelete = {}, onDismiss = {}, onWordClick = {} ) } ) } } @Composable internal fun DeleteMenu( item: String, expanded: Boolean, onWordClick: () -> Unit, onDismiss: () -> Unit, onDelete: () -> Unit, modifier: Modifier = Modifier ) { val delete = stringResource(R.string.delete) DropdownMenu( modifier = modifier, expanded = expanded, onDismissRequest = onDismiss, content = { DropdownMenuItem( onClick = onWordClick, text = { AppText(item) }, ) DropdownMenuItem( onClick = onDelete, text = { AppText(text = delete) }, leadingIcon = { Icon( imageVector = Icons.TwoTone.Delete, contentDescription = delete ) } ) } ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/EmptyList.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * EmptyList.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * EmptyList.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.airbnb.lottie.compose.LottieAnimation import com.airbnb.lottie.compose.LottieCompositionSpec import com.airbnb.lottie.compose.LottieConstants import com.airbnb.lottie.compose.animateLottieCompositionAsState import com.airbnb.lottie.compose.rememberLottieComposition import io.github.yamin8000.owl.common.R @Composable fun EmptyList( modifier: Modifier = Modifier, ) { val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.empty_list)) val progress by animateLottieCompositionAsState( composition = composition, iterations = LottieConstants.IterateForever ) LottieAnimation( modifier = modifier, composition = composition, progress = { progress }, ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/HighlightText.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * HighlightText.kt Copyrighted by Yamin Siahmargooei at 2025/1/16 * HighlightText.kt Last modified at 2025/1/16 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme @MyPreview @Composable private fun Preview() { PreviewTheme { HighlightText( fullText = "eating", highlightedText = "ing" ) } } @Composable fun HighlightText( fullText: String, highlightedText: String, modifier: Modifier = Modifier, highlightedTextStyle: SpanStyle = SpanStyle( fontWeight = FontWeight.ExtraBold, textDecoration = TextDecoration.Underline ) ) { if (highlightedText.isNotBlank() && fullText.contains(highlightedText, true)) { val start = remember(fullText, highlightedText) { fullText.indexOf(highlightedText, 0, true) } val end = remember(start, highlightedText) { start + highlightedText.length } val text = remember(fullText, start, end) { buildAnnotatedString { append(fullText) addStyle(highlightedTextStyle, start, end) } } Text( modifier = modifier, text = text, textAlign = TextAlign.Justify ) } else { Text( modifier = modifier, text = fullText, textAlign = TextAlign.Justify ) } } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/MySnackbar.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * MySnackbar.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * MySnackbar.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Snackbar import androidx.compose.material3.SnackbarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import io.github.yamin8000.owl.common.ui.theme.Sizes @Composable fun MySnackbar( modifier: Modifier = Modifier, action: @Composable (() -> Unit)? = null, dismissAction: @Composable (() -> Unit)? = null, actionOnNewLine: Boolean = false, containerColor: Color = SnackbarDefaults.color, contentColor: Color = SnackbarDefaults.contentColor, actionContentColor: Color = SnackbarDefaults.actionContentColor, dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor, content: @Composable () -> Unit ) { Snackbar( modifier = modifier .padding(vertical = Sizes.Large, horizontal = Sizes.Large) .padding(WindowInsets.ime.asPaddingValues()), action = action, dismissAction = dismissAction, actionOnNewLine = actionOnNewLine, shape = RoundedCornerShape(Sizes.Medium), containerColor = containerColor, contentColor = contentColor, actionContentColor = actionContentColor, dismissActionContentColor = dismissActionContentColor, content = content ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/Ripple.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * Ripple.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * Ripple.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @OptIn(ExperimentalFoundationApi::class) @Composable fun Ripple( onClick: () -> Unit, modifier: Modifier = Modifier, onLongClick: () -> Unit = {}, content: @Composable BoxScope.() -> Unit ) { Box( content = content, modifier = modifier .combinedClickable( interactionSource = remember { MutableInteractionSource() }, indication = ripple(), onClick = onClick, onLongClick = onLongClick ) ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/ScaffoldWithTitle.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * ScaffoldWithTitle.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * ScaffoldWithTitle.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.twotone.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.strings.R @OptIn(ExperimentalMaterial3Api::class) @Composable fun ScaffoldWithTitle( title: String, onBackClick: () -> Unit, modifier: Modifier = Modifier, snackbarHost: @Composable () -> Unit = {}, content: @Composable BoxScope.() -> Unit ) { val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val connection by remember { mutableStateOf(scrollBehavior.nestedScrollConnection) } Scaffold( modifier = modifier .fillMaxSize() .nestedScroll(connection), snackbarHost = snackbarHost, topBar = { Surface( shadowElevation = Sizes.Large, content = { TopAppBar( scrollBehavior = scrollBehavior, title = { AppText( text = title, fontSize = 20.sp, textAlign = TextAlign.Center, maxLines = 1, overflow = TextOverflow.Ellipsis ) }, actions = { ClickableIcon( imageVector = Icons.AutoMirrored.TwoTone.ArrowBack, contentDescription = stringResource(R.string.back), onClick = onBackClick ) } ) } ) }, content = { Box( content = content, modifier = Modifier .padding(it) .padding( start = Sizes.Large, end = Sizes.Large, bottom = 0.dp, top = Sizes.Small ) ) } ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/Texts.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * Texts.kt Copyrighted by Yamin Siahmargooei at 2024/8/18 * Texts.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components import android.content.ClipData import android.content.Context import android.content.res.Configuration import android.media.AudioManager import android.widget.Toast import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.text.selection.LocalTextSelectionColors import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.TextSelectionColors import androidx.compose.material3.ElevatedSuggestionChip import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.window.Dialog import io.github.yamin8000.owl.common.ui.theme.DefaultCutShape import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.common.ui.theme.defaultGradientBorder import io.github.yamin8000.owl.common.util.ContextUtils.findActivity import io.github.yamin8000.owl.common.util.LocalTTS import io.github.yamin8000.owl.common.util.StringUtils.sanitizeWords import io.github.yamin8000.owl.strings.R @OptIn(ExperimentalFoundationApi::class) @Composable fun CopyAbleRippleText( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, onDoubleClick: ((String) -> Unit)? = null, content: @Composable (() -> Unit)? = null ) { val textCopied = stringResource(R.string.text_copied) val context = LocalContext.current val clipboardManager = LocalClipboard.current val haptic = LocalHapticFeedback.current var isDialogShown by remember { mutableStateOf(false) } Box( content = { Box( modifier = Modifier.padding(Sizes.Medium), content = { val selectionColors = TextSelectionColors( handleColor = MaterialTheme.colorScheme.secondary, backgroundColor = MaterialTheme.colorScheme.onSecondary ) CompositionLocalProvider( values = arrayOf(LocalTextSelectionColors provides selectionColors), content = { SelectionContainer(content = { content?.invoke() ?: Text(text) }) } ) } ) }, modifier = modifier .clip(DefaultCutShape) .combinedClickable( interactionSource = remember { MutableInteractionSource() }, indication = ripple(), onClick = onClick, onDoubleClick = { isDialogShown = true }, onLongClick = { haptic.performHapticFeedback(HapticFeedbackType.LongPress) clipboardManager.nativeClipboard.setPrimaryClip( ClipData.newPlainText(text, text) ) Toast.makeText(context, textCopied, Toast.LENGTH_SHORT) .show() } ) ) if (isDialogShown && onDoubleClick != null) { Dialog( onDismissRequest = { isDialogShown = false }, content = { Surface( shape = DefaultCutShape, content = { val words = remember(text) { sanitizeWords(text.split(Regex("\\s+")).toSet()).toList() } LazyVerticalGrid( columns = GridCells.Fixed(2), verticalArrangement = Arrangement.spacedBy( Sizes.Small, Alignment.CenterVertically ), horizontalArrangement = Arrangement.spacedBy( Sizes.Small, Alignment.CenterHorizontally ), modifier = Modifier.padding(Sizes.Medium), content = { items(words) { item -> ElevatedSuggestionChip( border = defaultGradientBorder(), onClick = { onDoubleClick(item) isDialogShown = false }, label = { Text( text = item, overflow = TextOverflow.Ellipsis, modifier = Modifier .padding(Sizes.Medium) .fillMaxSize() ) } ) } } ) } ) } ) } } @Composable fun CopyAbleRippleTextWithIcon( text: String, imageVector: ImageVector, onClick: () -> Unit, modifier: Modifier = Modifier, title: String? = null, onDoubleClick: ((String) -> Unit)? = null, content: @Composable (() -> Unit)? = null ) { Row( modifier = modifier, horizontalArrangement = Arrangement.spacedBy(Sizes.Medium, Alignment.CenterHorizontally), verticalAlignment = Alignment.CenterVertically, content = { val iconContent = remember(imageVector, text) { movableContentOf { Icon( imageVector = imageVector, contentDescription = null, tint = MaterialTheme.colorScheme.secondary ) } } if (title != null) { val config = LocalConfiguration.current val orientation = remember(config) { config.orientation } val density = LocalDensity.current val titleFraction = remember(density) { if (orientation == Configuration.ORIENTATION_PORTRAIT) { .25f * (density.fontScale + 1) / 2 } else { .15f * (density.fontScale + 1) / 2 } } Box( modifier = Modifier.fillMaxWidth(titleFraction), content = { val titleContent = remember { movableContentOf { iconContent() AppText( text = title, color = MaterialTheme.colorScheme.secondary, maxLines = 1, overflow = TextOverflow.Ellipsis ) } } if (orientation == Configuration.ORIENTATION_PORTRAIT) { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy( Sizes.Medium, Alignment.CenterVertically ), content = { titleContent() } ) } else { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy( Sizes.Medium, Alignment.Start ), content = { titleContent() } ) } } ) } else { iconContent() } CopyAbleRippleText( modifier = Modifier.fillMaxWidth(), text = text, content = content, onClick = onClick, onDoubleClick = onDoubleClick ) } ) } @Composable fun SpeakableRippleTextWithIcon( text: String, imageVector: ImageVector, modifier: Modifier = Modifier, ttsText: String = text, title: String? = null, onDoubleClick: ((String) -> Unit)? = null, content: @Composable (() -> Unit)? = null ) { val increaseVolumeText = stringResource(R.string.increase_volume_notice) val context = LocalContext.current val audio = remember { context.findActivity()?.getSystemService(Context.AUDIO_SERVICE) as AudioManager? } val tts = LocalTTS.current CopyAbleRippleTextWithIcon( modifier = modifier, text = text, content = content, title = title, imageVector = imageVector, onDoubleClick = onDoubleClick, onClick = { if (audio?.getStreamVolume(AudioManager.STREAM_MUSIC) == 0) { Toast.makeText(context, increaseVolumeText, Toast.LENGTH_SHORT).show() } tts?.speak(ttsText) Unit } ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/crud/CrudContent.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * CrudContent.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * CrudContent.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components.crud import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import io.github.yamin8000.owl.common.ui.components.EmptyList import io.github.yamin8000.owl.common.ui.components.ScaffoldWithTitle import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlin.random.Random import kotlin.random.nextInt @Preview(showBackground = true) @Composable private fun Preview() { PreviewTheme { CrudContent( title = "Title", onBackClick = {}, onRemoveAll = {}, onRemoveSingle = { _ -> }, onItemClick = {}, items = buildList { repeat(Random.nextInt(0..100)) { add("Item") } }.toImmutableList() ) } } @Composable fun CrudContent( title: String, items: ImmutableList, onBackClick: () -> Unit, onRemoveAll: () -> Unit, onRemoveSingle: (T) -> Unit, onItemClick: (T) -> Unit, modifier: Modifier = Modifier, itemDisplayProvider: (T) -> String = { it.toString() }, emptyContent: (@Composable () -> Unit) = { EmptyList( modifier = Modifier.fillMaxWidth() ) } ) { ScaffoldWithTitle( modifier = modifier, title = title, onBackClick = onBackClick, content = { val isNotEmpty by remember(items.size) { mutableStateOf(items.isNotEmpty()) } if (isNotEmpty) { LazyColumn( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy( Sizes.Medium, Alignment.CenterVertically ), content = { item { RemoveAllContent( modifier = Modifier.fillMaxWidth(), onRemoveAllClick = onRemoveAll ) } items( items = items, itemContent = { item -> CrudItem( item = itemDisplayProvider(item), onClick = { onItemClick(item) }, onLongClick = { onRemoveSingle(item) } ) } ) } ) } else emptyContent() } ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/crud/CrudItem.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * CrudItem.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * CrudItem.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components.crud import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @Composable internal fun CrudItem( item: String, onClick: () -> Unit, onLongClick: () -> Unit, modifier: Modifier = Modifier ) { RemovableCard( modifier = modifier, item = item, onClick = onClick, onLongClick = onLongClick ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/crud/RemovableCard.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * RemovableCard.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * RemovableCard.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components.crud import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import io.github.yamin8000.owl.common.ui.components.DeleteMenu import io.github.yamin8000.owl.common.ui.components.Ripple import io.github.yamin8000.owl.common.ui.theme.DefaultCutShape import io.github.yamin8000.owl.common.ui.theme.Sizes @Composable fun RemovableCard( item: String, onClick: () -> Unit, onLongClick: () -> Unit, modifier: Modifier = Modifier ) { OutlinedCard( modifier = modifier, shape = DefaultCutShape, border = BorderStroke(Sizes.xxSmall, MaterialTheme.colorScheme.tertiary), content = { var isMenuExpanded by remember { mutableStateOf(false) } Ripple( onClick = onClick, onLongClick = { isMenuExpanded = true }, content = { Text( text = item, maxLines = 1, textAlign = TextAlign.Center, overflow = TextOverflow.Ellipsis, modifier = Modifier .padding(vertical = Sizes.Medium) .padding(horizontal = Sizes.Small) .fillMaxWidth() ) } ) val haptic = LocalHapticFeedback.current DeleteMenu( item = item, expanded = isMenuExpanded, onDismiss = { isMenuExpanded = false }, onWordClick = onClick, onDelete = { isMenuExpanded = false haptic.performHapticFeedback(HapticFeedbackType.LongPress) onLongClick() } ) } ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/components/crud/RemoveAllContent.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * RemoveAllContent.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * RemoveAllContent.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.components.crud import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.theme.DefaultCutShape import io.github.yamin8000.owl.strings.R @Composable internal fun RemoveAllContent( onRemoveAllClick: () -> Unit, modifier: Modifier = Modifier ) { var isShowingDialog by remember { mutableStateOf(false) } if (isShowingDialog) { AlertDialog( onDismissRequest = { isShowingDialog = false }, title = { AppText(stringResource(R.string.clear_all)) }, text = { AppText(stringResource(R.string.remove_all_prompt)) }, confirmButton = { Button( onClick = onRemoveAllClick, content = { AppText(stringResource(R.string.yes)) } ) }, dismissButton = { Button( onClick = { isShowingDialog = false }, content = { AppText(stringResource(R.string.no)) } ) } ) } Button( modifier = modifier, onClick = { isShowingDialog = true }, shape = DefaultCutShape, content = { AppText(text = stringResource(R.string.clear_all)) } ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/theme/Color.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.app.main * Color.kt Copyrighted by Yamin Siahmargooei at 2024/5/9 * Color.kt Last modified at 2024/3/23 * This file is part of freeDictionaryApp/freeDictionaryApp.app.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.app.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.app.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.theme import androidx.compose.ui.graphics.Color val md_theme_light_primary = Color(0xFFBB0055) val md_theme_light_onPrimary = Color(0xFFFFFFFF) val md_theme_light_primaryContainer = Color(0xFFFFC4CE) val md_theme_light_onPrimaryContainer = Color(0xFF3F0018) val md_theme_light_secondary = Color(0xFF75565C) val md_theme_light_onSecondary = Color(0xFFFFFFFF) val md_theme_light_secondaryContainer = Color(0xFFFFD9DF) val md_theme_light_onSecondaryContainer = Color(0xFF2B151A) val md_theme_light_tertiary = Color(0xFF7A5733) val md_theme_light_onTertiary = Color(0xFFFFFFFF) val md_theme_light_tertiaryContainer = Color(0xFFFFDCBD) val md_theme_light_onTertiaryContainer = Color(0xFF2C1600) val md_theme_light_error = Color(0xFFBA1A1A) val md_theme_light_errorContainer = Color(0xFFFFDAD6) val md_theme_light_onError = Color(0xFFFFFFFF) val md_theme_light_onErrorContainer = Color(0xFF410002) val md_theme_light_background = Color(0xFFFBE9E7) val md_theme_light_onBackground = Color(0xFF201A1B) val md_theme_light_surface = Color(0xFFFBE9E7) val md_theme_light_onSurface = Color(0xFF201A1B) val md_theme_light_surfaceVariant = Color(0xFFF3DDE0) val md_theme_light_onSurfaceVariant = Color(0xFF524346) val md_theme_light_outline = Color(0xFF847375) val md_theme_light_inverseOnSurface = Color(0xFFFAEEEF) val md_theme_light_inverseSurface = Color(0xFF352F30) val md_theme_light_inversePrimary = Color(0xFFFFB1C2) val md_theme_light_surfaceTint = Color(0xFFBB0055) val md_theme_dark_primary = Color(0xFFFFB1C2) val md_theme_dark_onPrimary = Color(0xFF66002B) val md_theme_dark_primaryContainer = Color(0xFF8F0040) val md_theme_dark_onPrimaryContainer = Color(0xFFFFD9DF) val md_theme_dark_secondary = Color(0xFFE4BDC4) val md_theme_dark_onSecondary = Color(0xFF43292F) val md_theme_dark_secondaryContainer = Color(0xFF5B3F45) val md_theme_dark_onSecondaryContainer = Color(0xFFFFD9DF) val md_theme_dark_tertiary = Color(0xFFECBE91) val md_theme_dark_onTertiary = Color(0xFF462A09) val md_theme_dark_tertiaryContainer = Color(0xFF60401E) val md_theme_dark_onTertiaryContainer = Color(0xFFFFDCBD) val md_theme_dark_error = Color(0xFFFFB4AB) val md_theme_dark_errorContainer = Color(0xFF93000A) val md_theme_dark_onError = Color(0xFF690005) val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) val md_theme_dark_background = Color(0xFF201A1B) val md_theme_dark_onBackground = Color(0xFFECE0E0) val md_theme_dark_surface = Color(0xFF201A1B) val md_theme_dark_onSurface = Color(0xFFECE0E0) val md_theme_dark_surfaceVariant = Color(0xFF524346) val md_theme_dark_onSurfaceVariant = Color(0xFFD6C2C4) val md_theme_dark_outline = Color(0xFF9E8C8F) val md_theme_dark_inverseOnSurface = Color(0xFF201A1B) val md_theme_dark_inverseSurface = Color(0xFFECE0E0) val md_theme_dark_inversePrimary = Color(0xFFBB0055) val md_theme_dark_surfaceTint = Color(0xFFFFB1C2) ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/theme/Shape.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.app.main * Shape.kt Copyrighted by Yamin Siahmargooei at 2024/5/9 * Shape.kt Last modified at 2024/3/23 * This file is part of freeDictionaryApp/freeDictionaryApp.app.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.app.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.app.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.theme import androidx.compose.animation.animateColor import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.infiniteRepeatable import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Brush val DefaultCutShape = CutCornerShape(Sizes.Medium) @Composable fun defaultGradientBorder( duration: Int = 5000 ): BorderStroke { val animation = rememberInfiniteTransition(label = "") val start by animation.animateColor( initialValue = MaterialTheme.colorScheme.primary, targetValue = MaterialTheme.colorScheme.tertiary, label = "", animationSpec = infiniteRepeatable( tween(duration), repeatMode = RepeatMode.Reverse ) ) val end by animation.animateColor( initialValue = MaterialTheme.colorScheme.tertiary, targetValue = MaterialTheme.colorScheme.primary, label = "", animationSpec = infiniteRepeatable( tween(duration), repeatMode = RepeatMode.Reverse ) ) return BorderStroke( width = Sizes.xxSmall, brush = Brush.verticalGradient(listOf(start, end)) ) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/theme/Sizes.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * Sizes.kt Copyrighted by Yamin Siahmargooei at 2025/1/16 * Sizes.kt Last modified at 2025/1/16 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.theme import androidx.compose.ui.unit.dp @Suppress("unused") object Sizes { /** 1.dp */ val xxSmall = 1.dp /** 2.dp */ val xSmall = 2.dp /** 4.dp */ val Small = 4.dp /** 8.dp */ val Medium = 8.dp /** 16.dp */ val Large = 16.dp /** 32.dp */ val xLarge = 32.dp /** 64.dp */ val xxLarge = 64.dp } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/theme/Theme.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * Theme.kt Copyrighted by Yamin Siahmargooei at 2024/8/18 * Theme.kt Last modified at 2024/8/17 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ @file:OptIn(ExperimentalMaterial3ExpressiveApi::class) package io.github.yamin8000.owl.common.ui.theme import android.app.Activity import android.os.Build import androidx.compose.foundation.background import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MotionScheme import androidx.compose.material3.Text import androidx.compose.material3.darkColorScheme import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.tooling.preview.Preview import androidx.core.view.WindowCompat import kotlin.random.Random val lightColors = lightColorScheme( primary = md_theme_light_primary, onPrimary = md_theme_light_onPrimary, primaryContainer = md_theme_light_primaryContainer, onPrimaryContainer = md_theme_light_onPrimaryContainer, inversePrimary = md_theme_light_inversePrimary, secondary = md_theme_light_secondary, onSecondary = md_theme_light_onSecondary, secondaryContainer = md_theme_light_secondaryContainer, onSecondaryContainer = md_theme_light_onSecondaryContainer, tertiary = md_theme_light_tertiary, onTertiary = md_theme_light_onTertiary, tertiaryContainer = md_theme_light_tertiaryContainer, onTertiaryContainer = md_theme_light_onTertiaryContainer, background = md_theme_light_background, onBackground = md_theme_light_onBackground, surface = md_theme_light_surface, onSurface = md_theme_light_onSurface, surfaceVariant = md_theme_light_surfaceVariant, onSurfaceVariant = md_theme_light_onSurfaceVariant, surfaceTint = md_theme_light_surfaceTint, inverseSurface = md_theme_light_inverseSurface, inverseOnSurface = md_theme_light_inverseOnSurface, error = md_theme_light_error, onError = md_theme_light_onError, errorContainer = md_theme_light_errorContainer, onErrorContainer = md_theme_light_onErrorContainer, outline = md_theme_light_outline, ) val darkColors = darkColorScheme( primary = md_theme_dark_primary, onPrimary = md_theme_dark_onPrimary, primaryContainer = md_theme_dark_primaryContainer, onPrimaryContainer = md_theme_dark_onPrimaryContainer, secondary = md_theme_dark_secondary, onSecondary = md_theme_dark_onSecondary, secondaryContainer = md_theme_dark_secondaryContainer, onSecondaryContainer = md_theme_dark_onSecondaryContainer, tertiary = md_theme_dark_tertiary, onTertiary = md_theme_dark_onTertiary, tertiaryContainer = md_theme_dark_tertiaryContainer, onTertiaryContainer = md_theme_dark_onTertiaryContainer, error = md_theme_dark_error, errorContainer = md_theme_dark_errorContainer, onError = md_theme_dark_onError, onErrorContainer = md_theme_dark_onErrorContainer, background = md_theme_dark_background, onBackground = md_theme_dark_onBackground, surface = md_theme_dark_surface, onSurface = md_theme_dark_onSurface, surfaceVariant = md_theme_dark_surfaceVariant, onSurfaceVariant = md_theme_dark_onSurfaceVariant, outline = md_theme_dark_outline, inverseOnSurface = md_theme_dark_inverseOnSurface, inverseSurface = md_theme_dark_inverseSurface, inversePrimary = md_theme_dark_inversePrimary, surfaceTint = md_theme_dark_surfaceTint, ) @Composable fun PreviewTheme( isDarkTheme: Boolean = Random.nextBoolean(), isOledTheme: Boolean = Random.nextBoolean(), isDynamicColor: Boolean = Random.nextBoolean(), content: @Composable () -> Unit ) { Column( content = { Text( text = "isDark $isDarkTheme" ) Text( text = "isOledTheme $isOledTheme" ) Text( text = "isDynamicColor $isDynamicColor" ) Box( modifier = Modifier .height(Sizes.Large) .fillMaxWidth() .background(Color.Red) ) AppTheme( isDarkTheme = isDarkTheme, isOledTheme = isOledTheme, isPreviewing = true, isDynamicColor = isDynamicColor, content = content ) } ) } @Preview( showBackground = true, ) annotation class MyPreview @Composable fun AppTheme( isDarkTheme: Boolean = isSystemInDarkTheme(), isOledTheme: Boolean = false, isPreviewing: Boolean = false, isDynamicColor: Boolean = false, content: @Composable () -> Unit ) { val isDynamicColorReadyDevice = isDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S var colors = when { isDynamicColorReadyDevice && isDarkTheme -> dynamicDarkColorScheme(LocalContext.current) isDynamicColorReadyDevice && !isDarkTheme -> dynamicLightColorScheme(LocalContext.current) isDynamicColorReadyDevice && isOledTheme -> dynamicDarkColorScheme(LocalContext.current) isDarkTheme -> darkColors else -> lightColors } if (isDarkTheme && isOledTheme) { colors = colors.copy( onPrimary = colors.onPrimary.darken(), primaryContainer = colors.primaryContainer.darken(), onSecondary = colors.onSecondary.darken(), secondaryContainer = colors.secondaryContainer.darken(), onTertiary = colors.onTertiary.darken(), tertiaryContainer = colors.tertiaryContainer.darken(), background = Color(0xFF000000), surface = Color(0xFF000000), surfaceVariant = Color(0xFF000000), inverseOnSurface = Color(0xFF000000), inversePrimary = colors.inversePrimary.darken(), surfaceTint = colors.surfaceTint.darken(), ) } if (!isPreviewing) { val activity = LocalView.current.context as Activity SideEffect { val wic = WindowCompat.getInsetsController(activity.window, activity.window.decorView) wic.isAppearanceLightStatusBars = !isDarkTheme wic.isAppearanceLightNavigationBars = !isDarkTheme } } MaterialTheme( colorScheme = colors, content = content, motionScheme = MotionScheme.expressive() ) } private fun Color.darken(): Color { return copy(alpha, red / 5, green / 5, blue / 5) } @Suppress("unused") private fun Color.lighten(): Color { return copy(alpha, red + (1f - red) / 2, green + (1f - green) / 2, blue + (1f - blue) / 2) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/ui/theme/Type.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.app.main * Type.kt Copyrighted by Yamin Siahmargooei at 2024/5/9 * Type.kt Last modified at 2024/3/23 * This file is part of freeDictionaryApp/freeDictionaryApp.app.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.app.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.app.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.ui.theme import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import io.github.yamin8000.owl.common.R val Samim = FontFamily(Font(R.font.samimbold)) ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/util/Constants.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * Constants.kt Copyrighted by Yamin Siahmargooei at 2025/11/17 * Constants.kt Last modified at 2025/11/17 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.util /** * App-wide constants */ object Constants { const val LOG_TAG = "<==>" } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/util/ContextUtils.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * ContextUtils.kt Copyrighted by Yamin Siahmargooei at 2024/8/18 * ContextUtils.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.util import android.app.Activity import android.content.Context import android.content.ContextWrapper object ContextUtils { /** Finds current activity instance or null */ fun Context.findActivity(): Activity? = when (this) { is Activity -> this is ContextWrapper -> baseContext.findActivity() else -> null } } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/util/DateTimeUtils.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * DateTimeUtils.kt Copyrighted by Yamin Siahmargooei at 2024/8/30 * DateTimeUtils.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.util import java.time.LocalDateTime import java.time.ZoneOffset object DateTimeUtils { fun epoch() = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/util/LocalTTS.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * LocalTTS.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * LocalTTS.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.util import androidx.compose.runtime.compositionLocalOf val LocalTTS = compositionLocalOf { null } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/util/LocaleUtils.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * LocaleUtils.kt Copyrighted by Yamin Siahmargooei at 2025/9/8 * LocaleUtils.kt Last modified at 2025/7/5 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.util object LocaleUtils { fun isRtlChar(char: Char): Boolean = when (Character.getDirectionality(char)) { Character.DIRECTIONALITY_RIGHT_TO_LEFT, Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC, Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING, Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE, -> true else -> false } } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/util/StringUtils.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * StringUtils.kt Copyrighted by Yamin Siahmargooei at 2024/8/18 * StringUtils.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.util object StringUtils { /** * This method sanitize words from the given [data] set by removing * unnecessary characters like white spaces and numbers, etc. * and making them lowercase and filtering out blank entries */ fun sanitizeWords( data: Set ): Set = data.asSequence() .map { sanitizeWord(it) } .filter { it.isNotBlank() } .toSet() fun sanitizeWord( data: String ): String = data.replace(Regex("\\W+"), "") .lowercase() .trim() } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/util/TTS.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.common.main * TTS.kt Copyrighted by Yamin Siahmargooei at 2024/8/20 * TTS.kt Last modified at 2024/8/20 * This file is part of freeDictionaryApp/freeDictionaryApp.common.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.common.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.common.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.util import android.content.Context import android.speech.tts.TextToSpeech import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.util.Locale /** A helper/wrapper for [TextToSpeech] */ class TTS( private val context: Context, private val languageTag: String ) { private var tts: TextToSpeech? = null private val scope = CoroutineScope(Dispatchers.Main) init { scope.launch { createEngine() } } fun createEngine(tag: String = languageTag) { tts = TextToSpeech(context) { if (it == TextToSpeech.SUCCESS) { tts?.language = Locale.forLanguageTag(tag) } } } /** * Extension method for [TextToSpeech] that calls [TextToSpeech.speak] with * some predefined parameters */ fun speak(text: String) { val current = this.languageTag println(current) tts?.speak(text, TextToSpeech.QUEUE_FLUSH, null, null) } fun englishLanguages() = tts?.availableLanguages ?.filter { it.language == Locale.ENGLISH.language } ?: listOf() } ================================================ FILE: common/src/main/java/io/github/yamin8000/owl/common/util/Utility.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.app.main * Utility.kt Copyrighted by Yamin Siahmargooei at 2024/5/9 * Utility.kt Last modified at 2024/3/23 * This file is part of freeDictionaryApp/freeDictionaryApp.app.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.app.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.app.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.common.util import android.util.Log import androidx.compose.ui.graphics.Color import io.github.yamin8000.owl.common.BuildConfig import kotlin.random.Random /** Prints [message] to logcat if app is in debug build */ fun log( message: String ) { if (BuildConfig.DEBUG) { Log.d(Constants.LOG_TAG, message) } } fun randomColor(): Color { val hue = Random.nextDouble(0.0, 360.0) val saturation = Random.nextDouble(50.0, 100.0) / 100f val value = Random.nextDouble(75.0, 100.0) / 100f val hsv = floatArrayOf(hue.toFloat(), saturation.toFloat(), value.toFloat()) return Color(android.graphics.Color.HSVToColor(255, hsv)) } ================================================ FILE: common/src/main/res/raw/empty_list.json ================================================ {"v":"5.5.9","fr":60,"ip":0,"op":295,"w":426,"h":290,"nm":"search_empty","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"mouth","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":44,"s":[211,145,0],"to":[0,0.667,0],"ti":[0,-0.667,0]},{"t":69,"s":[211,149,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":44,"s":[100,100,100]},{"t":68,"s":[105,105,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.5,0.25],[-1.715,0.372]],"o":[[-13.025,-6.512],[7.5,-1.625]],"v":[[-10,12.25],[-31.75,12.625]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.454901960784,0.513725490196,0.580392156863,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"汗","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":51,"s":[0]},{"t":95,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":51,"s":[248,95.252,0],"to":[0,1.5,0],"ti":[0,-1.5,0]},{"t":94,"s":[248,104.252,0]}],"ix":2},"a":{"a":0,"k":[0,-19.333,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":51,"s":[40,40,100]},{"t":95,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-9.94,0],[0,7.27],[0,0],[0,-7.27]],"o":[[9.94,0],[0,-7.27],[0,0],[0,7.27]],"v":[[0,19],[18,5.84],[0,-19],[-18,5.84]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"gf","o":{"a":0,"k":100,"ix":10},"r":1,"bm":0,"g":{"p":3,"k":{"a":0,"k":[0,0.69,0.976,1,0.5,0.714,0.954,0.997,1,0.738,0.932,0.994],"ix":9}},"s":{"a":0,"k":[0.019,-9.236],"ix":5},"e":{"a":0,"k":[10.208,19],"ix":6},"t":1,"nm":"Gradient Fill 1","mn":"ADBE Vector Graphic - G-Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"汗","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":569,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"eye","parent":1,"sr":1,"ks":{"o":{"a":0,"k":92,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-7.064,-11.347,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":3,"s":[100,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":6,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":9,"s":[100,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":12,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":50,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":53,"s":[100,20,100]},{"t":56,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[8.466,16.135],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.3411764705882353,0.3411764705882353,0.3411764705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"eye","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":569,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"eye","parent":1,"sr":1,"ks":{"o":{"a":0,"k":92,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-33.767,-11.347,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":3,"s":[100,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":6,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":9,"s":[100,20,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":12,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":50,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":53,"s":[100,20,100]},{"t":56,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[8.466,16.135],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.3411764705882353,0.3411764705882353,0.3411764705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"eye","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":569,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"magnifier","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[213,145,0],"ix":2},"a":{"a":0,"k":[213,145,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":426,"h":290,"ip":0,"op":569,"st":0,"bm":0}]},{"id":"comp_1","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[213,145,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"st","c":{"a":0,"k":[0.435294117647,0.498039215686,0.564705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"橢圓形","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-5,"ix":10},"p":{"a":0,"k":[190.106,142.222,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[99.994,100.091],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-40,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"橢圓形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"橢圓形","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-5,"ix":10},"p":{"a":0,"k":[192.013,142.223,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-26.81,22.62],[22.49,26.96],[26.8,-22.62],[-22.5,-26.95]],"o":[[26.81,-22.62],[-22.49,-26.96],[-26.81,22.62],[22.49,26.96]],"v":[[40.727,48.812],[48.537,-40.958],[-40.723,-48.808],[-48.533,40.952]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.8588235294117647,0.8588235294117647,0.8588235294117647,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"橢圓形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"矩形","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-5,"ix":10},"p":{"a":0,"k":[268.024,226.941,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[42.094,74.507],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":10,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.5882352941176471,0.5882352941176471,0.5882352941176471,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-40,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"矩形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"矩形","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-5,"ix":10},"p":{"a":0,"k":[238.933,197.25,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[24.446,44.702],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.847000002861,0.847000002861,0.847000002861,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-40,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"矩形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}]},{"id":"comp_2","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"合併形狀","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":60,"s":[0]},{"t":265,"s":[360]}],"ix":10},"p":{"a":0,"k":[208,27,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[16,2],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"rc","d":1,"s":{"a":0,"k":[2,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 2","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"mm","mm":2,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.796078026295,0.835294008255,0.843137025833,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"合併形狀","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"合併形狀","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":358,"s":[360]}],"ix":10},"p":{"a":0,"k":[418,107,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[16,2],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"rc","d":1,"s":{"a":0,"k":[2,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 2","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"mm","mm":2,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.796078026295,0.835294008255,0.843137025833,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"合併形狀","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"合併形狀","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":281,"s":[360]}],"ix":10},"p":{"a":0,"k":[31,196,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[16,2],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"rc","d":1,"s":{"a":0,"k":[2,18],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 2","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"mm","mm":2,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.796078026295,0.835294008255,0.843137025833,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"合併形狀","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"橢圓形","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":281,"s":[360]}],"ix":10},"p":{"a":0,"k":[70,33,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.701960980892,0.756862998009,0.776471018791,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"橢圓形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}]},{"id":"comp_3","layers":[{"ddd":0,"ind":1,"ty":4,"nm":"橢圓形","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[306,9,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[16,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.701960980892,0.756862998009,0.776471018791,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"橢圓形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"橢圓形","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[376,179,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[16,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.701960980892,0.768626987934,0.776471018791,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"橢圓形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"橢圓形","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[8,115,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[16,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.701960980892,0.768626987934,0.776471018791,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"橢圓形","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"shadow","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[191,145,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[70,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":30,"s":[50,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":60,"s":[70,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":90,"s":[50,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":120,"s":[70,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":150,"s":[50,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":180,"s":[70,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":210,"s":[50,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":240,"s":[70,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":270,"s":[50,100,100]},{"t":295,"s":[70,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56.697,6.631],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0.848757874732,0.861812935623,0.876608455882,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-9.651,124.815],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"main","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[213,145,0],"to":[0,0.833,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":30,"s":[213,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[213,145,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[213,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[213,145,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":150,"s":[213,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":180,"s":[213,145,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":210,"s":[213,150,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":240,"s":[213,145,0],"to":[0,0,0],"ti":[0,-0.833,0]},{"t":270,"s":[213,150,0]}],"ix":2},"a":{"a":0,"k":[213,145,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":426,"h":290,"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"decoration1","refId":"comp_2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.981],"y":[1.128]},"o":{"x":[0.39],"y":[0.019]},"t":8,"s":[0]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":40,"s":[100]},{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":72,"s":[80]},{"t":73,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[213,289,0],"ix":2},"a":{"a":0,"k":[213,289,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.667],"y":[0.866,0.866,1]},"o":{"x":[0.37,0.37,0.333],"y":[-0.029,-0.029,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":40,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":72,"s":[80,80,100]},{"t":133,"s":[90,90,100]}],"ix":6}},"ao":0,"w":426,"h":290,"ip":0,"op":569,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"decoratio2","refId":"comp_3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":41,"s":[100]},{"t":72,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[211,291,0],"ix":2},"a":{"a":0,"k":[211,291,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[0,0,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":43,"s":[99.442,99.442,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":72,"s":[84.442,84.442,100]},{"t":133,"s":[90,90,100]}],"ix":6}},"ao":0,"w":426,"h":290,"ip":0,"op":569,"st":0,"bm":0}],"markers":[{"tm":180,"cm":"1","dr":0},{"tm":3600,"cm":"2","dr":0}]} ================================================ FILE: datastore/.gitignore ================================================ /build ================================================ FILE: datastore/build.gradle.kts ================================================ import org.jetbrains.kotlin.gradle.dsl.JvmTarget /* * freeDictionaryApp/freeDictionaryApp.data * build.gradle.kts Copyrighted by Yamin Siahmargooei at 2024/5/9 * build.gradle.kts Last modified at 2024/5/5 * This file is part of freeDictionaryApp/freeDictionaryApp.data. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.data is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.data is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ plugins { alias(libs.plugins.android.library) alias(libs.plugins.google.ksp) } android { namespace = "com.github.yamin8000.owl.data" compileSdk = 36 defaultConfig { minSdk = 23 } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } } } dependencies { //datastore api(libs.androidx.datastore.preferences) //hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) implementation(libs.hilt.lifecycle.compose) } ================================================ FILE: datastore/src/main/AndroidManifest.xml ================================================ ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/data/datasource/Datastore.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * Datastores.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * Datastores.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.data.datasource import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStore object Datastore { internal val Context.settingsDataStore: DataStore by preferencesDataStore(name = "settings") internal val Context.historyDataStore: DataStore by preferencesDataStore(name = "history") internal val Context.favouritesDataStore: DataStore by preferencesDataStore(name = "favourites") } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/data/repository/BasicDatastoreRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * DataStoreRepository.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * DataStoreRepository.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.data.repository import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import io.github.yamin8000.owl.datastore.domain.repository.BaseDatastoreRepository import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map open class BasicDatastoreRepository( private val datastore: DataStore ) : BaseDatastoreRepository { override suspend fun getString( key: String ) = datastore.data.map { it[stringPreferencesKey(key)] }.firstOrNull() override suspend fun setString( key: String, value: String ) { datastore.edit { it[stringPreferencesKey(key)] = value } } override suspend fun getInt( key: String ) = datastore.data.map { it[intPreferencesKey(key)] }.firstOrNull() override suspend fun setInt( key: String, value: Int ) { datastore.edit { it[intPreferencesKey(key)] = value } } override suspend fun getBool( key: String ) = datastore.data.map { it[booleanPreferencesKey(key)] }.firstOrNull() override suspend fun setBool( key: String, value: Boolean ) { datastore.edit { it[booleanPreferencesKey(key)] = value } } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/data/repository/FavouriteDatastoreRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * FavouriteDatastoreRepository.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * FavouriteDatastoreRepository.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.data.repository import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import io.github.yamin8000.owl.datastore.domain.repository.BaseDatastoreRepository import io.github.yamin8000.owl.datastore.domain.repository.FavouriteRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map class FavouriteDatastoreRepository( private val datastore: DataStore ) : FavouriteRepository, BaseDatastoreRepository by BasicDatastoreRepository(datastore) { override suspend fun add(history: String) { datastore.edit { it[stringPreferencesKey(history)] = history } } override suspend fun remove(history: String) { datastore.edit { it.remove(stringPreferencesKey(history)) } } override suspend fun removeAll() { datastore.edit { it.clear() } } override suspend fun all(): Flow> { val test = datastore.data.map { preferences -> preferences.asMap().map { entry -> entry.value.toString() } } return test } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/data/repository/HistoryDatastoreRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * HistoryDatastoreRepository.kt Copyrighted by Yamin Siahmargooei at 2024/8/24 * HistoryDatastoreRepository.kt Last modified at 2024/8/24 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.data.repository import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import io.github.yamin8000.owl.datastore.domain.repository.BaseDatastoreRepository import io.github.yamin8000.owl.datastore.domain.repository.HistoryRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map class HistoryDatastoreRepository( private val datastore: DataStore ) : HistoryRepository, BaseDatastoreRepository by BasicDatastoreRepository(datastore) { override suspend fun add(history: String) { datastore.edit { it[stringPreferencesKey(history)] = history } } override suspend fun remove(history: String) { datastore.edit { it.remove(stringPreferencesKey(history)) } } override suspend fun removeAll() { datastore.edit { it.clear() } } override suspend fun all(): Flow> { val test = datastore.data.map { preferences -> preferences.asMap().map { entry -> entry.value.toString() } } return test } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/data/repository/SettingsDatastoreRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * SettingsRepository.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * SettingsRepository.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.data.repository import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import io.github.yamin8000.owl.datastore.domain.model.SettingsKeys import io.github.yamin8000.owl.datastore.domain.model.ThemeType import io.github.yamin8000.owl.datastore.domain.repository.BaseDatastoreRepository import io.github.yamin8000.owl.datastore.domain.repository.SettingsRepository class SettingsDatastoreRepository( private val datastore: DataStore ) : SettingsRepository, BaseDatastoreRepository by BasicDatastoreRepository(datastore) { override suspend fun getTheme(): ThemeType { return ThemeType.toType(getString(SettingsKeys.THEME)) } override suspend fun setTheme(theme: ThemeType) { setString(SettingsKeys.THEME, theme.toString()) } override suspend fun getTtsLang(): String? { return getString(SettingsKeys.TTS_LANG) } override suspend fun setTtsLang(ttsLang: String) { setString(SettingsKeys.TTS_LANG, ttsLang) } override suspend fun getIsVibrating(): Boolean { return getBool(SettingsKeys.IS_VIBRATING) != false } override suspend fun setIsVibrating(value: Boolean) { setBool(SettingsKeys.IS_VIBRATING, value) } override suspend fun getIsStartingBlank(): Boolean { return getBool(SettingsKeys.IS_STARTING_BLANK) != false } override suspend fun setIsStartingBlank(value: Boolean) { setBool(SettingsKeys.IS_STARTING_BLANK, value) } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/di/DatastoreModule.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * DatastoreModule.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * DatastoreModule.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.di import android.app.Application import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import io.github.yamin8000.owl.datastore.data.datasource.Datastore.favouritesDataStore import io.github.yamin8000.owl.datastore.data.datasource.Datastore.historyDataStore import io.github.yamin8000.owl.datastore.data.datasource.Datastore.settingsDataStore import io.github.yamin8000.owl.datastore.data.repository.FavouriteDatastoreRepository import io.github.yamin8000.owl.datastore.data.repository.HistoryDatastoreRepository import io.github.yamin8000.owl.datastore.data.repository.SettingsDatastoreRepository import io.github.yamin8000.owl.datastore.domain.repository.FavouriteRepository import io.github.yamin8000.owl.datastore.domain.repository.HistoryRepository import io.github.yamin8000.owl.datastore.domain.repository.SettingsRepository import io.github.yamin8000.owl.datastore.domain.usecase.favourites.AddFavourite import io.github.yamin8000.owl.datastore.domain.usecase.favourites.FavouriteUseCases import io.github.yamin8000.owl.datastore.domain.usecase.favourites.GetAllFavourite import io.github.yamin8000.owl.datastore.domain.usecase.favourites.RemoveAllFavourite import io.github.yamin8000.owl.datastore.domain.usecase.favourites.RemoveFavourite import io.github.yamin8000.owl.datastore.domain.usecase.history.AddHistory import io.github.yamin8000.owl.datastore.domain.usecase.history.GetAllHistory import io.github.yamin8000.owl.datastore.domain.usecase.history.HistoryUseCases import io.github.yamin8000.owl.datastore.domain.usecase.history.RemoveAllHistory import io.github.yamin8000.owl.datastore.domain.usecase.history.RemoveHistory import io.github.yamin8000.owl.datastore.domain.usecase.settings.GetStartingBlank import io.github.yamin8000.owl.datastore.domain.usecase.settings.GetTTS import io.github.yamin8000.owl.datastore.domain.usecase.settings.GetTheme import io.github.yamin8000.owl.datastore.domain.usecase.settings.GetVibration import io.github.yamin8000.owl.datastore.domain.usecase.settings.SetStartingBlank import io.github.yamin8000.owl.datastore.domain.usecase.settings.SetTTS import io.github.yamin8000.owl.datastore.domain.usecase.settings.SetTheme import io.github.yamin8000.owl.datastore.domain.usecase.settings.SetVibration import io.github.yamin8000.owl.datastore.domain.usecase.settings.SettingUseCases import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object DatastoreModule { @Provides @Singleton fun provideSettingsRepository(app: Application): SettingsRepository { return SettingsDatastoreRepository(app.settingsDataStore) } @Provides @Singleton fun providesSettingsUseCases(repository: SettingsRepository): SettingUseCases { return SettingUseCases( getTTS = GetTTS(repository), setTTS = SetTTS(repository), getTheme = GetTheme(repository), setTheme = SetTheme(repository), getVibration = GetVibration(repository), setVibration = SetVibration(repository), getStartingBlank = GetStartingBlank(repository), setStartingBlank = SetStartingBlank(repository) ) } @Provides @Singleton fun provideHistoryRepository(app: Application): HistoryRepository { return HistoryDatastoreRepository(app.historyDataStore) } @Provides @Singleton fun providesHistoryUseCases(repository: HistoryRepository): HistoryUseCases { return HistoryUseCases( addHistory = AddHistory(repository), removeHistory = RemoveHistory(repository), removeAllHistory = RemoveAllHistory(repository), getAllHistory = GetAllHistory(repository) ) } @Provides @Singleton fun provideFavouriteRepository(app: Application): FavouriteRepository { return FavouriteDatastoreRepository(app.favouritesDataStore) } @Provides @Singleton fun providesFavouriteUseCases(repository: FavouriteRepository): FavouriteUseCases { return FavouriteUseCases( addFavourite = AddFavourite(repository), removeFavourite = RemoveFavourite(repository), removeAllFavourite = RemoveAllFavourite(repository), getAllFavourite = GetAllFavourite(repository) ) } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/model/SettingsKeys.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * SettingsKeys.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * SettingsKeys.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.model object SettingsKeys { const val THEME = "theme" const val TTS_LANG = "tts_lang" const val IS_VIBRATING = "is_vibrating" const val IS_STARTING_BLANK = "is_starting_blank" } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/model/ThemeType.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * ThemeType.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * ThemeType.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.model import androidx.compose.runtime.Stable @Stable sealed interface ThemeType { data object Dark : ThemeType data object Light : ThemeType data object System : ThemeType data object Darker : ThemeType data object SystemDarker : ThemeType companion object { fun toType(value: String?): ThemeType { return when (value) { "Dark" -> Dark "Light" -> Light "System" -> System "Darker" -> Darker "SystemDarker" -> SystemDarker else -> System } } fun entries() = listOf(Dark, Light, System, Darker, SystemDarker) } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/repository/BaseDatastoreRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * DatastoreRepository.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * DatastoreRepository.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.repository interface BaseDatastoreRepository { suspend fun getString(key: String): String? suspend fun setString(key: String, value: String) suspend fun getInt(key: String): Int? suspend fun setInt(key: String, value: Int) suspend fun getBool(key: String): Boolean? suspend fun setBool(key: String, value: Boolean) } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/repository/FavouriteRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * FavouriteRepository.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * FavouriteRepository.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.repository import kotlinx.coroutines.flow.Flow interface FavouriteRepository { suspend fun add(history: String) suspend fun remove(history: String) suspend fun removeAll() suspend fun all(): Flow> } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/repository/HistoryRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * HistoryRepository.kt Copyrighted by Yamin Siahmargooei at 2024/8/24 * HistoryRepository.kt Last modified at 2024/8/24 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.repository import kotlinx.coroutines.flow.Flow interface HistoryRepository { suspend fun add(history: String) suspend fun remove(history: String) suspend fun removeAll() suspend fun all(): Flow> } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/repository/SettingsRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * SettingsRepository.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * SettingsRepository.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.repository import io.github.yamin8000.owl.datastore.domain.model.ThemeType interface SettingsRepository : BaseDatastoreRepository { suspend fun getTheme(): ThemeType suspend fun setTheme(theme: ThemeType) suspend fun getTtsLang(): String? suspend fun setTtsLang(ttsLang: String) suspend fun getIsVibrating(): Boolean suspend fun setIsVibrating(value: Boolean) suspend fun getIsStartingBlank(): Boolean suspend fun setIsStartingBlank(value: Boolean) } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/favourites/AddFavourite.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * AddFavourite.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * AddFavourite.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.favourites import io.github.yamin8000.owl.datastore.domain.repository.FavouriteRepository class AddFavourite( private val repository: FavouriteRepository ) { suspend operator fun invoke(favourite: String) { repository.add(favourite) } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/favourites/FavouriteUseCases.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * FavouriteUseCases.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * FavouriteUseCases.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.favourites data class FavouriteUseCases( val addFavourite: AddFavourite, val removeFavourite: RemoveFavourite, val removeAllFavourite: RemoveAllFavourite, val getAllFavourite: GetAllFavourite ) ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/favourites/GetAllFavourite.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * GetAllFavourite.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * GetAllFavourite.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.favourites import io.github.yamin8000.owl.datastore.domain.repository.FavouriteRepository import kotlinx.coroutines.flow.Flow class GetAllFavourite( private val repository: FavouriteRepository ) { suspend operator fun invoke(): Flow> { return repository.all() } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/favourites/RemoveAllFavourite.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * RemoveAllFavourite.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * RemoveAllFavourite.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.favourites import io.github.yamin8000.owl.datastore.domain.repository.FavouriteRepository class RemoveAllFavourite( private val repository: FavouriteRepository ) { suspend operator fun invoke() { repository.removeAll() } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/favourites/RemoveFavourite.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * RemoveFavourite.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * RemoveFavourite.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.favourites import io.github.yamin8000.owl.datastore.domain.repository.FavouriteRepository class RemoveFavourite( private val repository: FavouriteRepository ) { suspend operator fun invoke(favourite: String) { repository.remove(favourite) } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/history/AddHistory.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * AddHistory.kt Copyrighted by Yamin Siahmargooei at 2024/8/24 * AddHistory.kt Last modified at 2024/8/24 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.history import io.github.yamin8000.owl.datastore.domain.repository.HistoryRepository class AddHistory( private val repository: HistoryRepository ) { suspend operator fun invoke(history: String) { repository.add(history) } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/history/GetAllHistory.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * AddHistory.kt Copyrighted by Yamin Siahmargooei at 2024/8/24 * AddHistory.kt Last modified at 2024/8/24 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.history import io.github.yamin8000.owl.datastore.domain.repository.HistoryRepository import kotlinx.coroutines.flow.Flow class GetAllHistory( private val repository: HistoryRepository ) { suspend operator fun invoke(): Flow> { return repository.all() } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/history/HistoryUseCases.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * HistoryUseCases.kt Copyrighted by Yamin Siahmargooei at 2024/8/24 * HistoryUseCases.kt Last modified at 2024/8/24 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.history data class HistoryUseCases( val addHistory: AddHistory, val removeHistory: RemoveHistory, val removeAllHistory: RemoveAllHistory, val getAllHistory: GetAllHistory ) ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/history/RemoveAllHistory.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * AddHistory.kt Copyrighted by Yamin Siahmargooei at 2024/8/24 * AddHistory.kt Last modified at 2024/8/24 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.history import io.github.yamin8000.owl.datastore.domain.repository.HistoryRepository class RemoveAllHistory( private val repository: HistoryRepository ) { suspend operator fun invoke() { repository.removeAll() } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/history/RemoveHistory.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * AddHistory.kt Copyrighted by Yamin Siahmargooei at 2024/8/24 * AddHistory.kt Last modified at 2024/8/24 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.history import io.github.yamin8000.owl.datastore.domain.repository.HistoryRepository class RemoveHistory( private val repository: HistoryRepository ) { suspend operator fun invoke(history: String) { repository.remove(history) } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/settings/GetStartingBlank.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * GetTheme.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * GetTheme.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.settings import io.github.yamin8000.owl.datastore.domain.repository.SettingsRepository class GetStartingBlank( private val repository: SettingsRepository ) { suspend operator fun invoke(): Boolean { return repository.getIsStartingBlank() } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/settings/GetTTS.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * GetTheme.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * GetTheme.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.settings import io.github.yamin8000.owl.datastore.domain.repository.SettingsRepository class GetTTS( private val repository: SettingsRepository ) { suspend operator fun invoke(): String { return repository.getTtsLang() ?: "en-US" } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/settings/GetTheme.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * GetTheme.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * GetTheme.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.settings import io.github.yamin8000.owl.datastore.domain.model.ThemeType import io.github.yamin8000.owl.datastore.domain.repository.SettingsRepository class GetTheme( private val repository: SettingsRepository ) { suspend operator fun invoke(): ThemeType { return repository.getTheme() } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/settings/GetVibration.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * GetTheme.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * GetTheme.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.settings import io.github.yamin8000.owl.datastore.domain.repository.SettingsRepository class GetVibration( private val repository: SettingsRepository ) { suspend operator fun invoke(): Boolean { return repository.getIsVibrating() } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/settings/SetStartingBlank.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * SetTheme.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * SetTheme.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.settings import io.github.yamin8000.owl.datastore.domain.repository.SettingsRepository class SetStartingBlank( private val repository: SettingsRepository ) { suspend operator fun invoke(value: Boolean) { repository.setIsStartingBlank(value) } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/settings/SetTTS.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * SetTheme.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * SetTheme.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.settings import io.github.yamin8000.owl.datastore.domain.repository.SettingsRepository class SetTTS( private val repository: SettingsRepository ) { suspend operator fun invoke(value: String) { repository.setTtsLang(value) } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/settings/SetTheme.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * SetTheme.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * SetTheme.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.settings import io.github.yamin8000.owl.datastore.domain.model.ThemeType import io.github.yamin8000.owl.datastore.domain.repository.SettingsRepository class SetTheme( private val repository: SettingsRepository ) { suspend operator fun invoke(value: ThemeType) { repository.setTheme(value) } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/settings/SetVibration.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * SetTheme.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * SetTheme.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.settings import io.github.yamin8000.owl.datastore.domain.repository.SettingsRepository class SetVibration( private val repository: SettingsRepository ) { suspend operator fun invoke(value: Boolean) { repository.setIsVibrating(value) } } ================================================ FILE: datastore/src/main/java/io/github/yamin8000/owl/datastore/domain/usecase/settings/SettingUseCases.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.datastore.main * SettingUseCases.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * SettingUseCases.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.datastore.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.datastore.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.datastore.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.datastore.domain.usecase.settings data class SettingUseCases( val getTTS: GetTTS, val setTTS: SetTTS, val getTheme: GetTheme, val setTheme: SetTheme, val getVibration: GetVibration, val setVibration: SetVibration, val getStartingBlank: GetStartingBlank, val setStartingBlank: SetStartingBlank ) ================================================ FILE: fastlane/metadata/android/en-US/changelogs/34.txt ================================================ - Added OLED theme - Fixed a bug that showed non-english languages during TTS Language selection - Fixed a bug that would crash the app during text selection ================================================ FILE: fastlane/metadata/android/en-US/changelogs/35.txt ================================================ - Fixed some UI bugs - Fixed some performance issues related to Compose - Added borders for some Cards - Migration from Plain Class State Holders to ViewModels WIP - Some refactors and reformats - Migration to Kotlin DSL for build.gradle ================================================ FILE: fastlane/metadata/android/en-US/changelogs/36.txt ================================================ OLED Theme Fixed! ================================================ FILE: fastlane/metadata/android/en-US/changelogs/38.txt ================================================ - Fixed some bugs - Fixed some performance issues related to Compose ================================================ FILE: fastlane/metadata/android/en-US/changelogs/42.txt ================================================ - Startup Crash BUGFIX ================================================ FILE: fastlane/metadata/android/en-US/changelogs/43.txt ================================================ - Navigation bug fix - Android 15 support ================================================ FILE: fastlane/metadata/android/en-US/changelogs/44.txt ================================================ - Added Overlay/Floating Window feature ================================================ FILE: fastlane/metadata/android/en-US/full_description.txt ================================================ freeDictionaryApp is a simple android application for Free Dictionary API. You can just use the search input to search the word. Features
  1. English to English dictionary
  2. Definition of the word
  3. Example of the word usage if available
  4. Synonyms/Antonyms of the work if available
  5. Pronunciation of the word, both IPA text and audio using TTS
  6. Save searched data for offline uses
  7. Search for a random word
================================================ FILE: fastlane/metadata/android/en-US/short_description.txt ================================================ a simple app for Free Dictionary API ================================================ FILE: fastlane/metadata/android/en-US/title.txt ================================================ freeDictionaryApp ================================================ FILE: feature_about/.gitignore ================================================ /build ================================================ FILE: feature_about/build.gradle.kts ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_favourites * build.gradle.kts Copyrighted by Yamin Siahmargooei at 2025/9/8 * build.gradle.kts Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_favourites. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_favourites is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_favourites is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.google.ksp) alias(libs.plugins.jetbrains.kotlin.compose.plugin) alias(libs.plugins.legacy.kapt) alias(libs.plugins.hilt) } android { namespace = "io.github.yamin8000.owl.feature_about" compileSdk = 36 defaultConfig { minSdk = 23 } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } } buildFeatures { compose = true } } composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_compiler") metricsDestination = layout.buildDirectory.dir("compose_compiler") } dependencies { implementation(project(":common")) //hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) implementation(libs.hilt.lifecycle.compose) //retrofit api(libs.retrofit.main) implementation(libs.retrofit.converter.moshi) kapt(libs.retrofit.type.keeper) //moshi implementation(libs.moshi.kotlin) ksp(libs.moshi.kotlin.codegen) //coil implementation(libs.coil.compose) } ================================================ FILE: feature_about/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/data/datasource/remote/GithubAPIs.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * GithubAPIs.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * GithubAPIs.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.data.datasource.remote import io.github.yamin8000.owl.feature_about.data.datasource.remote.dto.ContributorDto import io.github.yamin8000.owl.feature_about.data.datasource.remote.dto.ReleaseDto import io.github.yamin8000.owl.feature_about.data.datasource.remote.dto.RepositoryDto import retrofit2.http.GET import retrofit2.http.Path interface GithubAPIs { @GET("/repos/{owner}/{repo}") suspend fun getRepository( @Path("owner") owner: String, @Path("repo") repo: String ): RepositoryDto @GET("/repos/{owner}/{repo}/contributors") suspend fun repositoryContributors( @Path("owner") owner: String, @Path("repo") repo: String ): List @GET("/repos/{owner}/{repo}/releases") suspend fun releases( @Path("owner") owner: String, @Path("repo") repo: String ): List } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/data/datasource/remote/dto/ContributorDto.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * ContributorDto.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * ContributorDto.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.data.datasource.remote.dto import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import io.github.yamin8000.owl.feature_about.domain.Contributor import io.github.yamin8000.owl.feature_about.domain.ContributorType @JsonClass(generateAdapter = true) data class ContributorDto( val id: Long, val login: String, val type: String, @param:Json(name = "avatar_url") val avatarUrl: String, @param:Json(name = "html_url") val profileUrl: String, val contributions: Long ) { fun domain() = Contributor( id = id, username = login, type = ContributorType.valueOf(type), avatarUrl = avatarUrl, profileUrl = profileUrl, contributions = contributions, ) } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/data/datasource/remote/dto/ReleaseDto.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * ReleaseDto.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * ReleaseDto.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.data.datasource.remote.dto import com.squareup.moshi.JsonClass import io.github.yamin8000.owl.feature_about.domain.Release @JsonClass(generateAdapter = true) data class ReleaseDto( val id: Long, val name: String ) { fun domain() = Release( id = id, name = name ) } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/data/datasource/remote/dto/RepositoryDto.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * RepositoryDto.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * RepositoryDto.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.data.datasource.remote.dto import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import io.github.yamin8000.owl.feature_about.domain.Repository @JsonClass(generateAdapter = true) data class RepositoryDto( val name: String, val description: String, val forks: Int, @param:Json(name = "stargazers_count") val stargazers: Int ) { fun domain() = Repository( name = name, description = description, stars = stargazers, forks = forks ) } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/data/repository/GithubWebRepoRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * GithubWebRepoRepository.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * GithubWebRepoRepository.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.data.repository import io.github.yamin8000.owl.feature_about.data.datasource.remote.GithubAPIs import io.github.yamin8000.owl.feature_about.domain.Contributor import io.github.yamin8000.owl.feature_about.domain.Release import io.github.yamin8000.owl.feature_about.domain.Repository import io.github.yamin8000.owl.feature_about.domain.repository.GithubRepoRepository class GithubWebRepoRepository( private val api: GithubAPIs ) : GithubRepoRepository { override suspend fun getRepository( owner: String, repo: String ): Repository { return api.getRepository(owner, repo).domain() } override suspend fun getRepositoryContributors( owner: String, repo: String ): List { return api.repositoryContributors(owner, repo).map { it.domain() } } override suspend fun getReleases( owner: String, repo: String ): List { return api.releases(owner, repo).map { it.domain() } } } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/di/AboutModule.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * AboutModule.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * AboutModule.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.di import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import io.github.yamin8000.owl.feature_about.data.datasource.remote.GithubAPIs import io.github.yamin8000.owl.feature_about.data.repository.GithubWebRepoRepository import io.github.yamin8000.owl.feature_about.domain.repository.GithubRepoRepository import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.create import javax.inject.Qualifier import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object AboutModule { @Qualifier annotation class AboutModule @Provides @Singleton fun providesOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() .addInterceptor { chain -> val newRequest = chain.request() newRequest.newBuilder() .addHeader("Accept", "application/vnd.github+json") .addHeader("X-GitHub-Api-Version", "2022-11-28") .build() return@addInterceptor chain.proceed(newRequest) }.build() } @AboutModule @Provides @Singleton fun providesRetrofit( client: OkHttpClient ): Retrofit { val baseUrl = "https://api.github.com" return Retrofit.Builder() .baseUrl(baseUrl) .client(client) .addConverterFactory(MoshiConverterFactory.create()) .build() } @Provides @Singleton fun providesGithubAPIs( @AboutModule retrofit: Retrofit ): GithubAPIs { return retrofit.create() } @Provides @Singleton fun providesGithubRepoRepository(api: GithubAPIs): GithubRepoRepository { return GithubWebRepoRepository(api) } } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/domain/Contributor.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * Contributor.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * Contributor.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.domain import kotlin.random.Random data class Contributor( val id: Long, val username: String, val type: ContributorType, val avatarUrl: String, val profileUrl: String, val contributions: Long, ) { companion object { fun mock() = Contributor( id = Random.nextLong(), username = "username", type = ContributorType.entries.toTypedArray().random(), avatarUrl = "https://example.com/avatar.png", profileUrl = "https://example.com/profile", contributions = Random.nextLong(1, 1000) ) } } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/domain/ContributorType.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * ContributorType.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * ContributorType.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.domain enum class ContributorType { User, Bot } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/domain/Release.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * Release.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * Release.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.domain data class Release( val id: Long, val name: String, ) ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/domain/Repository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * Repository.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * Repository.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.domain data class Repository( val name: String, val description: String, val stars: Int, val forks: Int, ) ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/domain/repository/GithubRepoRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * GithubRepoRepository.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * GithubRepoRepository.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.domain.repository import io.github.yamin8000.owl.feature_about.domain.Contributor import io.github.yamin8000.owl.feature_about.domain.Release import io.github.yamin8000.owl.feature_about.domain.Repository interface GithubRepoRepository { suspend fun getRepository( owner: String, repo: String ): Repository suspend fun getRepositoryContributors( owner: String, repo: String ): List suspend fun getReleases( owner: String, repo: String ): List } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/ui/About.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * About.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * About.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.Tab import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewFontScale import androidx.compose.ui.tooling.preview.PreviewScreenSizes import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.components.ScaffoldWithTitle import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.feature_about.ui.tabs.AboutContributors import io.github.yamin8000.owl.feature_about.ui.tabs.AboutInfo import io.github.yamin8000.owl.feature_about.ui.tabs.AboutLicense import io.github.yamin8000.owl.strings.R import kotlinx.collections.immutable.toImmutableList import kotlin.random.Random import kotlin.random.nextInt @PreviewFontScale @PreviewScreenSizes @Composable private fun Preview() { PreviewTheme { var tab by remember { mutableStateOf(AboutTab.entries.random()) } AboutContent( installedVersionName = "1.0.0", state = AboutState( tab = tab, contributors = buildList { repeat(Random.nextInt(2..5)) { add(UiContributor.mock()) } }.toImmutableList() ), onAction = { when (it) { AboutAction.OnRefresh -> {} is AboutAction.OnTabChanged -> tab = it.tab } }, onBackClick = {} ) } } @Composable fun AboutScreen( onBackClick: () -> Unit, installedVersionName: String, modifier: Modifier = Modifier, vm: AboutViewModel = hiltViewModel() ) { val state = vm.state.collectAsStateWithLifecycle().value PullToRefreshBox( isRefreshing = state.isLoading, onRefresh = { vm.onAction(AboutAction.OnRefresh) }, content = { AboutContent( onBackClick = onBackClick, modifier = modifier, installedVersionName = installedVersionName, state = state, onAction = { action -> vm.onAction(action) }, ) } ) } @Composable internal fun AboutContent( onBackClick: () -> Unit, installedVersionName: String, state: AboutState, onAction: (AboutAction) -> Unit, modifier: Modifier = Modifier, ) { ScaffoldWithTitle( modifier = modifier, title = stringResource(R.string.about), onBackClick = onBackClick, content = { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(Sizes.Small, Alignment.Top), horizontalAlignment = Alignment.CenterHorizontally, content = { PrimaryTabRow( selectedTabIndex = state.tab.index, divider = {}, tabs = { AboutTab.entries.forEach { tab -> Tab( modifier = Modifier.fillMaxWidth(), selected = tab == state.tab, onClick = { onAction(AboutAction.OnTabChanged(tab)) }, text = { AppText( text = tab.name, modifier = Modifier.fillMaxWidth(), maxLines = 1, overflow = TextOverflow.Ellipsis ) } ) } } ) when (state.tab) { AboutTab.Info -> { AboutInfo( installedVersionName = installedVersionName, latestVersionName = state.latestVersionName, description = state.repository?.description ?: "" ) } AboutTab.License -> AboutLicense() AboutTab.Contributors -> AboutContributors(contributors = state.contributors) } } ) } ) } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/ui/AboutAction.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * AboutAction.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * AboutAction.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.ui sealed interface AboutAction { data object OnRefresh : AboutAction data class OnTabChanged(val tab: AboutTab) : AboutAction } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/ui/AboutState.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * AboutState.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * AboutState.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.ui import io.github.yamin8000.owl.feature_about.domain.Repository import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf data class AboutState( val tab: AboutTab = AboutTab.Info, val isLoading: Boolean = false, val repository: Repository? = null, val contributors: ImmutableList = persistentListOf(), val latestVersionName: String = "-" ) ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/ui/AboutTab.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * AboutTab.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * AboutTab.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.ui enum class AboutTab( val index: Int ) { Info(0), License(1), Contributors(2) } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/ui/AboutViewModel.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * AboutViewModel.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * AboutViewModel.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import io.github.yamin8000.owl.common.util.log import io.github.yamin8000.owl.common.util.randomColor import io.github.yamin8000.owl.feature_about.domain.repository.GithubRepoRepository import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @HiltViewModel class AboutViewModel @Inject constructor( private val repository: GithubRepoRepository ) : ViewModel() { private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> _state.update { it.copy(isLoading = false) } log(throwable.stackTraceToString()) } private val scope = CoroutineScope( SupervisorJob() + viewModelScope.coroutineContext + exceptionHandler ) private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + exceptionHandler) private var _state = MutableStateFlow(AboutState()) val state = _state.asStateFlow() init { scope.launch { load() } } private suspend fun load() { _state.update { it.copy(isLoading = true) } val repo = repository.getRepository("yamin8000", "freeDictionaryApp") val releases = repository.getReleases("yamin8000", "freeDictionaryApp") val contributors = repository.getRepositoryContributors("yamin8000", "freeDictionaryApp") _state.update { it.copy( isLoading = false, repository = repo, latestVersionName = releases.firstOrNull()?.name ?: "-", contributors = contributors.toImmutableList().map { contributor -> UiContributor( contributor = contributor, borderColor = randomColor() ) }.toImmutableList() ) } } fun onAction(action: AboutAction) { when (action) { AboutAction.OnRefresh -> scope.launch { load() } is AboutAction.OnTabChanged -> _state.update { it.copy(tab = action.tab) } } } } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/ui/UiContributor.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * UiContributor.kt Copyrighted by Yamin Siahmargooei at 2025/11/17 * UiContributor.kt Last modified at 2025/11/17 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.ui import androidx.compose.ui.graphics.Color import io.github.yamin8000.owl.common.util.randomColor import io.github.yamin8000.owl.feature_about.domain.Contributor data class UiContributor( val contributor: Contributor, val borderColor: Color ) { companion object { fun mock() = UiContributor( contributor = Contributor.mock(), borderColor = randomColor() ) } } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/ui/components/ContributionsBar.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * ContributionsBar.kt Copyrighted by Yamin Siahmargooei at 2025/11/17 * ContributionsBar.kt Last modified at 2025/11/17 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.ui.components import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.feature_about.ui.UiContributor import kotlin.random.Random import kotlin.random.nextInt @Preview(showBackground = true) @Composable private fun Preview() { PreviewTheme { Box( modifier = Modifier.fillMaxWidth(), content = { ContributionsBar( modifier = Modifier.padding(Sizes.Medium), contributors = buildList { repeat(Random.nextInt(2..5)) { add(UiContributor.mock()) } } ) } ) } } @Composable internal fun ContributionsBar( contributors: List, modifier: Modifier = Modifier, thickness: Dp = Sizes.Small ) { val sorted = remember(contributors) { contributors.sortedByDescending { it.contributor.contributions } } val colors = remember(contributors) { sorted.map { it.borderColor } } BoxWithConstraints( modifier = modifier, content = { Canvas( modifier = Modifier, onDraw = { val total = sorted.sumOf { it.contributor.contributions }.toFloat() var portions = 0f drawLine( color = colors.first(), strokeWidth = thickness.toPx(), start = Offset(0f, 0f), end = Offset(0f, 0f), cap = StrokeCap.Round ) sorted.forEachIndexed { index, value -> val portion = value.contributor.contributions / total drawLine( color = colors[index], strokeWidth = thickness.toPx(), start = Offset(portions * constraints.maxWidth, 0f), end = Offset((portion + portions) * constraints.maxWidth, 0f), cap = StrokeCap.Butt ) portions += portion } } ) } ) } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/ui/components/ContributorItem.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * ContributorItem.kt Copyrighted by Yamin Siahmargooei at 2025/11/17 * ContributorItem.kt Last modified at 2025/11/17 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.ui.components import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import coil.compose.AsyncImage import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.common.util.randomColor import io.github.yamin8000.owl.feature_about.R @Preview(showBackground = true) @Composable private fun Preview() { PreviewTheme { ContributorItem( profileUrl = "https://github.com/yamin8000", avatarUrl = "https://avatars.githubusercontent.com/u/9919?s=200&v=4", username = "yamin8000", borderColor = randomColor() ) } } @Composable internal fun ContributorItem( profileUrl: String, avatarUrl: String, username: String, borderColor: Color, modifier: Modifier = Modifier ) { val uriHandler = LocalUriHandler.current Column( modifier = modifier.clickable( onClick = { uriHandler.openUri(profileUrl) } ), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(Sizes.Small, Alignment.CenterVertically), content = { AsyncImage( model = avatarUrl, contentDescription = username, placeholder = painterResource(R.drawable.image_48px), fallback = painterResource(R.drawable.broken_image_48px), modifier = Modifier .size(Sizes.xxLarge * 1.5f) .clip(CircleShape) .border( width = Sizes.Small, color = borderColor, shape = CircleShape ) ) AppText( text = username, maxLines = 1, overflow = TextOverflow.Ellipsis ) } ) } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/ui/tabs/AboutContributors.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * AboutContributors.kt Copyrighted by Yamin Siahmargooei at 2025/11/17 * AboutContributors.kt Last modified at 2025/11/17 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.ui.tabs import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.feature_about.ui.UiContributor import io.github.yamin8000.owl.feature_about.ui.components.ContributionsBar import io.github.yamin8000.owl.feature_about.ui.components.ContributorItem import io.github.yamin8000.owl.strings.R import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlin.random.Random import kotlin.random.nextInt @Preview(showBackground = true) @Composable private fun Preview() { PreviewTheme { AboutContributors( contributors = buildList { repeat(Random.nextInt(2..5)) { add(UiContributor.mock()) } }.toImmutableList() ) } } @Composable internal fun AboutContributors( contributors: ImmutableList, modifier: Modifier = Modifier ) { LazyVerticalGrid( modifier = modifier, contentPadding = PaddingValues(Sizes.Small), columns = GridCells.Fixed(3), verticalArrangement = Arrangement.spacedBy(Sizes.Large, Alignment.CenterVertically), horizontalArrangement = Arrangement.spacedBy(Sizes.Medium, Alignment.CenterHorizontally), content = { item( span = { GridItemSpan(maxLineSpan) }, content = { AppText( text = stringResource(R.string.contributions_graph), maxLines = 1, overflow = TextOverflow.Ellipsis ) } ) item( span = { GridItemSpan(maxLineSpan) }, content = { if (contributors.isNotEmpty()) { ContributionsBar(contributors = contributors) } } ) items( items = contributors, key = { it.contributor.id }, itemContent = { ContributorItem( avatarUrl = it.contributor.avatarUrl, username = it.contributor.username, borderColor = it.borderColor, profileUrl = it.contributor.profileUrl ) } ) } ) } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/ui/tabs/AboutInfo.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * AboutInfo.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * AboutInfo.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.ui.tabs import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.components.Ripple import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.strings.R @Preview(showBackground = true) @Composable private fun Preview() { PreviewTheme { AboutInfo( installedVersionName = "1.0.0", latestVersionName = "1.2.0", description = "This is a sample description of the app." ) } } @Composable internal fun AboutInfo( installedVersionName: String, latestVersionName: String, description: String, modifier: Modifier = Modifier ) { Column( modifier = modifier.verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(Sizes.Medium, Alignment.CenterVertically), content = { AppText( maxLines = 2, text = stringResource( R.string.version_name, installedVersionName ) ) AppText( text = stringResource(R.string.latest_version, latestVersionName), maxLines = 2 ) AppText( text = description, modifier = Modifier.fillMaxWidth() ) AppText( text = stringResource(id = R.string.about_app), modifier = Modifier.fillMaxWidth() ) val uriHandler = LocalUriHandler.current val freeDictionaryUri = stringResource(R.string.free_dictionary_link) Ripple( onClick = { uriHandler.openUri(freeDictionaryUri) }, content = { Text( text = freeDictionaryUri, textDecoration = TextDecoration.Underline ) } ) } ) } ================================================ FILE: feature_about/src/main/kotlin/io/github/yamin8000/owl/feature_about/ui/tabs/AboutLicense.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_about.main * AboutLicense.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * AboutLicense.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_about.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_about.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_about.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_about.ui.tabs import android.content.res.Configuration import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.components.Ripple import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.strings.R import io.github.yamin8000.owl.feature_about.R.drawable as R_drawable @Preview(showBackground = true) @Composable private fun Preview() { PreviewTheme { AboutLicense() } } @Composable internal fun AboutLicense( modifier: Modifier = Modifier ) { Column( modifier = modifier.verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(Sizes.Medium, Alignment.CenterVertically), content = { val uriHandler = LocalUriHandler.current val sourceUri = stringResource(R.string.github_source) val licenseUri = stringResource(R.string.license_link) Ripple( onClick = { uriHandler.openUri(licenseUri) }, content = { val config = LocalConfiguration.current val fill = remember(config) { if (config.orientation == Configuration.ORIENTATION_PORTRAIT) 1f else .5f } Image( painter = painterResource(id = R_drawable.ic_gplv3), contentDescription = stringResource(id = R.string.gplv3_image_description), contentScale = ContentScale.FillWidth, colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant), modifier = Modifier.fillMaxWidth(fill) ) } ) AppText( text = stringResource(id = R.string.license_header), modifier = Modifier.fillMaxWidth() ) Ripple( onClick = { uriHandler.openUri(sourceUri) }, content = { Text( text = sourceUri, textDecoration = TextDecoration.Underline ) } ) } ) } ================================================ FILE: feature_about/src/main/res/drawable/broken_image_48px.xml ================================================ ================================================ FILE: feature_about/src/main/res/drawable/ic_gplv3.xml ================================================ ================================================ FILE: feature_about/src/main/res/drawable/image_48px.xml ================================================ ================================================ FILE: feature_favourites/.gitignore ================================================ /build ================================================ FILE: feature_favourites/build.gradle.kts ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_favourites * build.gradle.kts Copyrighted by Yamin Siahmargooei at 2025/9/8 * build.gradle.kts Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_favourites. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_favourites is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_favourites is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.google.ksp) alias(libs.plugins.jetbrains.kotlin.compose.plugin) alias(libs.plugins.hilt) } android { namespace = "io.github.yamin8000.owl.feature_favourites" compileSdk = 36 defaultConfig { minSdk = 23 } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } } buildFeatures { compose = true } } composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_compiler") metricsDestination = layout.buildDirectory.dir("compose_compiler") } dependencies { implementation(project(":common")) //hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) implementation(libs.hilt.lifecycle.compose) } ================================================ FILE: feature_favourites/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature_favourites/src/main/java/io/github/yamin8000/owl/feature_favourites/FavouriteEvent.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_favourites.main * FavouriteEvent.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * FavouriteEvent.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_favourites.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_favourites.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_favourites.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_favourites sealed interface FavouriteEvent { data object RemoveAll : FavouriteEvent data class Remove(val favourite: String) : FavouriteEvent } ================================================ FILE: feature_favourites/src/main/java/io/github/yamin8000/owl/feature_favourites/FavouriteState.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_favourites.main * FavouriteState.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * FavouriteState.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_favourites.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_favourites.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_favourites.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_favourites data class FavouriteState( val favourites: List = listOf() ) ================================================ FILE: feature_favourites/src/main/java/io/github/yamin8000/owl/feature_favourites/Favourites.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_favourites.main * Favourites.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * Favourites.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_favourites.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_favourites.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_favourites.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_favourites import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.yamin8000.owl.common.ui.components.crud.CrudContent import io.github.yamin8000.owl.strings.R import kotlinx.collections.immutable.toImmutableList @Composable fun FavouritesScreen( onFavouritesItemClick: (String) -> Unit, onBackClick: () -> Unit, modifier: Modifier = Modifier, vm: FavouritesViewModel = hiltViewModel(), ) { val state = vm.state.collectAsStateWithLifecycle().value CrudContent( modifier = modifier, title = stringResource(R.string.favourites), items = state.favourites.toImmutableList(), onBackClick = onBackClick, onRemoveAll = { vm.onEvent(FavouriteEvent.RemoveAll) }, onRemoveSingle = { vm.onEvent(FavouriteEvent.Remove(it)) }, onItemClick = onFavouritesItemClick ) } ================================================ FILE: feature_favourites/src/main/java/io/github/yamin8000/owl/feature_favourites/FavouritesViewModel.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_favourites.main * FavouritesViewModel.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * FavouritesViewModel.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_favourites.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_favourites.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_favourites.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_favourites import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import io.github.yamin8000.owl.datastore.domain.usecase.favourites.FavouriteUseCases import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class FavouritesViewModel @Inject constructor( private val useCases: FavouriteUseCases ) : ViewModel() { private val scope = viewModelScope private var _state = MutableStateFlow(FavouriteState()) val state = _state.asStateFlow() init { scope.launch { useCases.getAllFavourite().collect { favourites -> _state.update { it.copy(favourites = favourites) } } } } fun onEvent(event: FavouriteEvent) { when (event) { is FavouriteEvent.Remove -> scope.launch { useCases.removeFavourite(event.favourite) } FavouriteEvent.RemoveAll -> scope.launch { useCases.removeAllFavourite() } } } } ================================================ FILE: feature_history/.gitignore ================================================ /build ================================================ FILE: feature_history/build.gradle.kts ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_history * build.gradle.kts Copyrighted by Yamin Siahmargooei at 2025/9/8 * build.gradle.kts Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_history. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_history is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_history is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.google.ksp) alias(libs.plugins.jetbrains.kotlin.compose.plugin) alias(libs.plugins.hilt) } android { namespace = "io.github.yamin8000.owl.feature_history" compileSdk = 36 defaultConfig { minSdk = 23 } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } } buildFeatures { compose = true } } composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_compiler") metricsDestination = layout.buildDirectory.dir("compose_compiler") } dependencies { //core implementation(project(":common")) //hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) implementation(libs.hilt.lifecycle.compose) } ================================================ FILE: feature_history/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature_history/src/main/java/io/github/yamin8000/karlancer/feature_history/ui/History.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_history.main * History.kt Copyrighted by Yamin Siahmargooei at 2024/8/24 * History.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_history.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_history.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_history.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.karlancer.feature_history.ui import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.yamin8000.owl.common.ui.components.crud.CrudContent import io.github.yamin8000.owl.strings.R import kotlinx.collections.immutable.toImmutableList @Composable fun HistoryScreen( onHistoryItemClick: (String) -> Unit, onBackClick: () -> Unit, modifier: Modifier = Modifier, vm: HistoryViewModel = hiltViewModel(), ) { val state = vm.state.collectAsStateWithLifecycle().value CrudContent( modifier = modifier, title = stringResource(R.string.search_history), items = state.history.toImmutableList(), onBackClick = onBackClick, onRemoveAll = { vm.onEvent(HistoryEvent.RemoveAll) }, onRemoveSingle = { vm.onEvent(HistoryEvent.RemoveHistory(it)) }, onItemClick = onHistoryItemClick ) } ================================================ FILE: feature_history/src/main/java/io/github/yamin8000/karlancer/feature_history/ui/HistoryEvent.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_history.main * HistoryEvent.kt Copyrighted by Yamin Siahmargooei at 2024/8/24 * HistoryEvent.kt Last modified at 2024/8/24 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_history.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_history.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_history.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.karlancer.feature_history.ui sealed interface HistoryEvent { data class RemoveHistory(val history: String) : HistoryEvent data object RemoveAll : HistoryEvent } ================================================ FILE: feature_history/src/main/java/io/github/yamin8000/karlancer/feature_history/ui/HistoryState.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_history.main * HistoryState.kt Copyrighted by Yamin Siahmargooei at 2024/8/24 * HistoryState.kt Last modified at 2024/8/24 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_history.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_history.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_history.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.karlancer.feature_history.ui data class HistoryState( val history: List = listOf() ) ================================================ FILE: feature_history/src/main/java/io/github/yamin8000/karlancer/feature_history/ui/HistoryViewModel.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_history.main * HistoryViewModel.kt Copyrighted by Yamin Siahmargooei at 2024/8/24 * HistoryViewModel.kt Last modified at 2024/8/24 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_history.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_history.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_history.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.karlancer.feature_history.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import io.github.yamin8000.owl.datastore.domain.usecase.history.HistoryUseCases import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class HistoryViewModel @Inject constructor( private val useCases: HistoryUseCases ) : ViewModel() { private val scope = viewModelScope private var _state = MutableStateFlow(HistoryState()) val state = _state.asStateFlow() init { scope.launch { useCases.getAllHistory().collect { history -> _state.update { it.copy(history = history) } } } } fun onEvent(event: HistoryEvent) { when (event) { HistoryEvent.RemoveAll -> scope.launch { useCases.removeAllHistory() } is HistoryEvent.RemoveHistory -> scope.launch { useCases.removeHistory(event.history) } } } } ================================================ FILE: feature_home/.gitignore ================================================ /build ================================================ FILE: feature_home/build.gradle.kts ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home * build.gradle.kts Copyrighted by Yamin Siahmargooei at 2025/9/8 * build.gradle.kts Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.google.ksp) alias(libs.plugins.jetbrains.kotlin.compose.plugin) alias(libs.plugins.hilt) } android { namespace = "io.github.yamin8000.owl.feature_home" compileSdk = 36 defaultConfig { minSdk = 23 } compileOptions { isCoreLibraryDesugaringEnabled = true sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } } buildFeatures { compose = true } } composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_compiler") metricsDestination = layout.buildDirectory.dir("compose_compiler") } dependencies { //core coreLibraryDesugaring(libs.desugar.jdk.libs) implementation(project(":common")) implementation(project(":search")) //hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) implementation(libs.hilt.lifecycle.compose) } ================================================ FILE: feature_home/src/debug/java/io/github/yamin8000/owl/feature_home/ui/Previews.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * Previews.kt Copyrighted by Yamin Siahmargooei at 2025/11/16 * Previews.kt Last modified at 2025/11/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.search.domain.model.Definition import io.github.yamin8000.owl.search.domain.model.Entry import io.github.yamin8000.owl.search.domain.model.Meaning import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import net.datafaker.Faker import kotlin.random.Random import kotlin.random.nextInt @Preview(showBackground = true) @Composable private fun HomeLoadSuccess() { val faker = Faker() val word = faker.backToTheFuture().character() PreviewTheme { HomeContent( state = HomeState( searchResult = Entry( word = word, phonetics = persistentListOf(), meanings = buildList { repeat(Random.nextInt(2..3)) { add( Meaning( partOfSpeech = "noun", definitions = buildList { repeat(Random.nextInt(1..4)) { add( Definition( definition = faker.backToTheFuture().quote(), example = faker.backToTheFuture().quote(), synonyms = buildList { repeat(Random.nextInt(0..3)) { add(faker.backToTheFuture().character()) } }, antonyms = persistentListOf(), ) ) } }.toImmutableList(), synonyms = persistentListOf(), antonyms = persistentListOf() ) ) } }.toImmutableList() ), isOnline = true, isSearching = false, searchSuggestions = buildList { add(word) repeat(Random.nextInt(1..3)) { add(faker.backToTheFuture().character()) } }.toImmutableList(), word = word, phonetic = "/ɪɡˈzæmpəl/" ), term = word, isWordSelectedFromKeyboardSuggestions = Random.nextBoolean(), onAction = {}, onNavigateToAbout = {}, onNavigateToSettings = {}, onNavigateToFavourites = {}, onNavigateToHistory = {} ) } } ================================================ FILE: feature_home/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/data/repository/TermSuggesterRepositoryImpl.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * TermSuggesterRepositoryImpl.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * TermSuggesterRepositoryImpl.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.data.repository import android.app.Application import android.content.res.Resources.NotFoundException import io.github.yamin8000.owl.feature_home.R import io.github.yamin8000.owl.search.data.datasource.local.dao.DAOs import io.github.yamin8000.owl.feature_home.domain.repository.TermSuggesterRepository import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlin.math.abs import kotlin.math.ceil import kotlin.math.roundToInt class TermSuggesterRepositoryImpl( private val dao: DAOs.TermDao, private val app: Application ) : TermSuggesterRepository { private val defaultNGramSize = 3 private val notWordsRegex = Regex("\\W+") override suspend fun suggestTerms(searchTerm: String): List { items = items.plus(getOldSearchData()) val term = searchTerm.lowercase().replace(notWordsRegex, "") val nGramSize = nGramSizeProvider(term) if (items.contains(term)) return listOf(term) val searchTermGrams = term.windowed(nGramSize) val suggestions = buildSet { searchTermGrams.forEach { gram -> addAll(items.filter { word -> word.contains(gram) }) } } return sortSuggestions(suggestions, term).filter { it.isNotBlank() } } private val scope = CoroutineScope(Dispatchers.IO) private var items = setOf() init { items = getBasic2000Data().toSet() scope.launch { items = items.plus(getOldSearchData()) } } private fun getBasic2000Data() = try { app.resources.openRawResource(R.raw.basic2000) .bufferedReader() .use { it.readText() } .split(',') .map { it.replace(notWordsRegex, "") } } catch (e: NotFoundException) { listOf() } private fun sortSuggestions( suggestions: Set, searchTerm: String ): List { val nGramSize = nGramSizeProvider(searchTerm) return if (suggestions.isNotEmpty() && suggestions.size > 1) { val searchTermGrams = searchTerm.windowed(nGramSize) val rankedSuggestions = getRankedSuggestions(suggestions, nGramSize, searchTermGrams) rankedSuggestions.asSequence() .sortedBy { abs(it.second.length - searchTerm.length) } .sortedByDescending { it.second.startsWith(searchTerm.take(nGramSize)) || it.second.endsWith(searchTerm.takeLast(nGramSize)) } .sortedByDescending { it.first } .map { it.second } .toList() } else suggestions.toList() } private fun getRankedSuggestions( suggestions: Set, nGramSize: Int, searchTermGrams: List ) = buildList { suggestions.forEach { suggestion -> val rank = suggestion.windowed(nGramSize) .intersect(searchTermGrams.toSet()) .size add(rank to suggestion) } } private fun nGramSizeProvider( searchTerm: String ) = if (searchTerm.length > defaultNGramSize) { val size = ceil(searchTerm.length.toFloat() / defaultNGramSize).roundToInt() if (size < defaultNGramSize) defaultNGramSize else size } else defaultNGramSize private suspend fun getOldSearchData() = dao.all().map { it.word } } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/di/HomeModule.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * HomeModule.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * HomeModule.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.di import android.app.Application import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import io.github.yamin8000.owl.feature_home.data.repository.TermSuggesterRepositoryImpl import io.github.yamin8000.owl.feature_home.domain.repository.TermSuggesterRepository import io.github.yamin8000.owl.search.data.datasource.local.AppDatabase import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object HomeModule { @Provides @Singleton fun providesTermSuggesterRepository( db: AppDatabase, app: Application ): TermSuggesterRepository = TermSuggesterRepositoryImpl(db.termDao(), app) } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/di/HomeUseCases.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * HomeUseCases.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * HomeUseCases.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.di import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import io.github.yamin8000.owl.feature_home.domain.usecase.GetRandomWord import io.github.yamin8000.owl.search.domain.repository.local.TermRepository import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object HomeUseCases { @Provides @Singleton fun providesRandomWordUseCase( repository: TermRepository ): GetRandomWord = GetRandomWord(repository) } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/di/HomeViewModelFactory.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * HomeViewModelFactory.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * HomeViewModelFactory.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.di import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import io.github.yamin8000.owl.feature_home.ui.HomeViewModel @AssistedFactory interface HomeViewModelFactory { fun create( @Assisted("intent") intentSearch: String?, @Assisted("navigation") navigationSearch: String?, ): HomeViewModel } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/domain/repository/TermSuggesterRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * TermSuggesterRepository.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * TermSuggesterRepository.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.domain.repository interface TermSuggesterRepository { suspend fun suggestTerms(searchTerm: String): List } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/domain/usecase/GetRandomWord.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * GetRandomWordUseCase.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * GetRandomWordUseCase.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.domain.usecase import io.github.yamin8000.owl.search.domain.repository.local.TermRepository class GetRandomWord( private val repository: TermRepository ) { suspend operator fun invoke(): String { return repository.all().shuffled().firstOrNull() ?: "free" } } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/Home.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * Home.kt Copyrighted by Yamin Siahmargooei at 2024/8/17 * Home.kt Last modified at 2024/7/20 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.repeatOnLifecycle import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.components.EmptyList import io.github.yamin8000.owl.common.ui.components.MySnackbar import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.common.util.LocalTTS import io.github.yamin8000.owl.feature_home.ui.components.MainTopBar import io.github.yamin8000.owl.feature_home.ui.components.SearchList import io.github.yamin8000.owl.feature_home.ui.components.bottom_app_bar.MainBottomBar import io.github.yamin8000.owl.feature_home.ui.components.bottom_app_bar.SuggestionsChips import io.github.yamin8000.owl.feature_home.ui.util.ShareUtils.handleShareIntent import io.github.yamin8000.owl.feature_home.ui.util.Utils.ObserverEvent import io.github.yamin8000.owl.feature_home.ui.util.Utils.getErrorText import io.github.yamin8000.owl.search.domain.model.Entry import io.github.yamin8000.owl.strings.R import kotlinx.collections.immutable.persistentListOf import kotlin.random.Random @Preview(showBackground = true) @Composable private fun Preview() { PreviewTheme { HomeContent( state = HomeState( searchResult = Entry.mock(), isOnline = Random.nextBoolean(), isSearching = Random.nextBoolean(), searchSuggestions = persistentListOf("apple", "banana", "orange"), word = "example", phonetic = "/ɪɡˈzæmpəl/" ), term = "example", isWordSelectedFromKeyboardSuggestions = Random.nextBoolean(), onAction = {}, onNavigateToAbout = {}, onNavigateToSettings = {}, onNavigateToFavourites = {}, onNavigateToHistory = {} ) } } @Composable fun HomeScreen( onNavigateToAbout: () -> Unit, onNavigateToSettings: () -> Unit, onNavigateToFavourites: () -> Unit, onNavigateToHistory: () -> Unit, modifier: Modifier = Modifier, vm: HomeViewModel = hiltViewModel() ) { val state = vm.state.collectAsStateWithLifecycle().value val owner = LocalLifecycleOwner.current LaunchedEffect(Unit) { owner.repeatOnLifecycle(Lifecycle.State.STARTED) { vm.onAction(HomeAction.UpdateTTS) } } val context = LocalContext.current ObserverEvent(vm.shareChannelFlow) { data -> if (data != null) { handleShareIntent(context, data) } } ObserverEvent(vm.errorChannelFlow) { error -> state.snackbarHostState.showSnackbar(getErrorText(context, error)) } CompositionLocalProvider(LocalTTS provides vm.tts) { HomeContent( state = state, term = vm.searchTerm.collectAsState().value, isWordSelectedFromKeyboardSuggestions = vm.isWordSelectedFromKeyboardSuggestions.value, onAction = { action -> vm.onAction(action) }, onNavigateToAbout = onNavigateToAbout, onNavigateToSettings = onNavigateToSettings, onNavigateToFavourites = onNavigateToFavourites, onNavigateToHistory = onNavigateToHistory, modifier = modifier ) } } @Composable internal fun HomeContent( state: HomeState, term: String, isWordSelectedFromKeyboardSuggestions: Boolean, onAction: (HomeAction) -> Unit, onNavigateToAbout: () -> Unit, onNavigateToSettings: () -> Unit, onNavigateToFavourites: () -> Unit, onNavigateToHistory: () -> Unit, modifier: Modifier = Modifier ) { val listState = rememberScrollState() if (listState.isScrollInProgress && state.isVibrating) { LocalHapticFeedback.current.performHapticFeedback(HapticFeedbackType.TextHandleMove) } Scaffold( modifier = modifier, snackbarHost = { SnackbarHost(state.snackbarHostState) { data -> MySnackbar { AppText( text = data.visuals.message, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center ) } } }, topBar = { MainTopBar( onNavigateToAbout = onNavigateToAbout, onNavigateToSettings = onNavigateToSettings, onNavigateToFavourites = onNavigateToFavourites, onNavigateToHistory = onNavigateToHistory, onRandomClick = { onAction(HomeAction.RandomWord) } ) }, bottomBar = { val focusManager = LocalFocusManager.current val keyboardManager = LocalSoftwareKeyboardController.current MainBottomBar( searchTerm = term, suggestionsChips = { SuggestionsChips( searchTerm = term, suggestions = state.searchSuggestions, onSuggestionClick = { onAction(HomeAction.NewSearch(it)) }, ) }, isSearching = state.isSearching, onSearch = { onAction(HomeAction.NewSearch()) keyboardManager?.hide() focusManager.clearFocus() }, onCancel = { onAction(HomeAction.CancelSearch) keyboardManager?.hide() focusManager.clearFocus() }, onSearchTermChange = { onAction(HomeAction.OnTermChanged(it)) if (isWordSelectedFromKeyboardSuggestions) { onAction(HomeAction.NewSearch(it)) } } ) }, content = { contentPadding -> if (state.searchResult != null) { SearchList( modifier = Modifier.padding(contentPadding), meanings = state.searchResult.meanings, onAddToFavourite = { onAction(HomeAction.OnAddToFavourite(state.word)) }, onWordChipClick = { onAction(HomeAction.NewSearch(it)) }, onShareWord = { onAction(HomeAction.OnShareData) }, isOnline = state.isOnline, word = state.word, phonetic = state.phonetic ) } else { Column( modifier = modifier.padding(Sizes.Large), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy( Sizes.Large, Alignment.CenterVertically ), content = { AppText(stringResource(R.string.search_hint)) EmptyList() } ) } } ) } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/HomeAction.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * HomeEvent.kt Copyrighted by Yamin Siahmargooei at 2024/8/18 * HomeEvent.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui sealed interface HomeAction { data object RandomWord : HomeAction data class NewSearch(val searchTerm: String? = null) : HomeAction data class OnTermChanged(val term: String) : HomeAction data object OnShareData : HomeAction data object CancelSearch : HomeAction data object OnCheckInternet : HomeAction data class OnAddToFavourite(val word: String) : HomeAction data object UpdateTTS : HomeAction } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/HomeState.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * HomeState.kt Copyrighted by Yamin Siahmargooei at 2024/8/17 * HomeState.kt Last modified at 2024/8/17 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui import androidx.compose.material3.SnackbarHostState import io.github.yamin8000.owl.feature_home.ui.util.HomeSnackbarType import io.github.yamin8000.owl.search.domain.model.Entry import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf data class HomeState( val isOnline: Boolean = false, val isSearching: Boolean = false, val searchSuggestions: ImmutableList = persistentListOf(), val snackbarHostState: SnackbarHostState = SnackbarHostState(), val error: HomeSnackbarType? = null, val searchResult: Entry? = null, val phonetic: String = "", val word: String = "", val isVibrating: Boolean = false ) ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/HomeViewModel.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * HomeViewModel.kt Copyrighted by Yamin Siahmargooei at 2024/8/17 * HomeViewModel.kt Last modified at 2024/6/2 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.assisted.Assisted import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import io.github.yamin8000.owl.common.util.TTS import io.github.yamin8000.owl.datastore.domain.usecase.favourites.FavouriteUseCases import io.github.yamin8000.owl.datastore.domain.usecase.history.HistoryUseCases import io.github.yamin8000.owl.datastore.domain.usecase.settings.SettingUseCases import io.github.yamin8000.owl.feature_home.di.HomeViewModelFactory import io.github.yamin8000.owl.feature_home.domain.repository.TermSuggesterRepository import io.github.yamin8000.owl.feature_home.domain.usecase.GetRandomWord import io.github.yamin8000.owl.feature_home.ui.util.HomeSnackbarType import io.github.yamin8000.owl.search.domain.model.Entry import io.github.yamin8000.owl.search.domain.usecase.SearchFreeDictionary import io.github.yamin8000.owl.search.domain.usecase.WordCacheUseCases import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import retrofit2.HttpException import java.net.UnknownHostException import kotlin.coroutines.cancellation.CancellationException @HiltViewModel(assistedFactory = HomeViewModelFactory::class) class HomeViewModel @AssistedInject constructor( private val savedState: SavedStateHandle, private val searchFreeDictionaryUseCase: SearchFreeDictionary, private val termSuggesterRepository: TermSuggesterRepository, private val settingsUseCases: SettingUseCases, private val historyUseCases: HistoryUseCases, private val favouriteUseCases: FavouriteUseCases, private val cacheUseCases: WordCacheUseCases, private val randomWordUseCase: GetRandomWord, val tts: TTS, @Assisted("intent") private val intentSearch: String?, @Assisted("navigation") private val navigationSearch: String? ) : ViewModel() { private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> _state.update { it.copy(isSearching = false) } println(throwable.stackTraceToString()) viewModelScope.launch { when (throwable) { is HttpException -> when (throwable.code()) { 401 -> errorChannel.send(HomeSnackbarType.ApiAuthorizationError) 404 -> errorChannel.send(HomeSnackbarType.NotFound) 429 -> errorChannel.send(HomeSnackbarType.ApiThrottled) else -> errorChannel.send(HomeSnackbarType.Unknown) } is CancellationException -> errorChannel.send(HomeSnackbarType.Cancelled) is UnknownHostException -> errorChannel.send(HomeSnackbarType.NoInternet) else -> errorChannel.send(HomeSnackbarType.Unknown) } } } private val scope = CoroutineScope(viewModelScope.coroutineContext + exceptionHandler) val searchTerm = savedState.getStateFlow("Search", intentSearch ?: navigationSearch ?: "") private var errorChannel = Channel() val errorChannelFlow = errorChannel.receiveAsFlow() private var shareChannel = Channel() val shareChannelFlow = shareChannel.receiveAsFlow() private var _state = MutableStateFlow(HomeState()) val state = _state.asStateFlow() private var searchJob: Job? = null val isWordSelectedFromKeyboardSuggestions: State get() = derivedStateOf { searchTerm.value.length > 1 && searchTerm.value.last() == ' ' && !searchTerm.value.all { it == ' ' } } private val internetCheckDelay = 5000L private val dnsServers = listOf( "8.8.8.8", "8.8.4.4", "1.1.1.1", "1.0.0.1", "185.51.200.2", "178.22.122.100", "10.202.10.202", "10.202.10.102" ) init { scope.launch { _state.update { it.copy(isVibrating = settingsUseCases.getVibration()) } if (!settingsUseCases.getStartingBlank() && searchTerm.value.isBlank()) { savedState["Search"] = "free" } if (searchTerm.value.isNotBlank()) { searchForDefinition(searchTerm.value) } while (true) { onAction(HomeAction.OnCheckInternet) delay(internetCheckDelay) } } } fun onAction(action: HomeAction) { when (action) { HomeAction.RandomWord -> searchForRandomWord() HomeAction.OnShareData -> scope.launch { shareChannel.send(state.value.searchResult) } HomeAction.CancelSearch -> cancel() HomeAction.UpdateTTS -> scope.launch { tts.createEngine(settingsUseCases.getTTS()) } is HomeAction.NewSearch -> { val term = action.searchTerm ?: searchTerm.value savedState["Search"] = term searchJob = searchForDefinition(term) } is HomeAction.OnTermChanged -> { savedState["Search"] = action.term scope.launch { _state.update { it.copy( searchSuggestions = termSuggesterRepository.suggestTerms(action.term) .toImmutableList() ) } } } HomeAction.OnCheckInternet -> { scope.launch { _state.update { stateUpdate -> stateUpdate.copy(isOnline = dnsServers.any { dnsAccessible(it) }) } } } is HomeAction.OnAddToFavourite -> { scope.launch { favouriteUseCases.addFavourite(action.word) errorChannel.send(HomeSnackbarType.AddedToFavourite) } } } } private suspend fun dnsAccessible( dnsServer: String ) = withContext(Dispatchers.IO + exceptionHandler) { Runtime.getRuntime().exec("/system/bin/ping -c 1 $dnsServer").waitFor() } == 0 private fun searchForRandomWord() = scope.launch { searchForDefinition(randomWordUseCase()) } private fun searchForDefinition( searchTerm: String ) = scope.launch { if (searchTerm.isNotBlank()) { historyUseCases.addHistory(searchTerm) _state.update { it.copy(isSearching = true) } val cachedWord = cacheUseCases.getCachedWord(searchTerm) if (cachedWord == null) { val newWord = searchForDefinitionUsingApi(searchTerm) if (newWord != null) { cacheUseCases.cacheWord(newWord) cacheUseCases.cacheWordData(newWord) } } else loadCachedWord(cachedWord) _state.update { it.copy(isSearching = false) } } else errorChannel.send(HomeSnackbarType.TermIsEmpty) } private fun loadCachedWord(cachedWord: Entry) { val phonetic = cachedWord.phonetics.firstOrNull { it.text != null }?.text ?: "" _state.update { it.copy( searchResult = cachedWord, word = cachedWord.word, phonetic = phonetic, searchSuggestions = persistentListOf() ) } } private suspend fun searchForDefinitionUsingApi( searchTerm: String ): Entry? { val entry = searchFreeDictionaryUseCase(searchTerm).firstOrNull() val phonetic = entry?.phonetics?.firstOrNull { it.text != null }?.text ?: "" _state.update { it.copy( searchResult = entry, word = entry?.word ?: "", phonetic = phonetic, searchSuggestions = persistentListOf() ) } return entry } private fun cancel() { _state.update { it.copy(isSearching = false) } searchJob?.cancel() } } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/components/HomeTopBar.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * HomeTopBar.kt Copyrighted by Yamin Siahmargooei at 2024/8/18 * HomeTopBar.kt Last modified at 2024/8/17 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Casino import androidx.compose.material.icons.twotone.Favorite import androidx.compose.material.icons.twotone.History import androidx.compose.material.icons.twotone.Info import androidx.compose.material.icons.twotone.Settings import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import io.github.yamin8000.owl.common.ui.components.ClickableIcon import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.strings.R @OptIn(ExperimentalMaterial3Api::class) @Composable internal fun MainTopBar( onNavigateToAbout: () -> Unit, onNavigateToSettings: () -> Unit, onNavigateToFavourites: () -> Unit, onNavigateToHistory: () -> Unit, onRandomClick: () -> Unit, modifier: Modifier = Modifier ) { Surface( modifier = modifier, shadowElevation = Sizes.Medium, content = { TopAppBar( title = { Text( text = stringResource(R.string.app_name), style = MaterialTheme.typography.titleSmall, maxLines = 1, overflow = TextOverflow.Ellipsis ) }, actions = { ClickableIcon( imageVector = Icons.TwoTone.History, contentDescription = stringResource(R.string.search_history), onClick = onNavigateToHistory ) ClickableIcon( imageVector = Icons.TwoTone.Favorite, contentDescription = stringResource(R.string.favourites), onClick = onNavigateToFavourites ) ClickableIcon( imageVector = Icons.TwoTone.Casino, contentDescription = stringResource(R.string.random_word), onClick = onRandomClick ) ClickableIcon( imageVector = Icons.TwoTone.Settings, contentDescription = stringResource(R.string.settings), onClick = onNavigateToSettings ) ClickableIcon( imageVector = Icons.TwoTone.Info, contentDescription = stringResource(R.string.about_app), onClick = onNavigateToAbout ) } ) } ) } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/components/SearchList.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * SearchList.kt Copyrighted by Yamin Siahmargooei at 2025/1/16 * SearchList.kt Last modified at 2025/1/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.datasource.LoremIpsum import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.search.domain.model.Meaning import io.github.yamin8000.owl.search.ui.components.MeaningCard import io.github.yamin8000.owl.search.ui.components.WordCard import io.github.yamin8000.owl.strings.R import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import java.util.UUID import kotlin.random.Random @MyPreview @Composable private fun Preview() { PreviewTheme { SearchList( isOnline = Random.nextBoolean(), word = LoremIpsum(1).values.first(), phonetic = LoremIpsum(1).values.first(), onAddToFavourite = {}, onShareWord = {}, onWordChipClick = {}, meanings = Meaning.mockList().toImmutableList(), ) } } @Composable internal fun SearchList( isOnline: Boolean, word: String, phonetic: String, onAddToFavourite: () -> Unit, onShareWord: () -> Unit, onWordChipClick: (String) -> Unit, meanings: ImmutableList, modifier: Modifier = Modifier ) { LazyColumn( modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(Sizes.Medium, Alignment.CenterVertically), contentPadding = PaddingValues(Sizes.Medium), content = { item( key = isOnline, content = { AnimatedVisibility( visible = !isOnline, enter = slideInVertically() + fadeIn(), exit = slideOutVertically() + fadeOut(), content = { AppText( modifier = Modifier.padding(Sizes.Medium), color = MaterialTheme.colorScheme.error, text = stringResource(R.string.general_net_error) ) } ) } ) if (word.isNotBlank()) { item( key = word + phonetic, content = { WordCard( word = word, pronunciation = phonetic, onShareWord = onShareWord, onAddToFavourite = onAddToFavourite ) } ) } items( items = meanings, key = { item -> "meaning-${item.id ?: UUID.randomUUID()}" }, itemContent = { meaning -> MeaningCard( modifier = Modifier.animateItem(), word = word, meaning = meaning, onWordChipClick = onWordChipClick ) } ) } ) } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/components/bottom_app_bar/BottomAppBarDuringSearch.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * BottomAppBarDuringSearch.kt Copyrighted by Yamin Siahmargooei at 2025/1/16 * BottomAppBarDuringSearch.kt Last modified at 2025/1/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui.components.bottom_app_bar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Cancel import androidx.compose.material3.BottomAppBar import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.strings.R @MyPreview @Composable private fun Preview() { PreviewTheme { BottomAppBarDuringSearch( onCancel = {} ) } } @Composable internal fun BottomAppBarDuringSearch( onCancel: () -> Unit, modifier: Modifier = Modifier ) { BottomAppBar( modifier = modifier, actions = {}, floatingActionButton = { FloatingActionButton( onClick = onCancel, content = { Icon( imageVector = Icons.TwoTone.Cancel, contentDescription = stringResource(R.string.cancel) ) } ) } ) } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/components/bottom_app_bar/HomeBottomBar.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * HomeBottomBar.kt Copyrighted by Yamin Siahmargooei at 2025/1/16 * HomeBottomBar.kt Last modified at 2024/12/5 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui.components.bottom_app_bar import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import kotlinx.collections.immutable.persistentListOf @MyPreview @Composable private fun Preview() { PreviewTheme { MainBottomBar( searchTerm = "test", suggestionsChips = { SuggestionsChips( searchTerm = "test", suggestions = persistentListOf(), onSuggestionClick = {}, ) }, isSearching = false, onSearchTermChange = {}, onSearch = {}, onCancel = {} ) } } @Composable internal fun MainBottomBar( searchTerm: String, isSearching: Boolean, onSearchTermChange: (String) -> Unit, onSearch: () -> Unit, onCancel: () -> Unit, modifier: Modifier = Modifier, suggestionsChips: @Composable ColumnScope.() -> Unit ) { Column( modifier = modifier, content = { suggestionsChips() if (isSearching) { RainbowWavyLinearProgress() } AnimatedContent( targetState = isSearching, label = "", content = { target -> if (!target) { NormalBottomAppBar( onSearch = onSearch, onSearchTermChange = onSearchTermChange, searchTerm = searchTerm ) } else BottomAppBarDuringSearch(onCancel = onCancel) } ) } ) } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/components/bottom_app_bar/NormalBottomAppBar.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * NormalBottomAppBar.kt Copyrighted by Yamin Siahmargooei at 2025/1/16 * NormalBottomAppBar.kt Last modified at 2025/1/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui.components.bottom_app_bar import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CutCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Clear import androidx.compose.material.icons.twotone.Search import androidx.compose.material3.BottomAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import io.github.yamin8000.owl.common.ui.components.ClickableIcon import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.strings.R @MyPreview @Composable private fun Preview() { PreviewTheme { NormalBottomAppBar( onSearch = {}, onSearchTermChange = {}, searchTerm = "free" ) } } @Composable internal fun NormalBottomAppBar( searchTerm: String, onSearchTermChange: (String) -> Unit, onSearch: () -> Unit, modifier: Modifier = Modifier ) { BottomAppBar( modifier = modifier, content = { TextField( singleLine = true, shape = CutCornerShape(topEnd = Sizes.Medium, topStart = Sizes.Medium), modifier = Modifier .fillMaxWidth() .padding(horizontal = Sizes.Large), label = { AppText( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.search) ) }, placeholder = { AppText( modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.search_hint), style = MaterialTheme.typography.labelMedium ) }, leadingIcon = { ClickableIcon( enabled = searchTerm.isNotBlank(), imageVector = Icons.TwoTone.Clear, contentDescription = stringResource(R.string.clear), onClick = { onSearchTermChange("") } ) }, trailingIcon = { ClickableIcon( enabled = searchTerm.isNotBlank(), imageVector = Icons.TwoTone.Search, contentDescription = stringResource(R.string.search), onClick = onSearch ) }, value = searchTerm, onValueChange = onSearchTermChange, keyboardActions = KeyboardActions(onSearch = { onSearch() }), keyboardOptions = KeyboardOptions( imeAction = ImeAction.Search, keyboardType = KeyboardType.Text, capitalization = KeyboardCapitalization.Words ) ) } ) } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/components/bottom_app_bar/RainbowWavyLinearProgress.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * RainbowLinearProgress.kt Copyrighted by Yamin Siahmargooei at 2025/1/16 * RainbowLinearProgress.kt Last modified at 2025/1/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui.components.bottom_app_bar import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.LinearWavyProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.util.randomColor import kotlinx.coroutines.delay import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds @MyPreview @Composable private fun Preview() { PreviewTheme { RainbowWavyLinearProgress() } } @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable internal fun RainbowWavyLinearProgress( modifier: Modifier = Modifier, animationDuration: Duration = 500.milliseconds ) { val colors = buildList { repeat((50..100).random()) { add(randomColor()) } } var color by remember { mutableStateOf(colors.random()) } LaunchedEffect(Unit) { while (true) { color = colors.random() delay(animationDuration) } } val animatedColor by animateColorAsState( targetValue = color, animationSpec = MaterialTheme.motionScheme.fastEffectsSpec() ) LinearWavyProgressIndicator( modifier = modifier.fillMaxWidth(), color = animatedColor, trackColor = animatedColor ) } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/components/bottom_app_bar/SuggestionsChips.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * SuggestionsChips.kt Copyrighted by Yamin Siahmargooei at 2025/1/16 * SuggestionsChips.kt Last modified at 2024/12/5 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui.components.bottom_app_bar import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.material3.ElevatedSuggestionChip import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import io.github.yamin8000.owl.common.ui.components.HighlightText import io.github.yamin8000.owl.common.ui.theme.DefaultCutShape import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.common.ui.theme.defaultGradientBorder import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @MyPreview @Composable private fun Preview() { PreviewTheme { SuggestionsChips( searchTerm = "ing", suggestions = persistentListOf("eating", "drinking", "drink"), onSuggestionClick = {} ) } } @Composable internal fun SuggestionsChips( searchTerm: String, suggestions: ImmutableList, onSuggestionClick: (String) -> Unit, modifier: Modifier = Modifier, ) { if (suggestions.isNotEmpty()) { LazyRow( modifier = modifier, contentPadding = PaddingValues(Sizes.Small), horizontalArrangement = Arrangement.spacedBy( Sizes.Medium, Alignment.CenterHorizontally ), content = { items( items = suggestions, itemContent = { ElevatedSuggestionChip( shape = DefaultCutShape, border = defaultGradientBorder(1000), onClick = { onSuggestionClick(it) }, label = { HighlightText( fullText = it, highlightedText = searchTerm ) } ) } ) } ) } } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/util/HomeSnackbarType.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * HomeSnackbarType.kt Copyrighted by Yamin Siahmargooei at 2025/1/16 * HomeSnackbarType.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui.util sealed interface HomeSnackbarType { data object TermIsEmpty : HomeSnackbarType data object SearchFailed : HomeSnackbarType data object NoInternet : HomeSnackbarType data object ApiAuthorizationError : HomeSnackbarType data object NotFound : HomeSnackbarType data object ApiThrottled : HomeSnackbarType data object Cancelled : HomeSnackbarType data object Unknown : HomeSnackbarType data object AddedToFavourite : HomeSnackbarType } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/util/ShareUtils.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * ShareUtils.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * ShareUtils.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui.util import android.content.Context import android.content.Intent import io.github.yamin8000.owl.search.domain.model.Entry import io.github.yamin8000.owl.strings.R object ShareUtils { internal fun handleShareIntent( context: Context, entry: Entry ) { val text = createShareText(context, entry) val sendIntent = Intent().apply { action = Intent.ACTION_SEND putExtra(Intent.EXTRA_TEXT, text) type = "text/plain" } val shareIntent = Intent.createChooser( sendIntent, "${context.getString(R.string.app_name)}, ${entry.word}" ) context.startActivity(shareIntent) } private fun createShareText( context: Context, entry: Entry ) = buildString { appendLine("Word: ${entry.word}") appendLine() append("Pronunciation(IPA): ") appendLine(entry.phonetics.firstOrNull { it.text != null }?.text ?: "-") appendLine() entry.meanings.forEachIndexed { index, (partOfSpeech, definitions, _, _) -> appendLine("${index + 1})") appendLine("Type: $partOfSpeech") definitions.take(5).forEach { (definition, example, synonyms, antonyms) -> appendLine("Definition: $definition") if (example != null) { appendLine("Example: $example") } if (synonyms.isNotEmpty()) { appendLine("Synonyms: ${synonyms.take(5).joinToString()}") } if (antonyms.isNotEmpty()) { appendLine("Antonyms: ${antonyms.take(5).joinToString()}") } appendLine() } appendLine() } trim() appendLine(context.getString(R.string.this_text_generated_using_owl)) appendLine(context.getString(R.string.github_source)) appendLine(context.getString(R.string.this_text_extracted_from_free_dictionary)) append(context.getString(R.string.free_dictionary_link)) } } ================================================ FILE: feature_home/src/main/java/io/github/yamin8000/owl/feature_home/ui/util/Utils.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * Utils.kt Copyrighted by Yamin Siahmargooei at 2025/1/16 * Utils.kt Last modified at 2025/1/16 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_home.ui.util import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle import io.github.yamin8000.owl.strings.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext object Utils { @Composable internal fun ObserverEvent( flow: Flow, onEvent: suspend (T) -> Unit ) { val lifeCycleOwner = LocalLifecycleOwner.current LaunchedEffect(flow, lifeCycleOwner.lifecycle) { lifeCycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { withContext(Dispatchers.Main.immediate) { flow.collect(onEvent) } } } } internal fun getErrorText( context: Context, error: HomeSnackbarType? ) = when (error) { HomeSnackbarType.SearchFailed -> context.getString(R.string.general_net_error) HomeSnackbarType.TermIsEmpty -> context.getString(R.string.no_search_term_entered) HomeSnackbarType.NoInternet -> context.getString(R.string.general_net_error) HomeSnackbarType.ApiAuthorizationError -> context.getString(R.string.api_authorization_error) HomeSnackbarType.ApiThrottled -> context.getString(R.string.api_throttled) HomeSnackbarType.Cancelled -> context.getString(R.string.cancelled) HomeSnackbarType.NotFound -> context.getString(R.string.definition_not_found) HomeSnackbarType.Unknown -> context.getString(R.string.untracked_error) HomeSnackbarType.AddedToFavourite -> context.getString(R.string.added_to_favourites) null -> "" } } ================================================ FILE: feature_home/src/main/res/raw/basic2000.txt ================================================ a,able,about,account,acid,across,act,addition,adjustment,advertisement,agreement,after,again,against,air,all,almost,am,among,amount,amusement,an,and,angry,angle,animal,answer,ant,any,apparatus,apple,approval,arch,argument,arm,army,art,as,association,at,attack,attempt,attention,attitude,attraction,authority,automatic,awake,alcohol,Algebra,aluminum,ammonia,anesthetic,April,Arithmetic,asbestos,August,autobus,automobile,absence,absorption,acceleration,acceptance,accessory,accident,active,address,adjacent,adventure,advice,age,agent,agency,ago,allowance,along,also,alternative,always,ambition,amplitude,anchor,ankle,appendage,application,approximation,arbitration,arbitrary,arc,area,arrangement,ash,asset,assistant,average,awkward,axis,aftereffect,aftertaste,afterthought,aircushion,airman,airmen,airplane,airtight,another,anybody,anyhow,anyone,anything,anywhere,away,actor,acting,baby,back,bad,bag,balance,ball,band,base,basin,basket,bath,be,beautiful,because,bed,bee,been,before,behavior,belief,bell,bent,berry,best,better,between,bird,birth,bit,bite,bitter,black,blade,blood,blow,blue,board,boat,body,bone,book,boot,boiling,bottle,box,boy,brain,brake,branch,brass,bread,breath,brick,bridge,bright,broken,brother,brown,brush,bucket,building,bulb,button,burn,burst,business,but,butter,by,ballet,Bang,bank,bar,beef,beer,Biology,bomb,balcony,bale,bankrupt,bark,barrel,beak,beaker,beard,beat,behind,belt,bet,bill,birefringence,blame,blanket,both,bottom,brave,break,breakfast,breast,broker,bubble,bud,budget,buoyancy,bunch,burial,busy,backbone,backwoods,become,became,bedroom,beeswax,birthday,birthright,blackberry,blackbird,blackboard,bloodvessel,bluebell,bookkeeper,brushwood,buttercup,basing,based,builder,burner,burned,burning,cake,camera,canvas,card,care,carriage,cart,cat,cause,certain,chain,chalk,chance,change,cheap,cheese,chemical,chest,chief,chin,church,circle,class,clean,clear,clock,cloth,cloud,coal,coat,cold,collar,color,comb,come,comfort,committee,community,common,company,comparison,competition,complete,complex,computer,condition,connection,conscious,control,cook,copper,copy,cord,cork,cotton,cough,country,cover,cow,crack,credit,crime,cruel,crush,cry,culture,cup,custom,current,curtain,curve,cushion,cut,cafe,calendar,catarrh,cent,centi-,champagne,chauffeur,chemist,Chemistry,check,chocolate,chorus,cigarette,circus,citron,club,coffee,cocktail,cognac,College,colony,calculation,call,came,capacity,capital,carpet,cartilage,case,cast,cave,cavity,cell,ceremony,certificate,chair,character,charge,child,chimney,china,choice,circulation,circuit,circumference,civilization,clay,claim,claw,cleavage,clever,client,climber,clip,code,coil,collision,collection,column,combination,combine,communications,complaint,component,compound,concept,concrete,conductor,congruent,conservation,consignment,constant,consumer,continuous,contour,convenient,conversion,cool,corner,correlation,corrosion,cost,court,creeper,crop,cross,cunning,cusp,customs,cardboard,carefree,caretaker,clockwork,commonsense,copyright,cupboard,carter,clothier,clothing,cooker,cooked,cooking,crying,damage,danger,dark,daughter,day,dead,dear,death,debt,decision,deep,degree,delicate,dependent,design,desire,destruction,detail,development,did,different,digestion,direction,dirty,discovery,discussion,disease,disgust,distance,distribution,division,do,does,dog,done,door,down,doubt,drain,drawer,dress,drink,driving,drop,dry,dust,dance,December,deci-,degree,dollar,Dominion,dynamite,damping,date,debit,deck,decrease,defect,deficiency,deflation,degenerate,delivery,demand,denominator,department,desert,density,deposit,determining,dew,diameter,difference,difficulty,drift,dike,dilution,dinner,dip,direct,disappearance,discharge,discount,disgrace,dislike,dissipation,disturbance,ditch,dive,divisor,divorce,doll,domesticating,dreadful,dream,duct,dull,duty,daylight,downfall,daily,dancer,dancing,to,designer,dressing,up,driver,dropped,dropper,duster,ear,early,earth,east,edge,education,effect,egg,elastic,electric,end,engine,enough,environment,equal,error,even,event,ever,every,example,exchange,existence,expansion,experience,expert,eye,eight,electricity,eleven,Embassy,Empire,encyclopedia,engineer,euro,each,easy,economy,efficiency,effort,either,elimination,employer,employment,empty,enemy,envelope,environment,envy,equation,erosion,eruption,evaporation,evening,exact,excitement,experiment,exercise,explanation,explosion,export,expression,extinction,eyebrow,eyelash,ear-ring,earthwork,evergreen,everybody,everyday,everyone,everything,everywhere,eyeball,face,fact,fall,false,family,far,farm,farther,fat,father,fear,feather,feeble,feeling,female,fertile,fiction,field,fight,finger,fire,first,fish,fixed,flag,flame,flat,flight,floor,flower,fly,fold,food,foolish,foot,for,force,fork,form,forward,fowl,frame,free,frequent,friend,from,front,fruit,full,further,future,February,fifteen,fifth,fifty,five,four,fourteen,fourth,forty,Friday,factor,failure,fair,famous,fan,fastening,fault,ferment,fertilizing,fever,fiber,figure,fin,financial,flash,flask,flesh,flood,flour,focus,forecast,forehead,foreign,forgiveness,fraction,fracture,fresh,friction,flint,flood,flow,foliation,frost,frozen,fume,funnel,funny,fur,furnace,furniture,fusion,fatherland,fingerprint,firearm,fire-engine,firefly,fireman,fireplace,firework,first-rate,football,footlights,footman,footnote,footprint,footstep,farmer,fisher,fisherman,folder,fired,firing,gave,garden,general,get,girl,give,glass,glove,go,goat,goes,gold,gone,good,got,gat,government,grain,grass,great,green,grey/gray,grip,group,growth,guide,gun,gas,geography,geology,Geometry,gram,glycerin,gate,generation,germ,germinating,gill,glacier,gland,god,grand,grateful,grating,gravel,grease,grief,grocery,groove,gross,ground,guard,guarantee,guess,gum,gasworks,goldfish,goodlooking,good-morning,goodnight,gunboat,gun-carriage,gunmetal,gunpowder,gardener,had,hair,hammer,hand,hanging,happy,harbor,hard,harmony,has,hat,hate,hath,have,he,head,healthy,hearing,heart,heat,helicopter,help,her,here,heredity,high,him,history,hole,hollow,hook,hope,horn,horse,hospital,hour,house,how,humor,half,hiss,hotel,hundred,hyena,hygiene,hysteria,habit,handkerchief,handle,heavy,hedge,hill,hinge,hire,hold,holiday,home,honest,honey,hoof,host,human,hunt,hurry,hurt,husband,handbook,handwriting,headdress,headland,headstone,headway,hereafter,herewith,highlands,highway,himself,horseplay,horsepower,hourglass,houseboat,housekeeper,however,hanger,heater,heated,heating,I,ice,idea,if,ill,important,impulse,in,increase,industry,ink,insect,instrument,insurance,interest,invention,iron,is,island,it,its,Imperial,inch,inferno,influenza,international,igneous,image,imagination,import,impurity,incentive,inclusion,index,individual,inflation,infinity,inheritance,innocent,institution,insulator,integer,integration,intelligent,intercept,internet,interpretation,intersection,intrusion,investigation,investment,inverse,invitation,inasmuch,income,indoors,inland,inlet,input,inside,instep,into,itself,inner,jelly,jewel,join,journey,judge,jump,January,jazz,July,June,jam,jaw,jealous,jerk,joint,jug,juice,jury,justice,jeweler,joiner,keep,kept,kettle,key,kick,kind,kiss,knee,knife,knot,knowledge,kilo-,King,kennel,kidney,kitchen,knock,keeper,land,language,last,late,laugh,law,lead,leaf,learning,least,leather,leg,left,less,let,letter,level,library,lift,light,like,limit,line,linen,lip,liquid,list,little,living,lock,long,loose,loss,loud,love,low,latitude,lava,liter,liqueur,longitude,lace,lag,lake,lame,lamp,large,latitude,lawyer,layer,lazy,lecture,legal,length,lens,lesson,lever,lever,liability,license,lid,life,lime,limestone,link,liver,load,local,load,loan,locus,look,longitude,luck,lump,lunch,lung,landmark,landslip,lighthouse,looking-glass,laughing at,learner,likely,locker,locking up,machine,made,man,manager,make,male,map,mark,market,married,match,material,mass,may,me,meal,measure,meat,medical,meeting,memory,metal,middle,might,military,milk,mind,mine,minute,mist,mixed,money,monkey,month,moon,more,morning,most,mother,motion,mountain,mouth,move,much,muscle,music,my,macaroni,madam,magnetic,malaria,mania,March,Mathematics,May,meter,meow,micro-,microscope,mile,milli-,million,minute,Monday,Museum,magic,magnitude,manner,many,marble,margin,marriage,mast,mattress,mature,mean,meaning,medicine,medium,melt,member,mess,message,metabolism,mill,mineral,mixture,model,modern,modest,momentum,monopoly,mood,moral,moustache,mud,multiple,multiplication,murder,manhole,maybe,myself,marked,miner,nail,name,narrow,nation,natural,near,necessary,neck,need,needle,nerve,net,never,new,news,night,no,noise,normal,north,nose,not,note,now,number,nut,neutron,nickel,nicotine,nine,November,nasty,nature,navy,neat,neglect,neighbor,nest,next,nice,node,nostril,nuclear,nucleus,numerator,nurse,network,newspaper,nobody,nothing,nowhere,nearer,noted,observation,of,off,offer,office,oil,old,on,one,only,open,operation,opinion,opposite,or,orange,order,organization,ornament,other,our,out,oven,over,owner,October,olive,once,omelet,one,opera,opium,orchestra,organism,obedient,officer,orchestra,ore,organ,origin,outcrop,outlier,overlap,oval,own,oxidation,offspring,oncoming,oneself,onlooker,onto,outburst,outcome,outcry,outdoor,outgoing,outhouse,outlaw,outlet,outline,outlook,output,outside,outskirts,outstretched,overacting,overall,overbalancing,overbearing,overcoat,overcome,overdo,overdressed,overfull,overhanging,overhead,overland,overleaf,overload,overseas,overseer,overshoe,overstatement,overtake,overtaxed,overtime,overturned,overuse,overvalued,overweight,overworking,outer,page,pain,paint,paper,parallel,parcel,part,past,paste,payment,peace,pen,pencil,person,physical,picture,pig,pin,pipe,place,plane,plant,plate,play,please,pleasure,plough/plow,pocket,point,poison,polish,political,poor,porter,position,possible,pot,potato,powder,power,present,price,print,prison,private,probable,process,produce,profit,property,prose,protest,public,pull,pump,punishment,purpose,push,put,pajamas,paraffin,paradise,park,passport,patent,pence,penny,penguin,petroleum,phonograph,Physics,Physiology,piano,platinum,police,post,potash,pound,President,Prince,Princess,program,propaganda,Psychology,Purr,pyramid,packing,pad,pair,pan,paragraph,parent,particle,partner,party,passage,path,patience,pedal,pendulum,pension,people,perfect,petal,piston,plain,plan,plaster,plug,poetry,pollen,pool,population,porcelain,practice,praise,prayer,pressure,prick,priest,prime,probability,product,progress,projectile,projection,promise,proof,proud,pulley,pupil,purchase,pure,pincushion,plaything,policeman,postman,postmark,postmaster,post-office,painter,painting,parting,playing,played,pleased with,pointer,pointing at,potter,printer,prisoner,producer,quality,question,quick,quiet,quite,Quack,quarter,Queen,quinine,quantity,quotient,rail,rain,range,rat,rate,ray,reaction,red,reading,ready,reason,receipt,record,regret,regular,relation,religion,representative,request,respect,responsible,rest,reward,rhythm,rice,right,ring,river,road,rod,roll,roof,room,root,rough,round,rub,rule,run,radio,radium,referendum,restaurant,rheumatism,Royal,rum,race,radiation,ratio,reagent,real,receiver,reciprocal,rectangle,recurring,reference,reflux,reinforcement,relative,remark,remedy,rent,repair,reproduction,repulsion,resistance,residue,resolution,result,retail,revenge,reversible,rich,rigidity,rise,rival,rock,rot,rotation,rude,rust,reasonable,runaway,raining,reader,reading,roller,ruler,rubber,sad,said,safe,sail,salt,same,sand,saw,say,scale,school,science,scissors,screw,sea,seat,second,secret,secretary,see,seed,seen,seem,selection,self,send,sense,sent,separate,serious,servant,sex,shade,shake,shame,sharp,she,sheep,shelf,ship,shirt,shock,shoe,short,shut,side,sign,silk,silver,simple,sister,size,skin,skirt,sky,sleep,slip,slope,slow,small,smash,smell,smile,smoke,smooth,snake,sneeze,snow,so,soap,society,sock,soft,solid,some,son,song,sort,sound,soup,south,space,spade,special,sponge,spoon,spring,square,stage,standard,stamp,star,start,statement,station,steam,steel,stem,step,stick,sticky,stiff,still,stitch,stocking,stomach,stone,stop,store,story,straight,strange,street,strong,structure,substance,such,sudden,sugar,suggestion,summer,sun,support,surprise,sweet,swim,system,salad,sardine,Saturday,second,September,serum,seven,sir,six,sixteen,sport,Sunday,sac,sale,sample,satisfaction,saturated,saucer,saving,scale,scarp,schist,scratch,screen,seal,search,security,secretion,section,sedimentary,selfish,sensitivity,sentence,sepal,service,set,shadow,shale,share,shave,shear,sheet,shell,shore,shoulder,show,sight,sill,similarity,since,skull,slate,sleeve,slide,social,soil,soldier,solution,solvent,sorry,spark,specialization,specimen,speculation,spirit,spit,splash,spot,stable,stain,stair,stalk,stamen,statistics,steady,stimulus,storm,strain,straw,stream,strength,stress,strike,string,study,subject,substitution,subtraction,success,successive,sucker,sum,supply,surface,surgeon,suspension,suspicious,swelling,swing,switch,sympathetic,seaman,secondhand,shorthand,sideboard,sidewalk,somebody,someday,somehow,someone,something,sometime,somewhat,somewhere,suchlike,sunburn,sunlight,sunshade,sweetheart,sailor,shocking,shocked,snowing,steamer,stopper,stopping up,stretcher,table,tail,take,talk,tall,taste,tax,teaching,technology,tendency,test,than,that,the,their,them,then,theory,there,these,they,thick,thin,thing,this,those,though,thought,thread,throat,through,thumb,thunder,ticket,tight,tired,till,time,tin,to,toe,together,tomorrow,tongue,took,tooth,top,touch,town,trade,train,transport,tray,tree,trick,trousers,true,trouble,turn,twist,tapioca,taxi,tea,telegram,telephone,television,ten,terrace,theater,thermometer,third,thirteen,thirty,thousand,three,Thursday,toast,tobacco,torpedo,Tuesday,turbine,twenty-one,twelve,twenty,twice,two,tailor,tame,tap,tear,tent,term,texture,thickness,thief,thimble,thorax,threat,thrust,tide,tie,tissue,tongs,too,total,towel,tower,traffic,tragedy,transmission,transparent,trap,travel,treatment,triangle,truck,tube,tune,tunnel,twin,typist,today,tonight,tradesman,talking of,teacher,touching up,trader,trainer,training,troubling,troubled,turning over,umbrella,under,unit,up,us,use,university,ugly,unconformity,understanding,universe,unknown,underclothing,undercooked,undergo,undergrowth,undermined,undersigned,undersized,understatement,undertake,undervalued,undo,upkeep,uplift,upon,upright,uproot,uptake,used to,value,verse,very,vessel,view,violent,voice,vanilla,violin,visa,vitamin,vodka,volt,valency,valley,valve,vapor,variable,vascular,vegetable,velocity,vestigial,victim,victory,volume,vortex,vote,viewpoint,walk,wall,waiting,war,warm,was,wash,waste,watch,water,wave,wax,way,we,weather,week,weight,well,went,were,west,wet,what,wheel,when,where,which,while,whip,whistle,white,who,whom,whose,why,wide,will,wind,window,wine,wing,winter,wire,wise,with,woman,wood,wool,word,work,worm,would,wound,writing,wrong,Wednesday,whisky,weak,wedge,welcome,whether,wholesale,widow,wife,wild,world,wreck,wrist,waterfall,weekend,well-being,well-off,whatever,whenever,whereas,whereby,wherever,whichever,whitewash,whoever,windpipe,within,without,woodwork,workhouse,waiter,worker,working,on,out,up,writer,waiting,wasted,your,yours,year,yellow,yes,yesterday,you,young,zebra,zinc,zoology,yawn,x-ray,yearbook,yourself,zookeeper ================================================ FILE: feature_overlay/.gitignore ================================================ /build ================================================ FILE: feature_overlay/build.gradle.kts ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_overlay * build.gradle.kts Copyrighted by Yamin Siahmargooei at 2025/9/8 * build.gradle.kts Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_overlay. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_overlay is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_overlay is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.google.ksp) alias(libs.plugins.jetbrains.kotlin.compose.plugin) alias(libs.plugins.hilt) } android { namespace = "io.github.yamin8000.owl.feature_overlay" compileSdk = 36 defaultConfig { minSdk = 23 } compileOptions { isCoreLibraryDesugaringEnabled = true sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } } buildFeatures { compose = true } } composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_compiler") metricsDestination = layout.buildDirectory.dir("compose_compiler") } dependencies { //core coreLibraryDesugaring(libs.desugar.jdk.libs) implementation(project(":common")) implementation(project(":search")) //hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) implementation(libs.hilt.lifecycle.compose) } ================================================ FILE: feature_overlay/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature_overlay/src/main/java/io/github/yamin8000/owl/feature_overlay/di/OverlayViewModelFactory.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_overlay.main * OverlayViewModelFactory.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * OverlayViewModelFactory.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_overlay.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_overlay.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_overlay.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_overlay.di import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import io.github.yamin8000.owl.feature_overlay.ui.OverlayWindowViewModel @AssistedFactory interface OverlayViewModelFactory { fun create( @Assisted("intent") intentSearch: String?, ): OverlayWindowViewModel } ================================================ FILE: feature_overlay/src/main/java/io/github/yamin8000/owl/feature_overlay/ui/OverlayWindow.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_overlay.main * OverlayWindow.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * OverlayWindow.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_overlay.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_overlay.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_overlay.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_overlay.ui import android.content.res.Configuration import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.window.DialogProperties import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.yamin8000.owl.common.ui.theme.DefaultCutShape import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.common.ui.theme.defaultGradientBorder import io.github.yamin8000.owl.feature_overlay.ui.components.ButtonsRow import io.github.yamin8000.owl.feature_overlay.ui.components.SearchList @OptIn(ExperimentalMaterial3Api::class) @Composable fun OverlayScreen( onDismissRequest: () -> Unit, navigateToApp: (String) -> Unit, modifier: Modifier = Modifier, vm: OverlayWindowViewModel = hiltViewModel() ) { val state = vm.state.collectAsStateWithLifecycle().value val configuration = LocalConfiguration.current val isPortrait = remember(configuration) { configuration.orientation == Configuration.ORIENTATION_PORTRAIT } val localWindow = LocalWindowInfo.current val density = LocalDensity.current val screenHeight = remember(localWindow, density) { with(density) { localWindow.containerSize.height.toDp() } } val buttonsOffset = Sizes.xxLarge val windowHeight = remember(isPortrait, screenHeight) { if (isPortrait) screenHeight / 2 else screenHeight - buttonsOffset } BasicAlertDialog( modifier = modifier, onDismissRequest = onDismissRequest, properties = DialogProperties( dismissOnClickOutside = false, usePlatformDefaultWidth = false ), content = { Box( modifier = Modifier.height(windowHeight + buttonsOffset), content = { Surface( modifier = Modifier .height(windowHeight) .padding(horizontal = Sizes.Large), shape = DefaultCutShape, border = defaultGradientBorder(), content = { SearchList( modifier = Modifier.padding(Sizes.Large), isSearching = state.isSearching, word = state.word, phonetic = state.phonetic, meanings = state.meanings ) } ) ButtonsRow( modifier = Modifier .padding(top = buttonsOffset) .align(Alignment.BottomCenter), onDismissRequest = onDismissRequest, navigateToApp = { navigateToApp(state.word) } ) } ) } ) } ================================================ FILE: feature_overlay/src/main/java/io/github/yamin8000/owl/feature_overlay/ui/OverlayWindowState.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_floating.main * OverlayWindowState.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * OverlayWindowState.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_floating.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_floating.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_floating.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_overlay.ui import io.github.yamin8000.owl.search.domain.model.Meaning import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf data class OverlayWindowState( val isSearching: Boolean = false, val searchTerm: String = "", val meanings: ImmutableList = persistentListOf(), val word: String = "", val phonetic: String = "" ) ================================================ FILE: feature_overlay/src/main/java/io/github/yamin8000/owl/feature_overlay/ui/OverlayWindowViewModel.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_overlay.main * OverlayWindowViewModel.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * OverlayWindowViewModel.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_overlay.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_overlay.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_overlay.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_overlay.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.assisted.Assisted import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import io.github.yamin8000.owl.feature_overlay.di.OverlayViewModelFactory import io.github.yamin8000.owl.search.domain.model.Phonetic import io.github.yamin8000.owl.search.domain.usecase.SearchFreeDictionary import io.github.yamin8000.owl.search.domain.usecase.WordCacheUseCases import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @HiltViewModel(assistedFactory = OverlayViewModelFactory::class) class OverlayWindowViewModel @AssistedInject constructor( @Assisted("intent") private val intentSearch: String?, private val searchFreeDictionaryUseCase: SearchFreeDictionary, private val cacheUseCases: WordCacheUseCases, ) : ViewModel() { private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> println(throwable.stackTraceToString()) } private val scope = CoroutineScope(viewModelScope.coroutineContext + exceptionHandler) private var _state = MutableStateFlow(OverlayWindowState(searchTerm = intentSearch ?: "")) val state = _state.asStateFlow() init { val term = state.value.searchTerm if (term.isNotBlank()) { scope.launch { _state.update { it.copy(isSearching = true) } val cached = cacheUseCases.getCachedWord(term) if (cached != null) { _state.update { it.copy( meanings = cached.meanings, word = cached.word, phonetic = getFirstPhonetic(cached.phonetics) ) } } else { val result = searchFreeDictionaryUseCase(term).firstOrNull() if (result != null) { _state.update { it.copy( meanings = result.meanings, word = result.word, phonetic = getFirstPhonetic(result.phonetics) ) } } } _state.update { it.copy(isSearching = false) } } } } private fun getFirstPhonetic(phonetics: List): String { return phonetics.firstOrNull { it.text != null }?.text ?: "" } } ================================================ FILE: feature_overlay/src/main/java/io/github/yamin8000/owl/feature_overlay/ui/components/ButtonsRow.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_overlay.main * ButtonsRow.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * ButtonsRow.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_overlay.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_overlay.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_overlay.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_overlay.ui.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.strings.R @Composable internal fun ButtonsRow( onDismissRequest: () -> Unit, navigateToApp: () -> Unit, modifier: Modifier = Modifier ) { Row( modifier = modifier, horizontalArrangement = Arrangement.spacedBy(Sizes.Large, Alignment.CenterHorizontally), content = { Button( onClick = onDismissRequest, content = { Text(stringResource(R.string.close)) } ) Button( onClick = navigateToApp, content = { Text(stringResource(R.string.more_in_app)) } ) } ) } ================================================ FILE: feature_overlay/src/main/java/io/github/yamin8000/owl/feature_overlay/ui/components/SearchList.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_overlay.main * SearchList.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * SearchList.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_overlay.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_overlay.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_overlay.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_overlay.ui.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.LinearProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.search.domain.model.Meaning import io.github.yamin8000.owl.search.ui.components.MeaningCard import io.github.yamin8000.owl.search.ui.components.WordCard import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import java.util.UUID import kotlin.random.Random @Preview @Composable private fun Preview() { PreviewTheme { SearchList( isSearching = Random.nextBoolean(), word = "Word", phonetic = "Phonetic", meanings = Meaning.mockList().toImmutableList() ) } } @Composable internal fun SearchList( isSearching: Boolean, word: String, phonetic: String, meanings: ImmutableList, modifier: Modifier = Modifier ) { LazyColumn( modifier = modifier, verticalArrangement = Arrangement.spacedBy(Sizes.Medium, Alignment.CenterVertically), horizontalAlignment = Alignment.CenterHorizontally, content = { if (isSearching) { item { LinearProgressIndicator( modifier = Modifier.fillMaxWidth() ) } } else { item { WordCard( word = word, pronunciation = phonetic ) } items( items = meanings, key = { item -> "meaning-${item.id ?: UUID.randomUUID()}" }, itemContent = { meaning -> MeaningCard( word = word, meaning = meaning ) } ) } } ) } ================================================ FILE: feature_settings/.gitignore ================================================ /build ================================================ FILE: feature_settings/build.gradle.kts ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings * build.gradle.kts Copyrighted by Yamin Siahmargooei at 2025/9/8 * build.gradle.kts Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.google.ksp) alias(libs.plugins.jetbrains.kotlin.compose.plugin) alias(libs.plugins.hilt) } android { namespace = "io.github.yamin8000.owl.feature_settings" compileSdk = 36 defaultConfig { minSdk = 23 } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } } buildFeatures { compose = true } } composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_compiler") metricsDestination = layout.buildDirectory.dir("compose_compiler") } dependencies { //core implementation(project(":common")) //hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) implementation(libs.hilt.lifecycle.compose) } ================================================ FILE: feature_settings/src/main/AndroidManifest.xml ================================================ ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/Settings.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * Settings.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * Settings.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewFontScale import androidx.compose.ui.tooling.preview.PreviewScreenSizes import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.yamin8000.owl.common.ui.components.ScaffoldWithTitle import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.datastore.domain.model.ThemeType import io.github.yamin8000.owl.feature_settings.ui.components.GeneralSettings import io.github.yamin8000.owl.feature_settings.ui.components.theme.ThemeSetting import io.github.yamin8000.owl.feature_settings.ui.components.tts.TtsLanguageSetting import io.github.yamin8000.owl.strings.R import kotlinx.collections.immutable.persistentListOf import kotlin.random.Random @PreviewScreenSizes @PreviewFontScale @Composable private fun Preview() { PreviewTheme { SettingsContent( onAction = {}, onThemeChanged = {}, onBackClick = {}, state = SettingsState( theme = ThemeType.entries().random(), ttsLang = "en-US", isVibrating = Random.nextBoolean(), isStartingBlank = Random.nextBoolean(), englishLanguages = persistentListOf() ) ) } } @Composable fun SettingsScreen( modifier: Modifier = Modifier, vm: SettingsViewModel = hiltViewModel(), onThemeChanged: (ThemeType) -> Unit, onBackClick: () -> Unit ) { val state = vm.state.collectAsStateWithLifecycle().value SettingsContent( modifier = modifier, state = state, onAction = { vm.onAction(it) }, onThemeChanged = onThemeChanged, onBackClick = onBackClick ) } @Composable internal fun SettingsContent( state: SettingsState, onAction: (SettingsAction) -> Unit, onThemeChanged: (ThemeType) -> Unit, onBackClick: () -> Unit, modifier: Modifier = Modifier ) { ScaffoldWithTitle( modifier = modifier, title = stringResource(R.string.settings), onBackClick = onBackClick, content = { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy( Sizes.Medium, Alignment.CenterVertically ), modifier = Modifier .verticalScroll(rememberScrollState()) .padding(bottom = Sizes.Large), content = { GeneralSettings( isVibrating = state.isVibrating, onVibratingChange = { onAction(SettingsAction.OnVibrationChange(it)) }, isStartingBlank = state.isStartingBlank, onStartingBlankChange = { onAction(SettingsAction.OnStartingBlankChange(it)) } ) ThemeSetting( theme = state.theme, onThemeChanged = { newTheme -> onAction(SettingsAction.OnThemeChange(newTheme)) onThemeChanged(newTheme) } ) TtsLanguageSetting( currentTtsTag = state.ttsLang, languages = state.englishLanguages, onTtsTagChange = { onAction(SettingsAction.OnTtsLangChange(it)) } ) } ) } ) } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/SettingsAction.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * SettingsEvent.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * SettingsEvent.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui import io.github.yamin8000.owl.datastore.domain.model.ThemeType sealed interface SettingsAction { data class OnVibrationChange(val value: Boolean) : SettingsAction data class OnStartingBlankChange(val value: Boolean) : SettingsAction data class OnTtsLangChange(val value: String) : SettingsAction data class OnThemeChange(val newTheme: ThemeType) : SettingsAction } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/SettingsState.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * SettingsState.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * SettingsState.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui import io.github.yamin8000.owl.datastore.domain.model.ThemeType import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import java.util.Locale data class SettingsState( val theme: ThemeType = ThemeType.System, val ttsLang: String = "en-US", val isVibrating: Boolean = true, val isStartingBlank: Boolean = true, val englishLanguages: ImmutableList = persistentListOf() ) ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/SettingsViewModel.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * SettingsViewModel.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * SettingsViewModel.kt Last modified at 2024/8/19 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import io.github.yamin8000.owl.common.util.TTS import io.github.yamin8000.owl.datastore.domain.usecase.settings.SettingUseCases import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import javax.inject.Inject @HiltViewModel class SettingsViewModel @Inject constructor( private val useCases: SettingUseCases, private val tts: TTS ) : ViewModel() { private val scope = viewModelScope private var _state = MutableStateFlow(SettingsState()) val state = _state.asStateFlow() init { runBlocking { _state.update { settingsState -> settingsState.copy( theme = useCases.getTheme(), ttsLang = useCases.getTTS(), isVibrating = useCases.getVibration(), isStartingBlank = useCases.getStartingBlank(), ) } } scope.launch { _state.update { settingsState -> settingsState.copy(englishLanguages = tts.englishLanguages().toImmutableList()) } } } fun onAction(action: SettingsAction) { when (action) { is SettingsAction.OnStartingBlankChange -> { _state.update { it.copy(isStartingBlank = action.value) } scope.launch { useCases.setStartingBlank(action.value) } } is SettingsAction.OnTtsLangChange -> { _state.update { it.copy(ttsLang = action.value) } scope.launch { useCases.setTTS(action.value) } } is SettingsAction.OnVibrationChange -> { _state.update { it.copy(isVibrating = action.value) } scope.launch { useCases.setVibration(action.value) } } is SettingsAction.OnThemeChange -> { _state.update { it.copy(theme = action.newTheme) } scope.launch { useCases.setTheme(action.newTheme) } } } } } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/components/GeneralSettings.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * GeneralSettings.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * GeneralSettings.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui.components import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Language import androidx.compose.material.icons.twotone.Search import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.strings.R @MyPreview @Composable private fun Preview() { PreviewTheme { GeneralSettings( isVibrating = true, onVibratingChange = {}, isStartingBlank = true, onStartingBlankChange = {} ) } } @Composable internal fun GeneralSettings( isVibrating: Boolean, onVibratingChange: (Boolean) -> Unit, isStartingBlank: Boolean, onStartingBlankChange: (Boolean) -> Unit, modifier: Modifier = Modifier ) { SettingsItemCard( modifier = modifier.fillMaxWidth(), title = stringResource(R.string.general), content = { SwitchItem( imageVector = Icons.TwoTone.Language, caption = stringResource(R.string.vibrate_on_scroll), checked = isVibrating, onCheckedChange = onVibratingChange ) SwitchItem( imageVector = Icons.TwoTone.Search, caption = stringResource(R.string.start_blank_search), checked = isStartingBlank, onCheckedChange = onStartingBlankChange ) } ) } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/components/SettingsItem.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * SettingsItem.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * SettingsItem.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui.components import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.ArrowDropDownCircle import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes @MyPreview @Composable private fun Preview() { PreviewTheme { SettingsItem( onClick = {}, content = {} ) } } @Composable internal fun SettingsItem( onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable RowScope.() -> Unit ) { val interactionSource = remember { MutableInteractionSource() } Box( modifier = modifier.clickable( interactionSource = interactionSource, indication = LocalIndication.current, onClick = onClick, ), content = { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, content = { Row( //Extra padding for increasing touch area modifier = Modifier .fillMaxWidth() .padding(vertical = Sizes.Medium) .weight(5f), horizontalArrangement = Arrangement.spacedBy(Sizes.Medium, Alignment.Start), verticalAlignment = Alignment.CenterVertically, content = content ) Box( modifier = Modifier.weight(1f), content = { Icon( imageVector = Icons.TwoTone.ArrowDropDownCircle, contentDescription = null, modifier = Modifier.align(Alignment.CenterEnd) ) } ) } ) } ) } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/components/SettingsItemCard.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * SettingsItemCard.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * SettingsItemCard.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui.components import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.sp import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.theme.DefaultCutShape import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes @MyPreview @Composable private fun Preview() { PreviewTheme { SettingsItemCard( title = "Wifi", content = { Text( text = "on/off" ) } ) } } @Composable internal fun SettingsItemCard( title: String, modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit ) { Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(Sizes.Small, Alignment.CenterVertically), horizontalAlignment = Alignment.Start, content = { AppText( text = title, fontSize = 18.sp, color = MaterialTheme.colorScheme.primary ) OutlinedCard( border = BorderStroke(Sizes.xxSmall, MaterialTheme.colorScheme.secondary), shape = DefaultCutShape, content = { Column( modifier = Modifier.padding(Sizes.Large), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy( Sizes.Medium, Alignment.CenterVertically ), content = content ) } ) } ) } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/components/SwitchItem.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * SwitchItem.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * SwitchItem.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Category import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes @MyPreview @Composable private fun Preview() { PreviewTheme { SwitchItem( imageVector = Icons.TwoTone.Category, caption = "Item on/off", checked = false, onCheckedChange = {} ) } } @Composable internal fun SwitchItem( imageVector: ImageVector, caption: String, checked: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier ) { Row( modifier = modifier, horizontalArrangement = Arrangement.spacedBy(Sizes.Small, Alignment.Start), verticalAlignment = Alignment.CenterVertically, content = { Icon( imageVector = imageVector, contentDescription = null, tint = MaterialTheme.colorScheme.secondary ) SwitchWithText( caption = caption, checked = checked, onCheckedChange = onCheckedChange ) } ) } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/components/SwitchWithText.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * Components.kt Copyrighted by Yamin Siahmargooei at 2024/8/19 * Components.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Icon import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.style.TextOverflow import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme @MyPreview @Composable private fun Preview() { PreviewTheme { var checked by remember { mutableStateOf(true) } SwitchWithText( caption = "some settingssssssssssssssssssssssssssssssssssssssssssssssssssssssss", checked = checked, onCheckedChange = { checked = it } ) } } @Composable internal fun SwitchWithText( caption: String, checked: Boolean, onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier ) { val hapticFeedback = LocalHapticFeedback.current val feedbackType = remember(checked) { if (checked) HapticFeedbackType.ToggleOff else HapticFeedbackType.ToggleOn } Box( modifier = modifier.clickable( role = Role.Switch, onClick = { hapticFeedback.performHapticFeedback(feedbackType) onCheckedChange(!checked) } ), content = { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, content = { AppText( text = caption, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(5f) ) Box( modifier = Modifier.weight(1f), content = { val icon = remember(checked) { if (checked) Icons.Filled.Check else Icons.Filled.Close } Switch( checked = checked, onCheckedChange = onCheckedChange, modifier = Modifier.align(Alignment.CenterEnd), thumbContent = { Icon( imageVector = icon, contentDescription = null, modifier = Modifier.size(SwitchDefaults.IconSize), ) } ) } ) } ) } ) } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/components/theme/DynamicThemeNotice.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * DynamicThemeNotice.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * DynamicThemeNotice.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui.components.theme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.strings.R @Composable internal fun DynamicThemeNotice( modifier: Modifier = Modifier, ) { AppText( modifier = modifier, text = stringResource(R.string.dynamic_theme_notice), textAlign = TextAlign.Justify ) } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/components/theme/ThemeChangerDialog.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * ThemeChangerDialog.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * ThemeChangerDialog.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui.components.theme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.DisplaySettings import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.RadioButton import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.theme.DefaultCutShape import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.datastore.domain.model.ThemeType import io.github.yamin8000.owl.feature_settings.ui.utils.Utility.toStringResource import io.github.yamin8000.owl.strings.R @MyPreview @Composable private fun Preview() { PreviewTheme { ThemeChangerDialog( currentTheme = ThemeType.entries().random(), onCurrentThemeChange = {}, onDismiss = {} ) } } @OptIn(ExperimentalMaterial3Api::class) @Composable internal fun ThemeChangerDialog( currentTheme: ThemeType, onCurrentThemeChange: (ThemeType) -> Unit, onDismiss: () -> Unit, modifier: Modifier = Modifier ) { val themes = remember { ThemeType.entries() } BasicAlertDialog( modifier = modifier, onDismissRequest = onDismiss, content = { Surface( shape = DefaultCutShape, content = { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy( Sizes.Small, Alignment.CenterVertically ), modifier = Modifier .padding(Sizes.Large) .selectableGroup() .fillMaxWidth() .verticalScroll(rememberScrollState()), content = { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy( Sizes.Small, Alignment.CenterHorizontally ), verticalAlignment = Alignment.CenterVertically, content = { Icon( imageVector = Icons.TwoTone.DisplaySettings, contentDescription = null ) AppText(text = stringResource(R.string.theme)) } ) themes.forEach { theme -> Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy( Sizes.xSmall, Alignment.Start ), modifier = Modifier .fillMaxWidth() .selectable( selected = theme == currentTheme, role = Role.RadioButton, onClick = { onCurrentThemeChange(theme) onDismiss() } ), content = { val context = LocalContext.current RadioButton( modifier = Modifier.padding(start = Sizes.Medium), selected = theme == currentTheme, onClick = null ) AppText( modifier = Modifier.padding(vertical = Sizes.Large), text = theme.toStringResource(context) ) } ) } } ) } ) } ) } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/components/theme/ThemeSetting.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * ThemeSetting.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * ThemeSetting.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui.components.theme import android.os.Build import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.DisplaySettings import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.datastore.domain.model.ThemeType import io.github.yamin8000.owl.feature_settings.ui.components.SettingsItem import io.github.yamin8000.owl.feature_settings.ui.components.SettingsItemCard import io.github.yamin8000.owl.feature_settings.ui.utils.Utility.toStringResource import io.github.yamin8000.owl.strings.R @MyPreview @Composable private fun Preview() { PreviewTheme { ThemeSetting( theme = ThemeType.System, onThemeChanged = {} ) } } @Composable internal fun ThemeSetting( theme: ThemeType, onThemeChanged: (ThemeType) -> Unit, modifier: Modifier = Modifier ) { var isShowingDialog by remember { mutableStateOf(false) } SettingsItemCard( modifier = modifier, title = stringResource(R.string.theme), content = { if (isShowingDialog) { ThemeChangerDialog( currentTheme = theme, onCurrentThemeChange = onThemeChanged, onDismiss = { isShowingDialog = false } ) } SettingsItem( onClick = { isShowingDialog = true }, content = { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy( Sizes.Small, Alignment.Start ), content = { val context = LocalContext.current Icon( imageVector = Icons.TwoTone.DisplaySettings, contentDescription = null, ) AppText( text = theme.toStringResource(context), maxLines = 2 ) } ) } ) if ((theme == ThemeType.System || theme == ThemeType.SystemDarker) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { DynamicThemeNotice() } } ) } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/components/tts/TtsLanguageItem.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * TtsLanguageItem.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * TtsLanguageItem.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui.components.tts import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.OutlinedCard import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.theme.DefaultCutShape import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import java.util.Locale @MyPreview @Composable private fun Preview() { PreviewTheme { TtsLanguageItem( localeTag = "en-us", isSelected = false, onClick = {} ) } } @Composable internal fun TtsLanguageItem( localeTag: String, isSelected: Boolean, onClick: (String) -> Unit, modifier: Modifier = Modifier ) { OutlinedCard( modifier = modifier.fillMaxWidth(), shape = DefaultCutShape, onClick = { onClick(localeTag) }, enabled = !isSelected, content = { AppText( modifier = Modifier.padding(Sizes.Large), text = Locale.forLanguageTag(localeTag).displayName, maxLines = 2, overflow = TextOverflow.Ellipsis ) } ) } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/components/tts/TtsLanguageSetting.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * TtsLanguageSetting.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * TtsLanguageSetting.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui.components.tts import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Language import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.feature_settings.ui.components.SettingsItem import io.github.yamin8000.owl.feature_settings.ui.components.SettingsItemCard import io.github.yamin8000.owl.strings.R import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import java.util.Locale @MyPreview @Composable private fun Preview() { PreviewTheme { TtsLanguageSetting( currentTtsTag = "en-us", languages = persistentListOf(), onTtsTagChange = {} ) } } @Composable internal fun TtsLanguageSetting( currentTtsTag: String, languages: ImmutableList, onTtsTagChange: (String) -> Unit, modifier: Modifier = Modifier ) { SettingsItemCard( modifier = modifier, title = stringResource(R.string.tts_language), content = { var isDialogShown by remember { mutableStateOf(false) } SettingsItem( onClick = { isDialogShown = true }, content = { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy( Sizes.Small, Alignment.Start ), content = { Icon( imageVector = Icons.TwoTone.Language, contentDescription = null ) AppText( text = Locale.forLanguageTag(currentTtsTag).displayName, maxLines = 2 ) } ) if (isDialogShown) { TtsLanguagesDialog( currentTtsTag = currentTtsTag, languages = languages, onDismiss = { isDialogShown = false }, onLanguageSelect = { onTtsTagChange(it) isDialogShown = false } ) } } ) } ) } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/components/tts/TtsLanguagesDialog.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * TtsLanguagesDialog.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * TtsLanguagesDialog.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui.components.tts import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Language import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.github.yamin8000.owl.common.ui.components.AppText import io.github.yamin8000.owl.common.ui.theme.DefaultCutShape import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.strings.R import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import java.util.Locale @MyPreview @Composable private fun Preview() { PreviewTheme { TtsLanguagesDialog( currentTtsTag = "", languages = persistentListOf(), onDismiss = {}, onLanguageSelect = {} ) } } @OptIn(ExperimentalMaterial3Api::class) @Composable internal fun TtsLanguagesDialog( currentTtsTag: String, languages: ImmutableList, onLanguageSelect: (String) -> Unit, onDismiss: () -> Unit, modifier: Modifier = Modifier ) { BasicAlertDialog( modifier = modifier, onDismissRequest = onDismiss, content = { Surface( shape = DefaultCutShape, content = { LazyColumn( modifier = Modifier.padding(Sizes.Large), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy( Sizes.Medium, Alignment.CenterVertically ), content = { item { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy( Sizes.Small, Alignment.CenterHorizontally ), verticalAlignment = Alignment.CenterVertically, content = { Icon( imageVector = Icons.TwoTone.Language, contentDescription = null ) AppText(text = stringResource(R.string.tts_language)) } ) } item { } items(items = languages) { TtsLanguageItem( localeTag = it.toLanguageTag(), isSelected = it.toLanguageTag() == currentTtsTag, onClick = onLanguageSelect ) } } ) } ) } ) } ================================================ FILE: feature_settings/src/main/java/io/github/yamin8000/owl/feature_settings/ui/utils/Utility.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_settings.main * Utility.kt Copyrighted by Yamin Siahmargooei at 2025/2/7 * Utility.kt Last modified at 2025/2/7 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_settings.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_settings.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.feature_settings.ui.utils import android.content.Context import io.github.yamin8000.owl.datastore.domain.model.ThemeType import io.github.yamin8000.owl.strings.R object Utility { internal fun ThemeType.toStringResource( context: Context ) = when (this) { ThemeType.Dark -> context.getString(R.string.theme_dark) ThemeType.Darker -> context.getString(R.string.theme_oled) ThemeType.Light -> context.getString(R.string.theme_light) ThemeType.System -> context.getString(R.string.theme_system) ThemeType.SystemDarker -> context.getString(R.string.theme_system_darker) } } ================================================ FILE: gradle/libs.versions.toml ================================================ [versions] android-application = "9.0.1" androidx-activity-compose = "1.13.0" androidx-compose-bom = "2026.05.00" androidx-core-ktx = "1.18.0" androidx-core-splash = "1.2.0" androidx-datastore = "1.2.1" androidx-lifecycle-compose = "2.10.0" androidx-material3 = "1.5.0-alpha15" androidx-navigation-compose = "2.9.8" androidx-room = "2.8.4" coil-compose = "2.7.0" datafaker = "2.5.4" desugar-jdk-libs = "2.1.5" hilt = "2.59.2" hilt-compose = "1.3.0" kotlin = "2.3.21" kotlinx-collections-immutable = "0.4.0" ksp = "2.3.7" lottie-compose = "6.7.1" moshi = "1.15.2" retrofit = "3.0.0" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidx-compose-bom" } androidx-compose-material = { module = "androidx.compose.material:material" } androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } androidx-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "androidx-material3" } androidx-compose-material3-window-size = { module = "androidx.compose.material3:material3-window-size-class", version.ref = "androidx-material3" } androidx-compose-ui = { module = "androidx.compose.ui:ui" } androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core-ktx" } androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidx-core-splash" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" } androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle-compose" } androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle-compose" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation-compose" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" } androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil-compose" } datafaker = { module = "net.datafaker:datafaker", version.ref = "datafaker" } desugar-jdk-libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar-jdk-libs" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } hilt-lifecycle-compose = { module = "androidx.hilt:hilt-lifecycle-viewmodel-compose", version.ref = "hilt-compose" } kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinx-collections-immutable" } lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = "lottie-compose" } moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } moshi-kotlin-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" } retrofit-converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" } retrofit-main = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } retrofit-type-keeper = { module = "com.squareup.retrofit2:response-type-keeper", version.ref = "retrofit" } [plugins] android-application = { id = "com.android.application", version.ref = "android-application" } android-library = { id = "com.android.library", version.ref = "android-application" } androix-room = { id = "androidx.room", version.ref = "androidx-room" } google-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } jetbrains-kotlin-compose-plugin = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-parcelize = { id = "kotlin-parcelize" } legacy-kapt = { id = "com.android.legacy-kapt", version.ref = "android-application" } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip networkTimeout=10000 retries=0 retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ # # freeDictionaryApp/freeDictionaryApp # gradle.properties Copyrighted by Yamin Siahmargooei at 2024/5/9 # gradle.properties Last modified at 2024/3/23 # This file is part of freeDictionaryApp/freeDictionaryApp. # Copyright (C) 2024 Yamin Siahmargooei # # freeDictionaryApp/freeDictionaryApp is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # freeDictionaryApp/freeDictionaryApp is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with freeDictionaryApp. If not, see . # org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official android.r8.optimizedResourceShrinking=true android.r8.strictFullModeForKeepRules=false org.gradle.configuration-cache=true ================================================ FILE: gradlew ================================================ #!/bin/sh # # Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # SPDX-License-Identifier: Apache-2.0 # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java if ! command -v java >/dev/null 2>&1 then die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. if ! command -v xargs >/dev/null 2>&1 then die "xargs is not available" fi # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @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 @rem SPDX-License-Identifier: Apache-2.0 @rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables, and ensure extensions are enabled setlocal EnableExtensions 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 to pass JVM options to this script. 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. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 "%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 "%COMSPEC%" /c exit 1 :execute @rem Setup the command line @rem Execute Gradle @rem endlocal doesn't take effect until after the line is parsed and variables are expanded @rem which allows us to clear the local environment before executing the java command endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel :exitWithErrorLevel @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts "%COMSPEC%" /c exit %ERRORLEVEL% ================================================ FILE: renovate.json ================================================ { "extends": [ "config:recommended" ] } ================================================ FILE: search/.gitignore ================================================ /build ================================================ FILE: search/build.gradle.kts ================================================ /* * freeDictionaryApp/freeDictionaryApp.search * build.gradle.kts Copyrighted by Yamin Siahmargooei at 2025/9/8 * build.gradle.kts Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.search. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.google.ksp) alias(libs.plugins.jetbrains.kotlin.compose.plugin) alias(libs.plugins.hilt) alias(libs.plugins.androix.room) } android { namespace = "io.github.yamin8000.owl.search" compileSdk = 36 defaultConfig { minSdk = 23 } room { schemaDirectory("$projectDir/schemas") } compileOptions { isCoreLibraryDesugaringEnabled = true sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } } buildFeatures { compose = true } } composeCompiler { reportsDestination = layout.buildDirectory.dir("compose_compiler") metricsDestination = layout.buildDirectory.dir("compose_compiler") } dependencies { //core coreLibraryDesugaring(libs.desugar.jdk.libs) implementation(project(":common")) //hilt implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) implementation(libs.hilt.lifecycle.compose) //room implementation(libs.androidx.room.runtime) annotationProcessor(libs.androidx.room.compiler) ksp(libs.androidx.room.compiler) api(libs.androidx.room.ktx) //retrofit api(libs.retrofit.main) implementation(libs.retrofit.converter.moshi) //moshi implementation(libs.moshi.kotlin) ksp(libs.moshi.kotlin.codegen) } ================================================ FILE: search/schemas/io.github.yamin8000.owl.search.data.datasource.local.AppDatabase/1.json ================================================ { "formatVersion": 1, "database": { "version": 1, "identityHash": "0c3ad9bffebaa41fcb44752f733c24f4", "entities": [ { "tableName": "WordEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `word` TEXT NOT NULL, `pronunciation` TEXT)", "fields": [ { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "word", "columnName": "word", "affinity": "TEXT", "notNull": true }, { "fieldPath": "pronunciation", "columnName": "pronunciation", "affinity": "TEXT", "notNull": false } ], "primaryKey": { "columnNames": [ "id" ], "autoGenerate": true }, "indices": [], "foreignKeys": [] }, { "tableName": "DefinitionEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `wordId` INTEGER NOT NULL, `type` TEXT, `definition` TEXT NOT NULL, `example` TEXT, `imageUrl` TEXT, `emoji` TEXT, FOREIGN KEY(`wordId`) REFERENCES `WordEntity`(`id`) ON UPDATE NO ACTION ON DELETE RESTRICT )", "fields": [ { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "wordId", "columnName": "wordId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "type", "columnName": "type", "affinity": "TEXT", "notNull": false }, { "fieldPath": "definition", "columnName": "definition", "affinity": "TEXT", "notNull": true }, { "fieldPath": "example", "columnName": "example", "affinity": "TEXT", "notNull": false }, { "fieldPath": "imageUrl", "columnName": "imageUrl", "affinity": "TEXT", "notNull": false }, { "fieldPath": "emoji", "columnName": "emoji", "affinity": "TEXT", "notNull": false } ], "primaryKey": { "columnNames": [ "id" ], "autoGenerate": true }, "indices": [ { "name": "index_DefinitionEntity_wordId", "unique": false, "columnNames": [ "wordId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_DefinitionEntity_wordId` ON `${TABLE_NAME}` (`wordId`)" } ], "foreignKeys": [ { "table": "WordEntity", "onDelete": "RESTRICT", "onUpdate": "NO ACTION", "columns": [ "wordId" ], "referencedColumns": [ "id" ] } ] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0c3ad9bffebaa41fcb44752f733c24f4')" ] } } ================================================ FILE: search/schemas/io.github.yamin8000.owl.search.data.datasource.local.AppDatabase/2.json ================================================ { "formatVersion": 1, "database": { "version": 2, "identityHash": "c721e6247cc6759b3f2cf71fc120d9f9", "entities": [ { "tableName": "AntonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "DefinitionEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`meaningId` INTEGER NOT NULL, `definition` TEXT NOT NULL, `example` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`meaningId`) REFERENCES `MeaningEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "meaningId", "columnName": "meaningId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "definition", "columnName": "definition", "affinity": "TEXT", "notNull": true }, { "fieldPath": "example", "columnName": "example", "affinity": "TEXT", "notNull": false }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "MeaningEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "meaningId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "EntryEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", "fields": [ { "fieldPath": "word", "columnName": "word", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [] }, { "tableName": "MeaningEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`entryId` INTEGER NOT NULL, `partOfSpeech` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "partOfSpeech", "columnName": "partOfSpeech", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "PhoneticEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`entryId` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "SynonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c721e6247cc6759b3f2cf71fc120d9f9')" ] } } ================================================ FILE: search/schemas/io.github.yamin8000.owl.search.data.datasource.local.AppDatabase/3.json ================================================ { "formatVersion": 1, "database": { "version": 3, "identityHash": "9564d8234936acf94b1e39343c7d6e66", "entities": [ { "tableName": "AntonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "DefinitionEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`meaningId` INTEGER NOT NULL, `definition` TEXT NOT NULL, `example` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`meaningId`) REFERENCES `MeaningEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "meaningId", "columnName": "meaningId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "definition", "columnName": "definition", "affinity": "TEXT", "notNull": true }, { "fieldPath": "example", "columnName": "example", "affinity": "TEXT", "notNull": false }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "MeaningEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "meaningId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "EntryEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", "fields": [ { "fieldPath": "word", "columnName": "word", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [] }, { "tableName": "MeaningEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`entryId` INTEGER NOT NULL, `partOfSpeech` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "partOfSpeech", "columnName": "partOfSpeech", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "PhoneticEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`value` TEXT NOT NULL, `entryId` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "SynonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9564d8234936acf94b1e39343c7d6e66')" ] } } ================================================ FILE: search/schemas/io.github.yamin8000.owl.search.data.datasource.local.AppDatabase/4.json ================================================ { "formatVersion": 1, "database": { "version": 4, "identityHash": "9564d8234936acf94b1e39343c7d6e66", "entities": [ { "tableName": "AntonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "DefinitionEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`meaningId` INTEGER NOT NULL, `definition` TEXT NOT NULL, `example` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`meaningId`) REFERENCES `MeaningEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "meaningId", "columnName": "meaningId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "definition", "columnName": "definition", "affinity": "TEXT", "notNull": true }, { "fieldPath": "example", "columnName": "example", "affinity": "TEXT", "notNull": false }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "MeaningEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "meaningId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "EntryEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", "fields": [ { "fieldPath": "word", "columnName": "word", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [] }, { "tableName": "MeaningEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`entryId` INTEGER NOT NULL, `partOfSpeech` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "partOfSpeech", "columnName": "partOfSpeech", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "PhoneticEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`value` TEXT NOT NULL, `entryId` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "SynonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9564d8234936acf94b1e39343c7d6e66')" ] } } ================================================ FILE: search/schemas/io.github.yamin8000.owl.search.data.datasource.local.AppDatabase/5.json ================================================ { "formatVersion": 1, "database": { "version": 5, "identityHash": "874e92af5349f88d5e65cf84db6f30ad", "entities": [ { "tableName": "AntonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_AntonymEntity_definitionId", "unique": false, "columnNames": [ "definitionId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_AntonymEntity_definitionId` ON `${TABLE_NAME}` (`definitionId`)" } ], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "DefinitionEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`meaningId` INTEGER NOT NULL, `definition` TEXT NOT NULL, `example` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`meaningId`) REFERENCES `MeaningEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "meaningId", "columnName": "meaningId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "definition", "columnName": "definition", "affinity": "TEXT", "notNull": true }, { "fieldPath": "example", "columnName": "example", "affinity": "TEXT", "notNull": false }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_DefinitionEntity_meaningId", "unique": false, "columnNames": [ "meaningId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_DefinitionEntity_meaningId` ON `${TABLE_NAME}` (`meaningId`)" } ], "foreignKeys": [ { "table": "MeaningEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "meaningId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "EntryEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", "fields": [ { "fieldPath": "word", "columnName": "word", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [] }, { "tableName": "MeaningEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`entryId` INTEGER NOT NULL, `partOfSpeech` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "partOfSpeech", "columnName": "partOfSpeech", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_MeaningEntity_entryId", "unique": false, "columnNames": [ "entryId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_MeaningEntity_entryId` ON `${TABLE_NAME}` (`entryId`)" } ], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "PhoneticEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`value` TEXT, `entryId` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": false }, { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_PhoneticEntity_entryId", "unique": false, "columnNames": [ "entryId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_PhoneticEntity_entryId` ON `${TABLE_NAME}` (`entryId`)" } ], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "SynonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_SynonymEntity_definitionId", "unique": false, "columnNames": [ "definitionId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_SynonymEntity_definitionId` ON `${TABLE_NAME}` (`definitionId`)" } ], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '874e92af5349f88d5e65cf84db6f30ad')" ] } } ================================================ FILE: search/schemas/io.github.yamin8000.owl.search.data.datasource.local.AppDatabase/6.json ================================================ { "formatVersion": 1, "database": { "version": 6, "identityHash": "874e92af5349f88d5e65cf84db6f30ad", "entities": [ { "tableName": "AntonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_AntonymEntity_definitionId", "unique": false, "columnNames": [ "definitionId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_AntonymEntity_definitionId` ON `${TABLE_NAME}` (`definitionId`)" } ], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "DefinitionEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`meaningId` INTEGER NOT NULL, `definition` TEXT NOT NULL, `example` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`meaningId`) REFERENCES `MeaningEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "meaningId", "columnName": "meaningId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "definition", "columnName": "definition", "affinity": "TEXT", "notNull": true }, { "fieldPath": "example", "columnName": "example", "affinity": "TEXT", "notNull": false }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_DefinitionEntity_meaningId", "unique": false, "columnNames": [ "meaningId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_DefinitionEntity_meaningId` ON `${TABLE_NAME}` (`meaningId`)" } ], "foreignKeys": [ { "table": "MeaningEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "meaningId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "EntryEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", "fields": [ { "fieldPath": "word", "columnName": "word", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [] }, { "tableName": "MeaningEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`entryId` INTEGER NOT NULL, `partOfSpeech` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "partOfSpeech", "columnName": "partOfSpeech", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_MeaningEntity_entryId", "unique": false, "columnNames": [ "entryId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_MeaningEntity_entryId` ON `${TABLE_NAME}` (`entryId`)" } ], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "PhoneticEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`value` TEXT, `entryId` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": false }, { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_PhoneticEntity_entryId", "unique": false, "columnNames": [ "entryId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_PhoneticEntity_entryId` ON `${TABLE_NAME}` (`entryId`)" } ], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "SynonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_SynonymEntity_definitionId", "unique": false, "columnNames": [ "definitionId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_SynonymEntity_definitionId` ON `${TABLE_NAME}` (`definitionId`)" } ], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '874e92af5349f88d5e65cf84db6f30ad')" ] } } ================================================ FILE: search/schemas/io.github.yamin8000.owl.search.data.datasource.local.AppDatabase/7.json ================================================ { "formatVersion": 1, "database": { "version": 7, "identityHash": "43a4c3665258d38f39d21dc3839c336c", "entities": [ { "tableName": "AntonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_AntonymEntity_definitionId", "unique": false, "columnNames": [ "definitionId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_AntonymEntity_definitionId` ON `${TABLE_NAME}` (`definitionId`)" } ], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "DefinitionEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`meaningId` INTEGER NOT NULL, `definition` TEXT NOT NULL, `example` TEXT, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`meaningId`) REFERENCES `MeaningEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "meaningId", "columnName": "meaningId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "definition", "columnName": "definition", "affinity": "TEXT", "notNull": true }, { "fieldPath": "example", "columnName": "example", "affinity": "TEXT", "notNull": false }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_DefinitionEntity_meaningId", "unique": false, "columnNames": [ "meaningId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_DefinitionEntity_meaningId` ON `${TABLE_NAME}` (`meaningId`)" } ], "foreignKeys": [ { "table": "MeaningEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "meaningId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "EntryEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", "fields": [ { "fieldPath": "word", "columnName": "word", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [] }, { "tableName": "MeaningEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`entryId` INTEGER NOT NULL, `partOfSpeech` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "partOfSpeech", "columnName": "partOfSpeech", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_MeaningEntity_entryId", "unique": false, "columnNames": [ "entryId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_MeaningEntity_entryId` ON `${TABLE_NAME}` (`entryId`)" } ], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "PhoneticEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`value` TEXT, `entryId` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": false }, { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_PhoneticEntity_entryId", "unique": false, "columnNames": [ "entryId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_PhoneticEntity_entryId` ON `${TABLE_NAME}` (`entryId`)" } ], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "SynonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_SynonymEntity_definitionId", "unique": false, "columnNames": [ "definitionId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_SynonymEntity_definitionId` ON `${TABLE_NAME}` (`definitionId`)" } ], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "TermEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", "fields": [ { "fieldPath": "word", "columnName": "word", "affinity": "TEXT", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '43a4c3665258d38f39d21dc3839c336c')" ] } } ================================================ FILE: search/schemas/io.github.yamin8000.owl.search.data.datasource.local.AppDatabase/8.json ================================================ { "formatVersion": 1, "database": { "version": 8, "identityHash": "ef5f55a0273992386750eddc29a250c5", "entities": [ { "tableName": "AntonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "createdAt", "columnName": "createdAt", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_AntonymEntity_definitionId", "unique": false, "columnNames": [ "definitionId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_AntonymEntity_definitionId` ON `${TABLE_NAME}` (`definitionId`)" } ], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "DefinitionEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`meaningId` INTEGER NOT NULL, `definition` TEXT NOT NULL, `example` TEXT, `createdAt` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`meaningId`) REFERENCES `MeaningEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "meaningId", "columnName": "meaningId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "definition", "columnName": "definition", "affinity": "TEXT", "notNull": true }, { "fieldPath": "example", "columnName": "example", "affinity": "TEXT", "notNull": false }, { "fieldPath": "createdAt", "columnName": "createdAt", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_DefinitionEntity_meaningId", "unique": false, "columnNames": [ "meaningId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_DefinitionEntity_meaningId` ON `${TABLE_NAME}` (`meaningId`)" } ], "foreignKeys": [ { "table": "MeaningEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "meaningId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "EntryEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", "fields": [ { "fieldPath": "word", "columnName": "word", "affinity": "TEXT", "notNull": true }, { "fieldPath": "createdAt", "columnName": "createdAt", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [] }, { "tableName": "MeaningEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`entryId` INTEGER NOT NULL, `partOfSpeech` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "partOfSpeech", "columnName": "partOfSpeech", "affinity": "TEXT", "notNull": true }, { "fieldPath": "createdAt", "columnName": "createdAt", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_MeaningEntity_entryId", "unique": false, "columnNames": [ "entryId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_MeaningEntity_entryId` ON `${TABLE_NAME}` (`entryId`)" } ], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "PhoneticEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`value` TEXT, `entryId` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`entryId`) REFERENCES `EntryEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": false }, { "fieldPath": "entryId", "columnName": "entryId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "createdAt", "columnName": "createdAt", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_PhoneticEntity_entryId", "unique": false, "columnNames": [ "entryId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_PhoneticEntity_entryId` ON `${TABLE_NAME}` (`entryId`)" } ], "foreignKeys": [ { "table": "EntryEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "entryId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "SynonymEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`definitionId` INTEGER NOT NULL, `value` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`definitionId`) REFERENCES `DefinitionEntity`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "fields": [ { "fieldPath": "definitionId", "columnName": "definitionId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "value", "columnName": "value", "affinity": "TEXT", "notNull": true }, { "fieldPath": "createdAt", "columnName": "createdAt", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [ { "name": "index_SynonymEntity_definitionId", "unique": false, "columnNames": [ "definitionId" ], "orders": [], "createSql": "CREATE INDEX IF NOT EXISTS `index_SynonymEntity_definitionId` ON `${TABLE_NAME}` (`definitionId`)" } ], "foreignKeys": [ { "table": "DefinitionEntity", "onDelete": "NO ACTION", "onUpdate": "NO ACTION", "columns": [ "definitionId" ], "referencedColumns": [ "id" ] } ] }, { "tableName": "TermEntity", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", "fields": [ { "fieldPath": "word", "columnName": "word", "affinity": "TEXT", "notNull": true }, { "fieldPath": "createdAt", "columnName": "createdAt", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "id", "columnName": "id", "affinity": "INTEGER", "notNull": true } ], "primaryKey": { "autoGenerate": true, "columnNames": [ "id" ] }, "indices": [], "foreignKeys": [] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ef5f55a0273992386750eddc29a250c5')" ] } } ================================================ FILE: search/src/main/AndroidManifest.xml ================================================ ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/local/AppDatabase.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * AppDatabase.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * AppDatabase.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.local import androidx.room.AutoMigration import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import io.github.yamin8000.owl.search.data.datasource.local.dao.DAOs import io.github.yamin8000.owl.search.data.datasource.local.entity.AntonymEntity import io.github.yamin8000.owl.search.data.datasource.local.entity.DefinitionEntity import io.github.yamin8000.owl.search.data.datasource.local.entity.EntryEntity import io.github.yamin8000.owl.search.data.datasource.local.entity.MeaningEntity import io.github.yamin8000.owl.search.data.datasource.local.entity.PhoneticEntity import io.github.yamin8000.owl.search.data.datasource.local.entity.SynonymEntity import io.github.yamin8000.owl.search.data.datasource.local.entity.TermEntity @Database( version = 8, entities = [ AntonymEntity::class, DefinitionEntity::class, EntryEntity::class, MeaningEntity::class, PhoneticEntity::class, SynonymEntity::class, TermEntity::class ], exportSchema = true, autoMigrations = [ AutoMigration(from = 6, to = 7) ] ) abstract class AppDatabase : RoomDatabase() { abstract fun antonymDao(): DAOs.AntonymDao abstract fun definitionDao(): DAOs.DefinitionDao abstract fun entryDao(): DAOs.EntryDao abstract fun meaningDao(): DAOs.MeaningDao abstract fun phoneticDao(): DAOs.PhoneticDao abstract fun synonymDao(): DAOs.SynonymDao abstract fun termDao(): DAOs.TermDao } val MIGRATION_7_8 = object : Migration(7, 8) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL(addDateColumn("AntonymEntity")) db.execSQL(addDateColumn("DefinitionEntity")) db.execSQL(addDateColumn("EntryEntity")) db.execSQL(addDateColumn("MeaningEntity")) db.execSQL(addDateColumn("PhoneticEntity")) db.execSQL(addDateColumn("SynonymEntity")) db.execSQL(addDateColumn("TermEntity")) } } private fun addDateColumn( tableName: String ): String = "ALTER TABLE `$tableName` ADD COLUMN createdAt INTEGER NOT NULL DEFAULT 0" ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/local/dao/AdvancedDao.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * AdvancedDao.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * AdvancedDao.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.local.dao import androidx.room.RawQuery import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SupportSQLiteQuery @Suppress("unused") abstract class AdvancedDao(tableName: String) : BaseDao(tableName) { @RawQuery protected abstract suspend fun find(query: SupportSQLiteQuery): T? suspend fun find(id: Long) = find(SimpleSQLiteQuery("$baseQuery where id = $id")) @RawQuery protected abstract suspend fun all(query: SupportSQLiteQuery): List suspend fun all() = all(SimpleSQLiteQuery(baseQuery)) @RawQuery protected abstract suspend fun findAll(query: SupportSQLiteQuery): List suspend fun findAll( ids: List ): List { val params = ids.joinToString(",") return findAll(SimpleSQLiteQuery("$baseWhereQuery id in ($params)")) } @RawQuery protected abstract suspend fun where(query: SupportSQLiteQuery): List suspend fun where( column: String, value: V, operator: String = "=" ) = where(SimpleSQLiteQuery("$baseWhereQuery $column $operator '$value'")) @RawQuery protected abstract suspend fun whereIn(query: SupportSQLiteQuery): List suspend fun whereIn( column: String, values: List<*> ): List { val temp = values.joinToString(separator = ",", transform = { "'$it'" }) val query = SimpleSQLiteQuery("$baseWhereQuery $column in ($temp)") return whereIn(query) } suspend fun getByParams( paramPair: Pair, vararg paramPairs: Pair ): List { val params = listOf(paramPair, *paramPairs) val condition = buildString { params.forEachIndexed { index, (first, second) -> append("${first}='${second}'") if (index != params.lastIndex) append(" and ") } } return where(SimpleSQLiteQuery("$baseWhereQuery $condition")) } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/local/dao/BaseDao.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.data.main * BaseDao.kt Copyrighted by Yamin Siahmargooei at 2024/5/9 * BaseDao.kt Last modified at 2024/3/23 * This file is part of freeDictionaryApp/freeDictionaryApp.data.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.data.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.data.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.local.dao import androidx.room.Delete import androidx.room.Insert import androidx.room.Update abstract class BaseDao(tableName: String) { protected val baseQuery = "select * from `$tableName`" protected val baseWhereQuery = "$baseQuery where" @Insert abstract suspend fun insert(entity: T): Long @Insert abstract suspend fun insertAll(entities: List): List @Update abstract suspend fun update(entity: T): Int @Delete abstract suspend fun delete(entity: T): Int @Delete abstract suspend fun deleteAll(entities: List): Int } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/local/dao/DAOs.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * DAOs.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * DAOs.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.local.dao import androidx.room.Dao import io.github.yamin8000.owl.search.data.datasource.local.entity.AntonymEntity import io.github.yamin8000.owl.search.data.datasource.local.entity.DefinitionEntity import io.github.yamin8000.owl.search.data.datasource.local.entity.EntryEntity import io.github.yamin8000.owl.search.data.datasource.local.entity.MeaningEntity import io.github.yamin8000.owl.search.data.datasource.local.entity.PhoneticEntity import io.github.yamin8000.owl.search.data.datasource.local.entity.SynonymEntity import io.github.yamin8000.owl.search.data.datasource.local.entity.TermEntity object DAOs { @Dao abstract class AntonymDao : AdvancedDao("AntonymEntity") @Dao abstract class DefinitionDao : AdvancedDao("DefinitionEntity") @Dao abstract class EntryDao : AdvancedDao("EntryEntity") @Dao abstract class MeaningDao : AdvancedDao("MeaningEntity") @Dao abstract class PhoneticDao : AdvancedDao("PhoneticEntity") @Dao abstract class SynonymDao : AdvancedDao("SynonymEntity") @Dao abstract class TermDao : AdvancedDao("TermEntity") } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/local/entity/AntonymEntity.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * AntonymEntity.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * AntonymEntity.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.local.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey @Entity( foreignKeys = [ ForeignKey( entity = DefinitionEntity::class, parentColumns = ["id"], childColumns = ["definitionId"] ) ] ) data class AntonymEntity( @ColumnInfo(index = true) val definitionId: Long, val value: String, val createdAt: Long, @PrimaryKey(autoGenerate = true) val id: Long = 0 ) ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/local/entity/DefinitionEntity.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * DefinitionEntity.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * DefinitionEntity.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.local.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey @Entity( foreignKeys = [ ForeignKey( entity = MeaningEntity::class, parentColumns = ["id"], childColumns = ["meaningId"], ) ] ) data class DefinitionEntity( @ColumnInfo(index = true) val meaningId: Long, val definition: String, val example: String?, val createdAt: Long, @PrimaryKey(autoGenerate = true) val id: Long = 0 ) ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/local/entity/EntryEntity.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * EntryEntity.kt Copyrighted by Yamin Siahmargooei at 2024/8/18 * EntryEntity.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.local.entity import androidx.room.Entity import androidx.room.PrimaryKey @Entity data class EntryEntity( val word: String, val createdAt: Long, @PrimaryKey(autoGenerate = true) val id: Long = 0 ) ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/local/entity/MeaningEntity.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * MeaningEntity.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * MeaningEntity.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.local.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey @Entity( foreignKeys = [ ForeignKey( entity = EntryEntity::class, parentColumns = ["id"], childColumns = ["entryId"] ) ] ) data class MeaningEntity( @ColumnInfo(index = true) val entryId: Long, val partOfSpeech: String, val createdAt: Long, @PrimaryKey(autoGenerate = true) val id: Long = 0 ) ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/local/entity/PhoneticEntity.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * PhoneticEntity.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * PhoneticEntity.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.local.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey @Entity( foreignKeys = [ ForeignKey( entity = EntryEntity::class, parentColumns = ["id"], childColumns = ["entryId"] ) ] ) data class PhoneticEntity( val value: String?, @ColumnInfo(index = true) val entryId: Long, val createdAt: Long, @PrimaryKey(autoGenerate = true) val id: Long = 0 ) ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/local/entity/SynonymEntity.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * SynonymEntity.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * SynonymEntity.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.local.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey @Entity( foreignKeys = [ ForeignKey( entity = DefinitionEntity::class, parentColumns = ["id"], childColumns = ["definitionId"] ) ] ) data class SynonymEntity( @ColumnInfo(index = true) val definitionId: Long, val value: String, val createdAt: Long, @PrimaryKey(autoGenerate = true) val id: Long = 0 ) ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/local/entity/TermEntity.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * TermEntity.kt Copyrighted by Yamin Siahmargooei at 2024/8/18 * TermEntity.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.local.entity import androidx.room.Entity import androidx.room.PrimaryKey @Entity data class TermEntity( val word: String, val createdAt: Long, @PrimaryKey(autoGenerate = true) val id: Long = 0 ) ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/remote/FreeDictionaryAPI.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * FreeDictionaryAPI.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * FreeDictionaryAPI.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.remote import io.github.yamin8000.owl.search.data.datasource.remote.dto.EntryDto import retrofit2.http.GET import retrofit2.http.Path /** [Free Dictionary API](https://dictionaryapi.dev) */ interface FreeDictionaryAPI { /** Search for [word] definitions */ @GET("entries/en/{word}") suspend fun search(@Path("word") word: String): List } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/remote/dto/DefinitionDto.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * DefinitionDto.kt Copyrighted by Yamin Siahmargooei at 2025/9/17 * DefinitionDto.kt Last modified at 2025/9/8 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.remote.dto import com.squareup.moshi.JsonClass import io.github.yamin8000.owl.search.domain.model.Definition @JsonClass(generateAdapter = true) data class DefinitionDto( val definition: String, val example: String?, val synonyms: List, val antonyms: List, val id: Long? = null, val meaningId: Long? = null ) { fun domain() = Definition( id = id, meaningId = meaningId, definition = definition, example = example, synonyms = synonyms, antonyms = antonyms ) } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/remote/dto/EntryDto.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * EntryDto.kt Copyrighted by Yamin Siahmargooei at 2025/9/17 * EntryDto.kt Last modified at 2025/9/8 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.remote.dto import com.squareup.moshi.JsonClass import io.github.yamin8000.owl.search.domain.model.Entry import kotlinx.collections.immutable.toImmutableList @JsonClass(generateAdapter = true) data class EntryDto( val word: String, val phonetics: List, val meanings: List, val license: LicenseDto? = null, val sourceUrls: List? = null, val id: Long? = null ) { fun domain() = Entry( word = word, phonetics = phonetics.map { it.domain() }.toImmutableList(), meanings = meanings.map { it.domain() }.toImmutableList(), license = license?.domain(), sourceUrls = sourceUrls?.toImmutableList(), id = id ) } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/remote/dto/LicenseDto.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * LicenseDto.kt Copyrighted by Yamin Siahmargooei at 2025/9/17 * LicenseDto.kt Last modified at 2025/9/8 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.remote.dto import com.squareup.moshi.JsonClass import io.github.yamin8000.owl.search.domain.model.License @JsonClass(generateAdapter = true) data class LicenseDto( val name: String, val url: String ) { fun domain() = License( name = name, url = url ) } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/remote/dto/MeaningDto.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * MeaningDto.kt Copyrighted by Yamin Siahmargooei at 2025/9/17 * MeaningDto.kt Last modified at 2025/9/8 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.remote.dto import com.squareup.moshi.JsonClass import io.github.yamin8000.owl.search.domain.model.Meaning import kotlinx.collections.immutable.toImmutableList @JsonClass(generateAdapter = true) data class MeaningDto( val partOfSpeech: String, val definitions: List, val synonyms: List, val antonyms: List, val id: Long? = null, val entryId: Long? = null ) { fun domain() = Meaning( id = id, entryId = entryId, partOfSpeech = partOfSpeech, definitions = definitions.map { it.domain() }.toImmutableList(), synonyms = synonyms.toImmutableList(), antonyms = antonyms.toImmutableList() ) } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/datasource/remote/dto/PhoneticDto.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * PhoneticDto.kt Copyrighted by Yamin Siahmargooei at 2025/9/17 * PhoneticDto.kt Last modified at 2025/9/8 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.datasource.remote.dto import com.squareup.moshi.JsonClass import io.github.yamin8000.owl.search.domain.model.Phonetic @JsonClass(generateAdapter = true) data class PhoneticDto( val id: Long? = null, val entryId: Long? = null, val text: String? = null, val audio: String? = null, val sourceUrl: String? = null, val license: LicenseDto? = null ) { fun domain() = Phonetic( id = id, entryId = entryId, text = text, audio = audio, sourceUrl = sourceUrl, license = license?.domain() ) } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/repository/local/BaseRoomRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * BaseRoomRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * BaseRoomRepository.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.repository.local import io.github.yamin8000.owl.search.data.datasource.local.dao.AdvancedDao import io.github.yamin8000.owl.search.domain.repository.local.util.BaseRepository abstract class BaseRoomRepository( private val dao: AdvancedDao ) : BaseRepository { override suspend fun remove(item: T): Int { val entity = findEntity(item) return if (entity != null) dao.delete(entity) else -1 } override suspend fun find(id: Long): T? { return mapToDomain(dao.find(id)) } override suspend fun all(): List { return dao.all().mapNotNull { mapToDomain(it) } } abstract suspend fun mapToDomain(item: E?): T? abstract suspend fun findEntity(item: T): E? } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/repository/local/DefinitionRoomRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * DefinitionRoomRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * DefinitionRoomRepository.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.repository.local import io.github.yamin8000.owl.common.util.DateTimeUtils.epoch import io.github.yamin8000.owl.search.data.datasource.local.dao.DAOs import io.github.yamin8000.owl.search.data.datasource.local.entity.DefinitionEntity import io.github.yamin8000.owl.search.domain.model.Definition import io.github.yamin8000.owl.search.domain.repository.local.DefinitionRepository class DefinitionRoomRepository( private val definitionDao: DAOs.DefinitionDao, private val antonymDao: DAOs.AntonymDao, private val synonymDao: DAOs.SynonymDao ) : DefinitionRepository, BaseRoomRepository(definitionDao) { override suspend fun findAllByMeaningId(meaningId: Long): List { return definitionDao.where("meaningId", meaningId).mapNotNull { mapToDomain(it) } } override suspend fun mapToDomain(item: DefinitionEntity?): Definition? { return if (item != null) { Definition( definition = item.definition, example = item.example, antonyms = antonymDao.where("definitionId", item.id).map { it.value }, synonyms = synonymDao.where("definitionId", item.id).map { it.value }, meaningId = item.meaningId, id = item.id ) } else null } override suspend fun findEntity(item: Definition): DefinitionEntity? { return if (item.id != null) definitionDao.find(item.id) else null } override suspend fun add(item: Definition): Long { return if (item.meaningId != null) { definitionDao.insert( DefinitionEntity( definition = item.definition, example = item.example, createdAt = epoch(), meaningId = item.meaningId ) ) } else -1 } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/repository/local/EntryRoomRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * EntryRoomRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * EntryRoomRepository.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.repository.local import io.github.yamin8000.owl.common.util.StringUtils.sanitizeWord import io.github.yamin8000.owl.common.util.DateTimeUtils.epoch import io.github.yamin8000.owl.search.data.datasource.local.dao.DAOs import io.github.yamin8000.owl.search.data.datasource.local.entity.EntryEntity import io.github.yamin8000.owl.search.domain.model.Entry import io.github.yamin8000.owl.search.domain.repository.local.EntryRepository import io.github.yamin8000.owl.search.domain.repository.local.MeaningRepository import io.github.yamin8000.owl.search.domain.repository.local.PhoneticRepository import kotlinx.collections.immutable.toImmutableList class EntryRoomRepository( private val dao: DAOs.EntryDao, private val phoneticRepository: PhoneticRepository, private val meaningRepository: MeaningRepository ) : EntryRepository, BaseRoomRepository(dao) { override suspend fun add(item: Entry): Long { return dao.insert( EntryEntity( word = item.word, createdAt = epoch() ) ) } override suspend fun findByTerm(term: String): Entry? { return mapToDomain(dao.where("word", sanitizeWord(term)).firstOrNull()) } override suspend fun mapToDomain(item: EntryEntity?): Entry? { return if (item != null) { Entry( id = item.id, word = item.word, phonetics = phoneticRepository.findAllByEntryId(item.id).toImmutableList(), meanings = meaningRepository.findAllByEntryId(item.id).toImmutableList() ) } else null } override suspend fun findEntity(item: Entry): EntryEntity? { return if (item.id != null) dao.find(item.id) else null } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/repository/local/MeaningRoomRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * MeaningRoomRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * MeaningRoomRepository.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.repository.local import io.github.yamin8000.owl.common.util.DateTimeUtils.epoch import io.github.yamin8000.owl.search.data.datasource.local.dao.DAOs import io.github.yamin8000.owl.search.data.datasource.local.entity.MeaningEntity import io.github.yamin8000.owl.search.domain.model.Meaning import io.github.yamin8000.owl.search.domain.repository.local.DefinitionRepository import io.github.yamin8000.owl.search.domain.repository.local.MeaningRepository import io.github.yamin8000.owl.search.domain.repository.local.util.HasEntry import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList class MeaningRoomRepository( private val meaningDao: DAOs.MeaningDao, private val definitionRepository: DefinitionRepository ) : MeaningRepository, BaseRoomRepository(meaningDao), HasEntry { override suspend fun findAllByEntryId(entryId: Long): List { return meaningDao.where("entryId", entryId).mapNotNull { mapToDomain(it) } } override suspend fun mapToDomain(item: MeaningEntity?): Meaning? { return if (item != null) { Meaning( id = item.id, entryId = item.entryId, partOfSpeech = item.partOfSpeech, definitions = definitionRepository.findAllByMeaningId(item.id).toImmutableList(), synonyms = persistentListOf(), antonyms = persistentListOf() ) } else null } override suspend fun findEntity(item: Meaning): MeaningEntity? { return if (item.id != null) meaningDao.find(item.id) else null } override suspend fun add(item: Meaning): Long { return if (item.entryId != null) { meaningDao.insert( MeaningEntity( entryId = item.entryId, partOfSpeech = item.partOfSpeech, createdAt = epoch(), ) ) } else -1 } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/repository/local/PhoneticRoomRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * PhoneticRoomRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * PhoneticRoomRepository.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.repository.local import io.github.yamin8000.owl.common.util.DateTimeUtils.epoch import io.github.yamin8000.owl.search.data.datasource.local.dao.DAOs import io.github.yamin8000.owl.search.data.datasource.local.entity.PhoneticEntity import io.github.yamin8000.owl.search.domain.model.Phonetic import io.github.yamin8000.owl.search.domain.repository.local.PhoneticRepository class PhoneticRoomRepository( private val phoneticDao: DAOs.PhoneticDao ) : PhoneticRepository, BaseRoomRepository(phoneticDao) { override suspend fun findAllByEntryId(entryId: Long): List { return phoneticDao.where("entryId", entryId).mapNotNull { mapToDomain(it) } } override suspend fun mapToDomain(item: PhoneticEntity?): Phonetic? { return if (item != null) { Phonetic( text = item.value, entryId = item.entryId, id = item.id ) } else null } override suspend fun findEntity(item: Phonetic): PhoneticEntity? { return if (item.id != null) phoneticDao.find(item.id) else null } override suspend fun add(item: Phonetic): Long { return if (item.entryId != null) { phoneticDao.insert( PhoneticEntity( value = item.text, entryId = item.entryId, createdAt = epoch() ) ) } else -1 } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/repository/local/TermRoomRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * TermRoomRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * TermRoomRepository.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.repository.local import io.github.yamin8000.owl.common.util.StringUtils.sanitizeWord import io.github.yamin8000.owl.common.util.DateTimeUtils.epoch import io.github.yamin8000.owl.search.data.datasource.local.dao.DAOs import io.github.yamin8000.owl.search.data.datasource.local.entity.TermEntity import io.github.yamin8000.owl.search.domain.repository.local.TermRepository class TermRoomRepository( private val dao: DAOs.TermDao ) : TermRepository, BaseRoomRepository(dao) { override suspend fun mapToDomain(item: TermEntity?): String? { return item?.word } override suspend fun findEntity(item: String): TermEntity? { return dao.where("word", sanitizeWord(item)).firstOrNull() } override suspend fun add(item: String): Long { return if (dao.where("word", sanitizeWord(item)).firstOrNull() == null) { dao.insert( TermEntity( word = item, createdAt = epoch() ) ) } else -1 } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/data/repository/remote/FreeDictionaryRetrofitApiRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * FreeDictionaryRetrofitApiRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * FreeDictionaryRetrofitApiRepository.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.data.repository.remote import io.github.yamin8000.owl.search.data.datasource.remote.FreeDictionaryAPI import io.github.yamin8000.owl.search.domain.model.Entry import io.github.yamin8000.owl.search.domain.repository.remote.FreeDictionaryApiRepository import javax.inject.Inject class FreeDictionaryRetrofitApiRepository @Inject constructor( private val api: FreeDictionaryAPI ) : FreeDictionaryApiRepository { override suspend fun searchWord(word: String): List { return api.search(word).map { it.domain() } } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/di/SearchDb.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * HomeDb.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * HomeDb.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.di import android.app.Application import androidx.room.Room import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import io.github.yamin8000.owl.search.data.datasource.local.AppDatabase import io.github.yamin8000.owl.search.data.datasource.local.MIGRATION_7_8 import io.github.yamin8000.owl.search.data.datasource.local.dao.DAOs import io.github.yamin8000.owl.search.data.repository.local.DefinitionRoomRepository import io.github.yamin8000.owl.search.data.repository.local.EntryRoomRepository import io.github.yamin8000.owl.search.data.repository.local.MeaningRoomRepository import io.github.yamin8000.owl.search.data.repository.local.PhoneticRoomRepository import io.github.yamin8000.owl.search.data.repository.local.TermRoomRepository import io.github.yamin8000.owl.search.domain.repository.local.DefinitionRepository import io.github.yamin8000.owl.search.domain.repository.local.EntryRepository import io.github.yamin8000.owl.search.domain.repository.local.MeaningRepository import io.github.yamin8000.owl.search.domain.repository.local.PhoneticRepository import io.github.yamin8000.owl.search.domain.repository.local.TermRepository import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object SearchDb { @Provides @Singleton fun providesDb(app: Application): AppDatabase { return Room.databaseBuilder(app, AppDatabase::class.java, "db") .addMigrations(MIGRATION_7_8) .build() } @Provides @Singleton fun providesEntryDao(app: AppDatabase): DAOs.EntryDao { return app.entryDao() } @Provides @Singleton fun providesEntryRepository( entryDao: DAOs.EntryDao, phoneticRepository: PhoneticRepository, meaningRepository: MeaningRepository ): EntryRepository = EntryRoomRepository( dao = entryDao, phoneticRepository = phoneticRepository, meaningRepository = meaningRepository ) @Provides @Singleton fun providesTermDao(app: AppDatabase): DAOs.TermDao { return app.termDao() } @Provides @Singleton fun providesTermRepository(dao: DAOs.TermDao): TermRepository { return TermRoomRepository(dao) } @Provides @Singleton fun providesDefinitionDao(app: AppDatabase): DAOs.DefinitionDao { return app.definitionDao() } @Provides @Singleton fun providesSynonymDao(app: AppDatabase): DAOs.SynonymDao { return app.synonymDao() } @Provides @Singleton fun providesAntonymDao(app: AppDatabase): DAOs.AntonymDao { return app.antonymDao() } @Provides @Singleton fun providesDefinitionRepository( definitionDao: DAOs.DefinitionDao, synonymDao: DAOs.SynonymDao, antonymDao: DAOs.AntonymDao ): DefinitionRepository = DefinitionRoomRepository( definitionDao = definitionDao, antonymDao = antonymDao, synonymDao = synonymDao ) @Provides @Singleton fun providesMeaningDao(app: AppDatabase): DAOs.MeaningDao { return app.meaningDao() } @Provides @Singleton fun providesMeaningRepository( meaningDao: DAOs.MeaningDao, definitionRepository: DefinitionRepository ): MeaningRepository = MeaningRoomRepository( meaningDao = meaningDao, definitionRepository = definitionRepository ) @Provides @Singleton fun providesPhoneticDao(app: AppDatabase): DAOs.PhoneticDao { return app.phoneticDao() } @Provides @Singleton fun providesPhoneticRepository(dao: DAOs.PhoneticDao): PhoneticRepository { return PhoneticRoomRepository(dao) } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/di/SearchUseCases.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * SearchUseCases.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * SearchUseCases.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.di import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import io.github.yamin8000.owl.search.domain.repository.local.DefinitionRepository import io.github.yamin8000.owl.search.domain.repository.local.EntryRepository import io.github.yamin8000.owl.search.domain.repository.local.MeaningRepository import io.github.yamin8000.owl.search.domain.repository.local.PhoneticRepository import io.github.yamin8000.owl.search.domain.repository.local.TermRepository import io.github.yamin8000.owl.search.domain.usecase.CacheWord import io.github.yamin8000.owl.search.domain.usecase.CacheWordData import io.github.yamin8000.owl.search.domain.usecase.GetCachedWord import io.github.yamin8000.owl.search.domain.usecase.WordCacheUseCases import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object SearchUseCases { @Provides @Singleton fun providesWordCacheUseCases( entryRepository: EntryRepository, phoneticRepository: PhoneticRepository, meaningRepository: MeaningRepository, definitionRepository: DefinitionRepository, termRepository: TermRepository ) = WordCacheUseCases( getCachedWord = GetCachedWord(entryRepository), cacheWord = CacheWord( entryRepository = entryRepository, meaningRepository = meaningRepository, definitionRepository = definitionRepository, phoneticRepository = phoneticRepository ), cacheWordData = CacheWordData(termRepository) ) } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/di/SearchWeb.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * SearchWeb.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * SearchWeb.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.di import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import io.github.yamin8000.owl.search.data.datasource.remote.FreeDictionaryAPI import io.github.yamin8000.owl.search.data.repository.remote.FreeDictionaryRetrofitApiRepository import io.github.yamin8000.owl.search.domain.repository.remote.FreeDictionaryApiRepository import io.github.yamin8000.owl.search.domain.usecase.SearchFreeDictionary import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import retrofit2.create import javax.inject.Qualifier import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object SearchWeb { @Qualifier annotation class SearchModule @SearchModule @Provides @Singleton fun providesRetrofit(): Retrofit { val baseUrl = "https://api.dictionaryapi.dev/api/v2/" return Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(MoshiConverterFactory.create()) .build() } @Provides @Singleton fun providesFreeDictionaryApi( @SearchModule retrofit: Retrofit ): FreeDictionaryAPI { return retrofit.create() } @Provides @Singleton fun providesFreeDictionaryApiRepository(api: FreeDictionaryAPI): FreeDictionaryApiRepository { return FreeDictionaryRetrofitApiRepository(api) } @Provides @Singleton fun providesFreeDictionaryApiUseCase(repository: FreeDictionaryApiRepository): SearchFreeDictionary { return SearchFreeDictionary(repository) } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/model/Definition.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * Definition.kt Copyrighted by Yamin Siahmargooei at 2025/9/17 * Definition.kt Last modified at 2025/9/17 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.model import kotlin.random.Random import kotlin.random.nextInt data class Definition( val definition: String, val example: String?, val synonyms: List, val antonyms: List, val meaningId: Long? = null, val id: Long? = null ) { companion object { fun mock() = Definition( definition = "Definition", example = "Example", synonyms = buildList { repeat(Random.nextInt(0..3)) { add("Synonym") } }, antonyms = buildList { repeat(Random.nextInt(0..3)) { add("Antonym") } } ) fun mockList(n: Int = Random.nextInt(1..5)) = buildList { repeat(n) { add(mock()) } } } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/model/Entry.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * Entry.kt Copyrighted by Yamin Siahmargooei at 2025/9/17 * Entry.kt Last modified at 2025/9/17 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.model import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList data class Entry( val word: String, val phonetics: ImmutableList, val meanings: ImmutableList, val license: License? = null, val sourceUrls: ImmutableList? = null, val id: Long? = null ) { companion object { fun mock() = Entry( word = "word", phonetics = persistentListOf(), meanings = Meaning.mockList().toImmutableList(), license = null, sourceUrls = persistentListOf() ) } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/model/License.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * License.kt Copyrighted by Yamin Siahmargooei at 2025/9/17 * License.kt Last modified at 2025/9/17 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.model data class License( val name: String, val url: String ) ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/model/Meaning.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * Meaning.kt Copyrighted by Yamin Siahmargooei at 2025/9/17 * Meaning.kt Last modified at 2025/9/17 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.model import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlin.random.Random import kotlin.random.nextInt data class Meaning( val partOfSpeech: String, val definitions: ImmutableList, val synonyms: ImmutableList, val antonyms: ImmutableList, val entryId: Long? = null, val id: Long? = null ) { companion object { fun mock() = Meaning( partOfSpeech = "partOfSpeech", definitions = Definition.mockList().toImmutableList(), synonyms = buildList { repeat(Random.nextInt(0..3)) { add("Synonym") } }.toImmutableList(), antonyms = buildList { repeat(Random.nextInt(0..3)) { add("Antonym") } }.toImmutableList() ) fun mockList(n: Int = Random.nextInt(1..5)) = buildList { repeat(n) { add(mock()) } } } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/model/Phonetic.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * Phonetic.kt Copyrighted by Yamin Siahmargooei at 2025/9/17 * Phonetic.kt Last modified at 2025/9/17 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.model data class Phonetic( val text: String? = null, val audio: String? = null, val sourceUrl: String? = null, val license: License? = null, val entryId: Long? = null, val id: Long? = null ) ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/repository/local/DefinitionRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * DefinitionRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * DefinitionRepository.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.repository.local import io.github.yamin8000.owl.search.domain.model.Definition import io.github.yamin8000.owl.search.domain.repository.local.util.BaseRepository interface DefinitionRepository : BaseRepository { suspend fun findAllByMeaningId(meaningId: Long): List } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/repository/local/EntryRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * EntryRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * EntryRepository.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.repository.local import io.github.yamin8000.owl.search.domain.model.Entry import io.github.yamin8000.owl.search.domain.repository.local.util.BaseRepository interface EntryRepository : BaseRepository { suspend fun findByTerm(term: String): Entry? } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/repository/local/MeaningRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * MeaningRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * MeaningRepository.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.repository.local import io.github.yamin8000.owl.search.domain.model.Meaning import io.github.yamin8000.owl.search.domain.repository.local.util.BaseRepository import io.github.yamin8000.owl.search.domain.repository.local.util.HasEntry interface MeaningRepository : BaseRepository, HasEntry ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/repository/local/PhoneticRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * PhoneticRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * PhoneticRepository.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.repository.local import io.github.yamin8000.owl.search.domain.model.Phonetic import io.github.yamin8000.owl.search.domain.repository.local.util.BaseRepository import io.github.yamin8000.owl.search.domain.repository.local.util.HasEntry interface PhoneticRepository : BaseRepository, HasEntry ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/repository/local/TermRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * TermRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * TermRepository.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.repository.local import io.github.yamin8000.owl.search.domain.repository.local.util.BaseRepository interface TermRepository : BaseRepository ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/repository/local/util/BaseRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * BaseRepisotry.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * BaseRepisotry.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.repository.local.util interface BaseRepository { suspend fun add(item: T): Long suspend fun remove(item: T): Int suspend fun find(id: Long): T? suspend fun all(): List } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/repository/local/util/HasEntry.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * HasEntry.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * HasEntry.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.repository.local.util interface HasEntry { suspend fun findAllByEntryId(entryId: Long): List } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/repository/remote/FreeDictionaryApiRepository.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * FreeDictionaryApiRepository.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * FreeDictionaryApiRepository.kt Last modified at 2024/9/5 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.repository.remote import io.github.yamin8000.owl.search.domain.model.Entry interface FreeDictionaryApiRepository { suspend fun searchWord(word: String): List } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/usecase/CacheWord.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * CacheWord.kt Copyrighted by Yamin Siahmargooei at 2024/8/30 * CacheWord.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.usecase import io.github.yamin8000.owl.search.domain.model.Entry import io.github.yamin8000.owl.search.domain.model.Meaning import io.github.yamin8000.owl.search.domain.repository.local.DefinitionRepository import io.github.yamin8000.owl.search.domain.repository.local.EntryRepository import io.github.yamin8000.owl.search.domain.repository.local.MeaningRepository import io.github.yamin8000.owl.search.domain.repository.local.PhoneticRepository import kotlinx.collections.immutable.persistentListOf class CacheWord( private val entryRepository: EntryRepository, private val meaningRepository: MeaningRepository, private val definitionRepository: DefinitionRepository, private val phoneticRepository: PhoneticRepository ) { suspend operator fun invoke(wordEntry: Entry) { val isNotCached = entryRepository.findByTerm(wordEntry.word.lowercase().trim()) == null if (isNotCached) { addWordEntryToDatabase(wordEntry) } } private suspend fun addWordEntryToDatabase(wordEntry: Entry) { val entryId = entryRepository.add(wordEntry) wordEntry.phonetics.forEach { phonetic -> phoneticRepository.add(phonetic.copy(entryId = entryId)) } wordEntry.meanings.forEach { (partOfSpeech, definitions, _, _) -> val meaningId = meaningRepository.add( Meaning( entryId = entryId, partOfSpeech = partOfSpeech, definitions = persistentListOf(), antonyms = persistentListOf(), synonyms = persistentListOf() ) ) definitions.forEach { definition -> definitionRepository.add(definition.copy(meaningId = meaningId)) } } } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/usecase/CacheWordData.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * CacheWordData.kt Copyrighted by Yamin Siahmargooei at 2024/8/30 * CacheWordData.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.usecase import io.github.yamin8000.owl.common.util.StringUtils.sanitizeWords import io.github.yamin8000.owl.search.domain.model.Entry import io.github.yamin8000.owl.search.domain.model.Meaning import io.github.yamin8000.owl.search.domain.repository.local.TermRepository class CacheWordData( private val termRepository: TermRepository ) { suspend operator fun invoke(wordEntry: Entry) { val oldData = termRepository.all().toSet() var newData = extractDataFromEntry(wordEntry.meanings) if (!oldData.contains(wordEntry.word)) { newData.add(wordEntry.word) } newData = sanitizeWords(newData).filter { it !in oldData }.toMutableSet() addWordDataToCache(newData) } private suspend fun addWordDataToCache(newData: Set) { newData.forEach { item -> termRepository.add(item) } } private fun extractDataFromEntry( meanings: List ) = meanings.asSequence() .flatMap { (partOfSpeech, definitions, _, _) -> listOf(partOfSpeech) .plus(definitions.map { it.definition }) .plus(definitions.map { it.example }) .plus(definitions.flatMap { it.synonyms }) .plus(definitions.flatMap { it.antonyms }) }.filterNotNull() .flatMap { it.split(Regex("\\s+")) } .map { it.trim() } .toMutableSet() } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/usecase/GetCachedWord.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * GetCachedWord.kt Copyrighted by Yamin Siahmargooei at 2024/8/25 * GetCachedWord.kt Last modified at 2024/8/25 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.usecase import io.github.yamin8000.owl.search.domain.model.Entry import io.github.yamin8000.owl.search.domain.repository.local.EntryRepository class GetCachedWord( private val entryRepository: EntryRepository ) { suspend operator fun invoke(term: String): Entry? { return entryRepository.findByTerm(term) } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/usecase/SearchFreeDictionary.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.feature_home.main * SearchWordUseCase.kt Copyrighted by Yamin Siahmargooei at 2024/8/18 * SearchWordUseCase.kt Last modified at 2024/8/18 * This file is part of freeDictionaryApp/freeDictionaryApp.feature_home.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.feature_home.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.feature_home.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.usecase import io.github.yamin8000.owl.search.domain.model.Entry import io.github.yamin8000.owl.search.domain.repository.remote.FreeDictionaryApiRepository class SearchFreeDictionary( private val repository: FreeDictionaryApiRepository ) { suspend operator fun invoke(word: String): List { return repository.searchWord(word) } } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/domain/usecase/WordCacheUseCases.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * WordCacheUseCases.kt Copyrighted by Yamin Siahmargooei at 2024/9/5 * WordCacheUseCases.kt Last modified at 2024/8/30 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.domain.usecase data class WordCacheUseCases( val getCachedWord: GetCachedWord, val cacheWord: CacheWord, val cacheWordData: CacheWordData ) ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/ui/components/MeaningCard.kt ================================================ package io.github.yamin8000.owl.search.ui.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.twotone.TextSnippet import androidx.compose.material3.OutlinedCard import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.datasource.LoremIpsum import io.github.yamin8000.owl.common.ui.components.SpeakableRippleTextWithIcon import io.github.yamin8000.owl.common.ui.theme.DefaultCutShape import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.common.ui.theme.defaultGradientBorder import io.github.yamin8000.owl.search.domain.model.Meaning import io.github.yamin8000.owl.search.ui.components.texts.WordDefinitionText import io.github.yamin8000.owl.search.ui.components.texts.WordExampleText import io.github.yamin8000.owl.search.ui.components.texts.WordTypeText import io.github.yamin8000.owl.strings.R @MyPreview @Composable private fun Preview() { PreviewTheme { Column( modifier = Modifier.padding(Sizes.Large), content = { MeaningCard( word = LoremIpsum(1).values.first(), meaning = Meaning.mock(), onWordChipClick = {} ) } ) } } @Composable fun MeaningCard( word: String, meaning: Meaning, modifier: Modifier = Modifier, onWordChipClick: ((String) -> Unit)? = null ) { OutlinedCard( shape = DefaultCutShape, modifier = modifier.fillMaxWidth(), border = defaultGradientBorder(), content = { Column( modifier = Modifier.padding(Sizes.Large), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(Sizes.Large, Alignment.CenterVertically), content = { WordTypeText( type = meaning.partOfSpeech, onDoubleClick = onWordChipClick ) meaning.definitions.forEach { (definition, example, synonyms, antonyms) -> WordDefinitionText( word = word, definition = definition, onDoubleClick = onWordChipClick ) if (example != null) { WordExampleText( word = word, example = example, onDoubleClick = onWordChipClick ) } antonyms.forEach { antonym -> SpeakableRippleTextWithIcon( text = antonym, imageVector = Icons.AutoMirrored.TwoTone.TextSnippet, title = stringResource(R.string.antonym), ) } synonyms.forEach { synonym -> SpeakableRippleTextWithIcon( text = synonym, imageVector = Icons.AutoMirrored.TwoTone.TextSnippet, title = stringResource(R.string.synonym), ) } } } ) } ) } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/ui/components/WordCard.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * WordCard.kt Copyrighted by Yamin Siahmargooei at 2025/9/8 * WordCard.kt Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.ui.components import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Favorite import androidx.compose.material.icons.twotone.Share import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedCard import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.github.yamin8000.owl.common.ui.components.ClickableIcon import io.github.yamin8000.owl.common.ui.theme.DefaultCutShape import io.github.yamin8000.owl.common.ui.theme.MyPreview import io.github.yamin8000.owl.common.ui.theme.PreviewTheme import io.github.yamin8000.owl.common.ui.theme.Sizes import io.github.yamin8000.owl.search.ui.components.texts.PronunciationText import io.github.yamin8000.owl.search.ui.components.texts.WordText import io.github.yamin8000.owl.strings.R @MyPreview @Composable private fun Preview() { PreviewTheme { Column( modifier = Modifier.padding(Sizes.Large), verticalArrangement = Arrangement.spacedBy(Sizes.Large), content = { WordCard( word = "Word", pronunciation = "Pronunciation", onShareWord = {}, onAddToFavourite = {} ) } ) } } @Composable fun WordCard( word: String, pronunciation: String?, modifier: Modifier = Modifier, onAddToFavourite: (() -> Unit)? = null, onShareWord: (() -> Unit)? = null ) { OutlinedCard( modifier = modifier, shape = DefaultCutShape, border = BorderStroke(Sizes.xxSmall, MaterialTheme.colorScheme.tertiary), content = { Row( modifier = Modifier.padding(Sizes.Large), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy( Sizes.Large, Alignment.CenterHorizontally ), content = { Column( modifier = Modifier.weight(3f), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy( Sizes.Medium, Alignment.CenterVertically ), content = { WordText( word = word ) if (!pronunciation.isNullOrBlank()) { PronunciationText( word = word, pronunciation = pronunciation, ) } } ) Column( modifier = Modifier.weight(1f), horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.spacedBy( Sizes.Medium, Alignment.CenterVertically ), content = { if (onAddToFavourite != null) { ClickableIcon( imageVector = Icons.TwoTone.Favorite, contentDescription = stringResource(R.string.favourites), onClick = onAddToFavourite ) } if (onShareWord != null) { ClickableIcon( imageVector = Icons.TwoTone.Share, contentDescription = stringResource(R.string.share), onClick = onShareWord ) } } ) } ) } ) } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/ui/components/texts/PronunciationText.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * PronunciationText.kt Copyrighted by Yamin Siahmargooei at 2025/9/8 * PronunciationText.kt Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.ui.components.texts import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.RecordVoiceOver import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import io.github.yamin8000.owl.common.ui.components.SpeakableRippleTextWithIcon @Composable internal fun PronunciationText( pronunciation: String, word: String, modifier: Modifier = Modifier, ) { SpeakableRippleTextWithIcon( modifier = modifier, text = pronunciation, ttsText = word, imageVector = Icons.TwoTone.RecordVoiceOver, ) } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/ui/components/texts/WordDefinitionText.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * WordDefinitionText.kt Copyrighted by Yamin Siahmargooei at 2025/9/8 * WordDefinitionText.kt Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.ui.components.texts import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.twotone.ShortText import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.github.yamin8000.owl.common.ui.components.HighlightText import io.github.yamin8000.owl.common.ui.components.SpeakableRippleTextWithIcon import io.github.yamin8000.owl.strings.R @Composable internal fun WordDefinitionText( word: String, definition: String, modifier: Modifier = Modifier, onDoubleClick: ((String) -> Unit)? = null ) { SpeakableRippleTextWithIcon( modifier = modifier, text = definition, imageVector = Icons.AutoMirrored.TwoTone.ShortText, title = stringResource(R.string.definition), onDoubleClick = onDoubleClick, content = { HighlightText( fullText = definition, highlightedText = word ) } ) } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/ui/components/texts/WordExampleText.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * WordExampleText.kt Copyrighted by Yamin Siahmargooei at 2025/9/8 * WordExampleText.kt Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.ui.components.texts import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.twotone.TextSnippet import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.github.yamin8000.owl.common.ui.components.HighlightText import io.github.yamin8000.owl.common.ui.components.SpeakableRippleTextWithIcon import io.github.yamin8000.owl.strings.R @Composable internal fun WordExampleText( word: String, example: String, modifier: Modifier = Modifier, onDoubleClick: ((String) -> Unit)? = null ) { SpeakableRippleTextWithIcon( modifier = modifier, text = example, imageVector = Icons.AutoMirrored.TwoTone.TextSnippet, title = stringResource(R.string.example), content = { HighlightText(fullText = example, highlightedText = word) }, onDoubleClick = onDoubleClick, ) } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/ui/components/texts/WordText.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * WordText.kt Copyrighted by Yamin Siahmargooei at 2025/9/8 * WordText.kt Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.ui.components.texts import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.twotone.ShortText import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import io.github.yamin8000.owl.common.ui.components.SpeakableRippleTextWithIcon @Composable internal fun WordText( word: String, modifier: Modifier = Modifier ) { SpeakableRippleTextWithIcon( modifier = modifier, text = word, imageVector = Icons.AutoMirrored.TwoTone.ShortText, ) } ================================================ FILE: search/src/main/java/io/github/yamin8000/owl/search/ui/components/texts/WordTypeText.kt ================================================ /* * freeDictionaryApp/freeDictionaryApp.search.main * WordTypeText.kt Copyrighted by Yamin Siahmargooei at 2025/9/8 * WordTypeText.kt Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.search.main. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.search.main is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.search.main is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ package io.github.yamin8000.owl.search.ui.components.texts import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Category import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import io.github.yamin8000.owl.common.ui.components.SpeakableRippleTextWithIcon import io.github.yamin8000.owl.strings.R @Composable internal fun WordTypeText( type: String, modifier: Modifier = Modifier, onDoubleClick: ((String) -> Unit)? = null ) { SpeakableRippleTextWithIcon( modifier = modifier, text = type, imageVector = Icons.TwoTone.Category, title = stringResource(R.string.type), onDoubleClick = onDoubleClick, ) } ================================================ FILE: settings.gradle.kts ================================================ /* * freeDictionaryApp/freeDictionaryApp * settings.gradle.kts Copyrighted by Yamin Siahmargooei at 2024/5/9 * settings.gradle.kts Last modified at 2024/3/23 * This file is part of freeDictionaryApp/freeDictionaryApp. * Copyright (C) 2024 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ pluginManagement { repositories { gradlePluginPortal() google() mavenCentral() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } } rootProject.name = "freeDictionaryApp" include(":app") include(":strings") include(":common") include(":datastore") include(":feature_home") include(":feature_settings") include(":feature_history") include(":feature_favourites") include(":feature_overlay") include(":search") include(":feature_about") ================================================ FILE: strings/.gitignore ================================================ /build ================================================ FILE: strings/build.gradle.kts ================================================ /* * freeDictionaryApp/freeDictionaryApp.strings * build.gradle.kts Copyrighted by Yamin Siahmargooei at 2025/9/8 * build.gradle.kts Last modified at 2025/8/31 * This file is part of freeDictionaryApp/freeDictionaryApp.strings. * Copyright (C) 2025 Yamin Siahmargooei * * freeDictionaryApp/freeDictionaryApp.strings is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * freeDictionaryApp/freeDictionaryApp.strings is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with freeDictionaryApp. If not, see . */ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) } android { namespace = "io.github.yamin8000.owl.strings" compileSdk = 36 defaultConfig { minSdk = 23 } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_17 } } } dependencies { } ================================================ FILE: strings/src/main/AndroidManifest.xml ================================================ ================================================ FILE: strings/src/main/res/values/strings.xml ================================================ freeDictionary Search Enter a word to search Favourite Words Search History Random Word No connection to the server. Data maybe loaded from the cache. Server is busy, try again! No definition for the word found. Authentication Error Text copied. This is a free application under GNU license, see more: GPLv3 License Logo freeDictionaryApp, An English to English Dictionary, based on: https://github.com/yamin8000/freeDictionaryApp https://raw.githubusercontent.com/yamin8000/freeDictionaryApp/master/LICENSE https://dictionaryapi.dev/ Added to favourites Clear Unexpected error! Nothing is entered. Share This text is generated using freeDictionaryApp Clear All Settings Theme On Android 12+ your app theme colors are based on your home screen background System Light Dark Text-to-Speech Language About Installed Version %s Example Definition Type This text is extracted from the Free Dictionary API General Vibrate on scroll Cancel Delete Increase your volume Antonym Synonym Blank startup search OLED/Darker System OLED/Darker Yes No Back Do you want to remove all? Cancelled Close More in app… Latest version: %1$s Contributions Graph ================================================ FILE: strings/src/main/res/values-fa/strings.xml ================================================ جستجو واژه‌ای برای جستجو بنویسید واژه‌های برگزیده جستجوهای پیشین واژه تصادفی خطا در برقراری ارتباط با اینترنت، خطا ممکن است به خاطر محدودیت های اینترنت باشد سرور برنامه تحت فشار است، لطفا چند دقیقه دیگر تلاش کنید توضیحی برای این کلمه یافت نشد خطای احراز هویت با سرور برنامه متن کپی شد این یک برنامه آزاد طبق پروانه گنو است که توضیحات بیشتر آن در ادامه آورده شده است لوگو پروانه برنامه GPLv3 فرهنگ واژگان انگلیسی به انگلیسی جغد، برگرفته از فرهنگ واژگان به برگزیده‌ها اضافه شد پاک کردن خطای پیش بینی نشده عبارتی وارد نشده اشتراک گذاری این متن توسط برنامه جغدک تولید شده پاک کردن همه تنظیمات زمینه در نسخه های اندروید 12 به بعد زمینه برنامه بر اساس رنگ قالب تصویر پس زمینه گوشی شما تعیین می شود هماهنگ سیستم روشن تاریک زبان متن به گفتار درباره نگارش نصب شده %s نمونه معنی نوع عمومی لرزش در زمان پیمایش لیست لغو پاک کردن صدای بلندگو را زیاد کنید. متضاد مترادف Blank startup search تاریک تر بله نه بازگشت آیا می‌خواهید همه را پاک کنید؟ لغو شد. بستن بیشتر در برنامه… جدیدترین نسخه: %1$s نمودار مشارکت‌کنندگان ================================================ FILE: strings/src/main/res/values-hi/strings.xml ================================================ खोज खोजने के लिए कोई शब्द दर्ज करें पसंदीदा शब्द खोज इतिहास यादृच्छिक शब्द सर्वर से कोई कनेक्शन नहीं है। डेटा कैश से लोड किया जा सकता है। सर्वर व्यस्त है, कृपया पुनः प्रयास करें! इस शब्द की कोई परिभाषा नहीं मिली। प्रमाणीकरण त्रुटि पाठ कॉपी किया गया। यह GNU लाइसेंस के अंतर्गत एक निःशुल्क एप्लिकेशन है, अधिक जानें: GPLv3 लाइसेंस लोगो अंग्रेज़ी-अंग्रेज़ी शब्दकोश पर आधारित: पसंदीदा में जोड़ा गया साफ़ करें अनपेक्षित त्रुटि! कोई शब्द दर्ज नहीं किया गया। साझा करें यह पाठ Owl ऐप का उपयोग करके बनाया गया है सभी साफ़ करें सेटिंग्स थीम Android 12 और उससे ऊपर पर, ऐप की थीम आपके वॉलपेपर के रंगों पर आधारित होती है सिस्टम हल्का गहरा पाठ-से-भाषण की भाषा एप्लिकेशन के बारे में संस्करण %s उदाहरण परिभाषा प्रकार सामान्य स्क्रॉल करते समय कंपन रद्द करें हटाएं अपनी वॉल्यूम बढ़ाएं विलोम पर्यायवाची रिक्त प्रारंभिक खोज OLED/गहरा हाँ नहीं वापस क्या आप सब हटाना चाहते हैं? रद्द किया गया बंद करें ऐप में और देखें… Latest version: %1$s Contributions Graph ================================================ FILE: strings/src/main/res/values-hu/strings.xml ================================================ Keresés Írj be egy szót a kereséshez Kedvenc Szavak Keresési Előzmények Véletlenszerű Szó Nincs kapcsolat a szerverrel. Az adatok előfordulhat, hogy a gyorsítótárból kerülnek betöltésre. A szerver elfoglalt, próbáld újra! Nem található definíció a szóra. Hitelesítési Hiba Szöveg másolva. Ez egy ingyenes applikáció a GNU licensz alatt, tudj meg többet: GPLv3 Licensz Logó Angol-Angol szótár alapján: Hozzáadva a kedvencekhez Kiürítés Váratlan hiba! Semmi sem került bevitelre. Megosztás Ezt a szöveget az Owl app generálta Mind kiürítése Beállítások Téma Android 12-n és afelett az alkalmazásod témája a háttérképed színei alapján készül el Rendszer Világos Sötét Szövegből-Beszéd Nyelve Az alkalmazásról Verzió %s Példa Definíció Típus Általános Rezgés görgetéskor Mégse Törlés Növeld meg a hangerődet Antonym Synonym Blank startup search OLED/Darker Igen Nem Back Do you want to remove all? Cancelled Close More in app… Latest version: %1$s Contributions Graph ================================================ FILE: strings/src/main/res/values-ja/strings.xml ================================================ 検索 単語を入力してください 好きな単語 検索履歴 ランダムワード サーバーに接続されていません。 データはキャッシュからロードされる場合があります。 サーバーがビジー状態です。もう一度お試しください! 言葉の定義が見つかりませんでした。 認証エラー テキストをコピーしました。 これはGNUライセンスに基づいた無料のアプリケーションです。詳細については次を参照してください: GPLv3 License Logo 英語から英語への辞書、以下をベースにしています。 お気に入りに追加されました 消去 予期しないエラーが発生しました! 何も入力されていません。 共有 このテキストはfreeDictionaryを使用して生成されています 全て消去 設定 テーマ Android 12以降では、アプリのテーマカラーはホーム画面の背景に基づきます システム ライト ダーク テキストの読み上げ言語 詳細 バージョン %s 例文 意味 タイプ 一般 スクロール時に振動 キャンセル 削除 音量を上げてください 対義語 同義語 起動時に空白で検索 OLED/Darker はい いいえ 戻る 本当に全て削除しますか? キャンセル Close More in app… Latest version: %1$s Contributions Graph