├── .github └── workflows │ └── build.yml ├── .gitignore ├── ISSUE_TEMPLATE.md ├── LICENSE.txt ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro ├── schemas │ ├── com.crossbowffs.quotelock.data.history.QuoteHistoryDatabase │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ └── 5.json │ ├── com.crossbowffs.quotelock.data.modules.collections.database.QuoteCollectionDatabase │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ └── 5.json │ ├── com.crossbowffs.quotelock.data.modules.custom.database.CustomQuoteDatabase │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4.json │ │ └── 5.json │ ├── com.crossbowffs.quotelock.data.modules.fortune.database.FortuneQuoteDatabase │ │ ├── 4.json │ │ └── 5.json │ └── com.crossbowffs.quotelock.data.modules.openai.OpenAIUsageDatabase │ │ └── 1.json └── src │ ├── androidTest │ ├── assets │ │ ├── Inter-VariableFont_slnt,wght.ttf │ │ ├── android_13_wallpaper_1_ytechb.webp │ │ └── fortune │ │ │ ├── art │ │ │ ├── computers │ │ │ ├── cookie │ │ │ ├── debian │ │ │ ├── definitions │ │ │ ├── disclaimer │ │ │ ├── drugs │ │ │ ├── education │ │ │ ├── ethnic │ │ │ ├── food │ │ │ ├── fortunes │ │ │ ├── goedel │ │ │ ├── humorists │ │ │ ├── kids │ │ │ ├── knghtbrd │ │ │ ├── law │ │ │ ├── linux │ │ │ ├── literature │ │ │ ├── love │ │ │ ├── magic │ │ │ ├── medicine │ │ │ ├── men-women │ │ │ ├── miscellaneous │ │ │ ├── news │ │ │ ├── paradoxum │ │ │ ├── people │ │ │ ├── perl │ │ │ ├── pets │ │ │ ├── platitudes │ │ │ ├── politics │ │ │ ├── pratchett │ │ │ ├── riddles │ │ │ ├── science │ │ │ ├── songs-poems │ │ │ ├── sports │ │ │ ├── startrek │ │ │ ├── tao │ │ │ ├── translate-me │ │ │ ├── wisdom │ │ │ ├── work │ │ │ └── zippy │ └── java │ │ └── com │ │ └── crossbowffs │ │ └── quotelock │ │ ├── CustomTestRunner.kt │ │ ├── app │ │ ├── ScreenshotTest.kt │ │ └── WidgetCaptureTest.kt │ │ └── data │ │ ├── QuoteDatabaseSamples.kt │ │ ├── history │ │ └── QuoteHistoryDatabaseTest.kt │ │ └── modules │ │ ├── collections │ │ └── database │ │ │ └── QuoteCollectionDatabaseTest.kt │ │ ├── custom │ │ └── database │ │ │ └── CustomQuoteDatabaseTest.kt │ │ ├── fortune │ │ └── database │ │ │ ├── FortuneConverterTest.kt │ │ │ └── FortuneQuoteDatabaseTest.kt │ │ └── wikiquote │ │ └── WikiquoteRepositoryTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── database │ │ │ └── fortune_quotes.db │ │ └── xposed_init │ ├── java │ │ └── com │ │ │ └── crossbowffs │ │ │ └── quotelock │ │ │ ├── account │ │ │ ├── SyncAccountManager.kt │ │ │ ├── authenticator │ │ │ │ ├── AuthenticationService.kt │ │ │ │ └── Authenticator.kt │ │ │ ├── google │ │ │ │ ├── GoogleAccountHelper.kt │ │ │ │ └── GoogleAccountManager.kt │ │ │ └── syncadapter │ │ │ │ ├── SyncAdapter.kt │ │ │ │ └── SyncService.kt │ │ │ ├── app │ │ │ ├── App.kt │ │ │ ├── CommonReceiver.kt │ │ │ ├── UiEvents.kt │ │ │ ├── about │ │ │ │ ├── AboutNavigation.kt │ │ │ │ ├── AboutPrefs.kt │ │ │ │ ├── AboutScreen.kt │ │ │ │ └── AboutViewModel.kt │ │ │ ├── collections │ │ │ │ ├── CollectionNavigation.kt │ │ │ │ ├── QuoteCollectionScreen.kt │ │ │ │ └── QuoteCollectionViewModel.kt │ │ │ ├── configs │ │ │ │ ├── ConfigsNavigations.kt │ │ │ │ ├── ConfigsViewModel.kt │ │ │ │ ├── brainyquote │ │ │ │ │ ├── BrainyQuoteNavigation.kt │ │ │ │ │ ├── BrainyQuotePrefKeys.kt │ │ │ │ │ └── BrainyQuoteScreen.kt │ │ │ │ ├── custom │ │ │ │ │ ├── CustomQuoteNavigation.kt │ │ │ │ │ ├── CustomQuoteScreen.kt │ │ │ │ │ └── CustomQuoteViewModel.kt │ │ │ │ ├── fortune │ │ │ │ │ ├── FortuneNavigation.kt │ │ │ │ │ ├── FortunePrefKeys.kt │ │ │ │ │ └── FortuneScreen.kt │ │ │ │ ├── hitokoto │ │ │ │ │ ├── HitkotoNavigation.kt │ │ │ │ │ ├── HitokotoPrefKeys.kt │ │ │ │ │ └── HitokotoScreen.kt │ │ │ │ ├── openai │ │ │ │ │ ├── OpenAIConfigsViewModel.kt │ │ │ │ │ ├── OpenAINavigation.kt │ │ │ │ │ ├── OpenAIPrefKeys.kt │ │ │ │ │ └── OpenAIScreen.kt │ │ │ │ └── wikiquote │ │ │ │ │ ├── WikiquoteNavigation.kt │ │ │ │ │ ├── WikiquotePrefKeys.kt │ │ │ │ │ ├── WikiquoteScreen.kt │ │ │ │ │ └── WikiquoteViewModel.kt │ │ │ ├── detail │ │ │ │ ├── DetailNavigation.kt │ │ │ │ └── jinrishici │ │ │ │ │ ├── DetailJinrishiciNavigation.kt │ │ │ │ │ ├── DetailJinrishiciScreen.kt │ │ │ │ │ └── DetailJinrishiciViewModel.kt │ │ │ ├── font │ │ │ │ ├── FontManagementNavigation.kt │ │ │ │ ├── FontManagementScreen.kt │ │ │ │ ├── FontManagementViewModel.kt │ │ │ │ └── FontManager.kt │ │ │ ├── history │ │ │ │ ├── HistoryNavigation.kt │ │ │ │ ├── QuoteHistoryScreen.kt │ │ │ │ └── QuoteHistoryViewModel.kt │ │ │ ├── lockscreen │ │ │ │ └── styles │ │ │ │ │ ├── LockscreenStylesNavigation.kt │ │ │ │ │ ├── LockscreenStylesScreen.kt │ │ │ │ │ ├── LockscreenStylesViewModel.kt │ │ │ │ │ ├── PreviewScreen.kt │ │ │ │ │ └── PreviewViewModel.kt │ │ │ ├── main │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MainActivityViewModel.kt │ │ │ │ ├── MainNavigation.kt │ │ │ │ ├── MainScreen.kt │ │ │ │ └── MainScreenViewModel.kt │ │ │ ├── quote │ │ │ │ ├── QuoteNavigation.kt │ │ │ │ ├── QuoteScreen.kt │ │ │ │ ├── QuoteViewModel.kt │ │ │ │ └── style │ │ │ │ │ ├── CardStylePopup.kt │ │ │ │ │ ├── CardStyleViewModel.kt │ │ │ │ │ ├── NumberButtonPicker.kt │ │ │ │ │ └── PopupFontStyleRow.kt │ │ │ ├── screenshot │ │ │ │ ├── FontAxisPreview.kt │ │ │ │ ├── Screenshot.kt │ │ │ │ ├── WidgetPreview.kt │ │ │ │ └── WidgetScreenshot.kt │ │ │ ├── settings │ │ │ │ ├── DarkModeNavigation.kt │ │ │ │ ├── DarkModeScreen.kt │ │ │ │ ├── DarkModeViewModel.kt │ │ │ │ ├── LanguageNavigation.kt │ │ │ │ ├── LanguageScreen.kt │ │ │ │ ├── SettingsNavigation.kt │ │ │ │ ├── SettingsScreen.kt │ │ │ │ └── SettingsViewModel.kt │ │ │ ├── share │ │ │ │ ├── ShareNavigation.kt │ │ │ │ ├── ShareScreen.kt │ │ │ │ └── ShareViewModel.kt │ │ │ └── widget │ │ │ │ └── QuoteGlanceWidget.kt │ │ │ ├── consts │ │ │ ├── PrefKeys.kt │ │ │ └── Urls.kt │ │ │ ├── data │ │ │ ├── AsyncResult.kt │ │ │ ├── CardStyleRepository.kt │ │ │ ├── ConfigurationRepository.kt │ │ │ ├── ShareRepository.kt │ │ │ ├── WidgetRepository.kt │ │ │ ├── api │ │ │ │ ├── AndroidString.kt │ │ │ │ ├── CardStyle.kt │ │ │ │ ├── GoogleAccount.kt │ │ │ │ ├── OpenAIConfigs.kt │ │ │ │ ├── QuoteConfigs.kt │ │ │ │ ├── QuoteContract.kt │ │ │ │ ├── QuoteData.kt │ │ │ │ ├── QuoteModule.kt │ │ │ │ ├── QuoteModuleData.kt │ │ │ │ ├── QuoteStyle.kt │ │ │ │ ├── QuoteViewData.kt │ │ │ │ ├── TextFontStyle.kt │ │ │ │ └── VersionData.kt │ │ │ ├── history │ │ │ │ ├── QuoteHistoryDatabase.kt │ │ │ │ └── QuoteHistoryRepository.kt │ │ │ ├── modules │ │ │ │ ├── ModuleNotFoundException.kt │ │ │ │ ├── Modules.kt │ │ │ │ ├── QuoteLocalSource.kt │ │ │ │ ├── QuoteRemoteSource.kt │ │ │ │ ├── QuoteRepository.kt │ │ │ │ ├── brainyquote │ │ │ │ │ └── BrainyQuoteQuoteModule.kt │ │ │ │ ├── collections │ │ │ │ │ ├── CollectionsQuoteModule.kt │ │ │ │ │ ├── QuoteCollectionRepository.kt │ │ │ │ │ ├── backup │ │ │ │ │ │ ├── CollectionLocalBackupSource.kt │ │ │ │ │ │ └── CollectionRemoteSyncSource.kt │ │ │ │ │ └── database │ │ │ │ │ │ └── QuoteCollectionDatabase.kt │ │ │ │ ├── custom │ │ │ │ │ ├── CustomQuoteModule.kt │ │ │ │ │ ├── CustomQuoteRepository.kt │ │ │ │ │ └── database │ │ │ │ │ │ └── CustomQuoteDatabase.kt │ │ │ │ ├── fortune │ │ │ │ │ ├── FortuneQuoteModule.kt │ │ │ │ │ └── database │ │ │ │ │ │ └── FortuneQuoteDatabase.kt │ │ │ │ ├── freakuotes │ │ │ │ │ └── FreakuotesQuoteModule.kt │ │ │ │ ├── hitokoto │ │ │ │ │ └── HitokotoQuoteModule.kt │ │ │ │ ├── jinrishici │ │ │ │ │ ├── JinrishiciQuoteModule.kt │ │ │ │ │ └── detail │ │ │ │ │ │ └── JinrishiciDetailData.kt │ │ │ │ ├── libquotes │ │ │ │ │ └── LibquotesQuoteModule.kt │ │ │ │ ├── natune │ │ │ │ │ └── NatuneQuoteModule.kt │ │ │ │ ├── openai │ │ │ │ │ ├── OpenAIDatabase.kt │ │ │ │ │ ├── OpenAIQuoteModule.kt │ │ │ │ │ ├── OpenAIRepository.kt │ │ │ │ │ ├── chat │ │ │ │ │ │ ├── OpenAIChatInput.kt │ │ │ │ │ │ ├── OpenAIChatResponse.kt │ │ │ │ │ │ ├── OpenAIModelsResponse.kt │ │ │ │ │ │ └── OpenAIQuote.kt │ │ │ │ │ └── geo │ │ │ │ │ │ ├── GeoData.kt │ │ │ │ │ │ └── OpenAITraceResponse.kt │ │ │ │ └── wikiquote │ │ │ │ │ ├── WikiquoteQuoteModule.kt │ │ │ │ │ └── WikiquoteRepository.kt │ │ │ └── version │ │ │ │ ├── VersionLocalSource.kt │ │ │ │ ├── VersionRemoteSource.kt │ │ │ │ └── VersionRepository.kt │ │ │ ├── di │ │ │ ├── DataModules.kt │ │ │ ├── DataStoreModules.kt │ │ │ ├── NetModules.kt │ │ │ └── QuoteModuleEntryPoint.kt │ │ │ ├── provider │ │ │ ├── ActionProvider.kt │ │ │ ├── PreferenceProvider.kt │ │ │ └── QuoteCollectionStubProvider.kt │ │ │ ├── ui │ │ │ ├── components │ │ │ │ ├── AppBars.kt │ │ │ │ ├── ContentAlpha.kt │ │ │ │ ├── Dialogs.kt │ │ │ │ ├── FontListItem.kt │ │ │ │ ├── ModifierExtension.kt │ │ │ │ ├── MultiSelectItems.kt │ │ │ │ ├── Popups.kt │ │ │ │ ├── PreferenceItems.kt │ │ │ │ ├── QuoteListItem.kt │ │ │ │ ├── RadioButtonItems.kt │ │ │ │ ├── SegmentedControl.kt │ │ │ │ ├── Shapes.kt │ │ │ │ ├── Snapshotable.kt │ │ │ │ └── TextToolbar.kt │ │ │ ├── navigation │ │ │ │ ├── QuoteNavHost.kt │ │ │ │ └── QuoteNavigationDestination.kt │ │ │ └── theme │ │ │ │ ├── Color.kt │ │ │ │ └── Theme.kt │ │ │ ├── utils │ │ │ ├── ByteUtils.kt │ │ │ ├── Coroutine.kt │ │ │ ├── DataStoreExtension.kt │ │ │ ├── DeviceUtils.kt │ │ │ ├── DpUtils.kt │ │ │ ├── FileSizeFormater.kt │ │ │ ├── FileUtils.kt │ │ │ ├── IOUtils.kt │ │ │ ├── InstallUtils.kt │ │ │ ├── Md5Utils.kt │ │ │ ├── ProcessUtils.kt │ │ │ ├── ReflectionUtils.kt │ │ │ ├── StringExt.kt │ │ │ ├── TextResize.kt │ │ │ ├── TypefaceUtils.kt │ │ │ ├── WorkUtils.kt │ │ │ ├── Xlog.kt │ │ │ └── XposedUtils.kt │ │ │ ├── worker │ │ │ ├── QuoteWorker.kt │ │ │ └── VersionWorker.kt │ │ │ └── xposed │ │ │ ├── LockscreenHook.kt │ │ │ ├── XSafeModuleResources.kt │ │ │ └── XposedUtilsHook.kt │ └── res │ │ ├── drawable-nodpi │ │ └── quote_widget_preview.webp │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-v33 │ │ └── ic_launcher_monochrome.xml │ │ ├── drawable-zh-rCN-nodpi │ │ └── quote_widget_preview.webp │ │ ├── drawable-zh-rTW-nodpi │ │ └── quote_widget_preview.webp │ │ ├── drawable │ │ ├── anim_star.xml │ │ ├── avd_star_selected_to_unselected.xml │ │ ├── avd_star_unselected_to_selected.xml │ │ ├── ic_format_decrease_segment_spacing_24_dp.xml │ │ ├── ic_format_increase_segment_spacing_24_dp.xml │ │ ├── ic_format_page_large_padding_24_dp.xml │ │ ├── ic_format_page_padding_24_dp.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_logo_libquotes.xml │ │ ├── ic_logo_openai.xml │ │ ├── ic_logo_wikiquote.xml │ │ ├── ic_quotelockx.xml │ │ ├── ic_round_refresh_24dp.xml │ │ ├── ic_round_star_24dp.xml │ │ ├── ic_round_star_border_24dp.xml │ │ ├── ic_text_style_24dp.xml │ │ ├── ic_variable_font_24dp.xml │ │ ├── ic_variable_italic_24dp.xml │ │ ├── ic_variable_weight_24dp.xml │ │ └── selector_star.xml │ │ ├── layout │ │ ├── quote_layout.xml │ │ ├── quote_widget_loading.xml │ │ └── quote_widget_preview.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-nodpi │ │ ├── ic_github_identicon.webp │ │ ├── ic_logo_brainyquote.webp │ │ ├── ic_logo_freakuotes.webp │ │ ├── ic_logo_hitokoto.webp │ │ ├── ic_logo_jinrishici.webp │ │ └── ic_logo_natune.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-zh-rCN │ │ ├── arrays.xml │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ ├── arrays.xml │ │ └── strings.xml │ │ ├── values │ │ ├── arrays.xml │ │ └── strings.xml │ │ ├── xml-v31 │ │ └── quote_widget.xml │ │ └── xml │ │ ├── authenticator.xml │ │ ├── file_provider_paths.xml │ │ ├── locales_config.xml │ │ ├── quote_widget.xml │ │ └── syncadapter.xml │ └── test │ └── java │ └── com │ └── crossbowffs │ └── quotelock │ └── data │ ├── api │ └── QuoteDataTest.kt │ └── modules │ └── jinrishici │ └── detail │ └── JinrishiciDetailDataTest.kt ├── build.gradle.kts ├── buildSrc ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── Configs.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── repos ├── com │ └── crossbowffs │ │ └── remotepreferences │ │ └── remotepreferences │ │ ├── 0.9 │ │ ├── remotepreferences-0.9-javadoc.jar │ │ ├── remotepreferences-0.9-javadoc.jar.asc │ │ ├── remotepreferences-0.9-sources.jar │ │ ├── remotepreferences-0.9-sources.jar.asc │ │ ├── remotepreferences-0.9.aar │ │ ├── remotepreferences-0.9.aar.asc │ │ ├── remotepreferences-0.9.module │ │ ├── remotepreferences-0.9.module.asc │ │ ├── remotepreferences-0.9.pom │ │ └── remotepreferences-0.9.pom.asc │ │ └── maven-metadata.xml └── de │ └── robv │ └── android │ └── xposed │ └── api │ ├── 53 │ ├── api-53-sources.jar │ ├── api-53-sources.jar.asc │ ├── api-53.jar │ ├── api-53.jar.asc │ ├── api-53.pom │ └── api-53.pom.asc │ ├── 81 │ ├── api-81-sources.jar │ ├── api-81-sources.jar.asc │ ├── api-81.jar │ ├── api-81.jar.asc │ ├── api-81.pom │ └── api-81.pom.asc │ ├── 82 │ ├── api-82-sources.jar │ ├── api-82-sources.jar.asc │ ├── api-82.jar │ ├── api-82.jar.asc │ ├── api-82.pom │ └── api-82.pom.asc │ └── maven-metadata.xml ├── screenshots ├── detail_page.png ├── screenshot.png ├── showcase.webp └── variable_font_showcase.webp └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | build/ 15 | 16 | # libraries 17 | libs/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Android Studio 23 | .idea/ 24 | *.iml 25 | 26 | # Gradle 27 | .gradle/ 28 | 29 | # Mac 30 | .DS_Store 31 | 32 | # Keystore 33 | *.jks -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Write your issue description here. 2 | 3 | --- 4 | 5 | **If you are submitting a bug report and do not include the following info, your issue will be ignored!** 6 | 7 | - Device ("Nexus 6P"): 8 | - Android version ("7.1.1"): 9 | - Xposed version ("53"): 10 | - QuoteLockX version ("2.0.0"): 11 | 12 | Please paste your Xposed logs (Xposed Installer -> Logs -> Menu -> Save to SD card) below: 13 | 14 | ``` 15 | Replace this line with your logs. Do not remove the backticks. 16 | ``` 17 | 18 | Thank you for helping us help you help us all. 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Andrew Sun (@crossbowffs) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -dontobfuscate 24 | -dontoptimize 25 | 26 | -keepattributes SourceFile,LineNumberTable 27 | 28 | # Xposed 29 | -keep class com.crossbowffs.quotelock.xposed.** {*;} 30 | 31 | # OpenCSV 32 | -keep class org.apache.commons.logging.LogConfigurationException {*;} 33 | -keep class org.apache.commons.logging.impl.LogFactoryImpl {*;} 34 | # Do not keep Log4JLogger class since the dependency org.apache.log4j.Priority is not available here. 35 | # Keep the alternative log implementation Jdk14Logger so that the OpenCSV process can continue 36 | -keep class org.apache.commons.logging.impl.Jdk14Logger {*;} 37 | 38 | -dontwarn org.slf4j.impl.StaticLoggerBinder 39 | -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.history.QuoteHistoryDatabase/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 2, 5 | "identityHash": "a1063d811fa09290da26ab3a899da71e", 6 | "entities": [ 7 | { 8 | "tableName": "histories", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `md5` TEXT NOT NULL, `text` TEXT NOT NULL, `source` TEXT NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "md5", 19 | "columnName": "md5", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "text", 25 | "columnName": "text", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "source", 31 | "columnName": "source", 32 | "affinity": "TEXT", 33 | "notNull": true 34 | } 35 | ], 36 | "primaryKey": { 37 | "columnNames": [ 38 | "_id" 39 | ], 40 | "autoGenerate": true 41 | }, 42 | "indices": [], 43 | "foreignKeys": [] 44 | } 45 | ], 46 | "views": [], 47 | "setupQueries": [ 48 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 49 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a1063d811fa09290da26ab3a899da71e')" 50 | ] 51 | } 52 | } -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.history.QuoteHistoryDatabase/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 3, 5 | "identityHash": "af3dbf7095a04e20cd9bcd075cb3dd2b", 6 | "entities": [ 7 | { 8 | "tableName": "histories", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `md5` TEXT NOT NULL, `text` TEXT NOT NULL, `source` TEXT NOT NULL, `AUTHOR` TEXT NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "md5", 19 | "columnName": "md5", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "text", 25 | "columnName": "text", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "source", 31 | "columnName": "source", 32 | "affinity": "TEXT", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "author", 37 | "columnName": "AUTHOR", 38 | "affinity": "TEXT", 39 | "notNull": true 40 | } 41 | ], 42 | "primaryKey": { 43 | "columnNames": [ 44 | "_id" 45 | ], 46 | "autoGenerate": true 47 | }, 48 | "indices": [], 49 | "foreignKeys": [] 50 | } 51 | ], 52 | "views": [], 53 | "setupQueries": [ 54 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 55 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'af3dbf7095a04e20cd9bcd075cb3dd2b')" 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.history.QuoteHistoryDatabase/4.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 4, 5 | "identityHash": "194f86c95d02a72433758649ffded57b", 6 | "entities": [ 7 | { 8 | "tableName": "histories", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `md5` TEXT NOT NULL, `text` TEXT NOT NULL, `source` TEXT NOT NULL, `author` TEXT NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "md5", 19 | "columnName": "md5", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "text", 25 | "columnName": "text", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "source", 31 | "columnName": "source", 32 | "affinity": "TEXT", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "author", 37 | "columnName": "author", 38 | "affinity": "TEXT", 39 | "notNull": true 40 | } 41 | ], 42 | "primaryKey": { 43 | "columnNames": [ 44 | "_id" 45 | ], 46 | "autoGenerate": true 47 | }, 48 | "indices": [ 49 | { 50 | "name": "index_histories_md5", 51 | "unique": true, 52 | "columnNames": [ 53 | "md5" 54 | ], 55 | "orders": [], 56 | "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_histories_md5` ON `${TABLE_NAME}` (`md5`)" 57 | } 58 | ], 59 | "foreignKeys": [] 60 | } 61 | ], 62 | "views": [], 63 | "setupQueries": [ 64 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 65 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '194f86c95d02a72433758649ffded57b')" 66 | ] 67 | } 68 | } -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.modules.collections.database.QuoteCollectionDatabase/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 2, 5 | "identityHash": "738625ad793f543cd771a7337a4e0ac3", 6 | "entities": [ 7 | { 8 | "tableName": "collections", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `md5` TEXT NOT NULL, `text` TEXT NOT NULL, `source` TEXT NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "md5", 19 | "columnName": "md5", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "text", 25 | "columnName": "text", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "source", 31 | "columnName": "source", 32 | "affinity": "TEXT", 33 | "notNull": true 34 | } 35 | ], 36 | "primaryKey": { 37 | "columnNames": [ 38 | "_id" 39 | ], 40 | "autoGenerate": true 41 | }, 42 | "indices": [], 43 | "foreignKeys": [] 44 | } 45 | ], 46 | "views": [], 47 | "setupQueries": [ 48 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 49 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '738625ad793f543cd771a7337a4e0ac3')" 50 | ] 51 | } 52 | } -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.modules.collections.database.QuoteCollectionDatabase/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 3, 5 | "identityHash": "2c5009266b9826aba72ca99ac4de3f8c", 6 | "entities": [ 7 | { 8 | "tableName": "collections", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `md5` TEXT NOT NULL, `text` TEXT NOT NULL, `source` TEXT NOT NULL, `AUTHOR` TEXT)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "md5", 19 | "columnName": "md5", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "text", 25 | "columnName": "text", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "source", 31 | "columnName": "source", 32 | "affinity": "TEXT", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "author", 37 | "columnName": "AUTHOR", 38 | "affinity": "TEXT", 39 | "notNull": false 40 | } 41 | ], 42 | "primaryKey": { 43 | "columnNames": [ 44 | "_id" 45 | ], 46 | "autoGenerate": true 47 | }, 48 | "indices": [], 49 | "foreignKeys": [] 50 | } 51 | ], 52 | "views": [], 53 | "setupQueries": [ 54 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 55 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2c5009266b9826aba72ca99ac4de3f8c')" 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.modules.collections.database.QuoteCollectionDatabase/4.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 4, 5 | "identityHash": "00f7bcd6e8da0d3e7945ecb7646a880a", 6 | "entities": [ 7 | { 8 | "tableName": "collections", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `md5` TEXT NOT NULL, `text` TEXT NOT NULL, `source` TEXT NOT NULL, `author` TEXT NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "md5", 19 | "columnName": "md5", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "text", 25 | "columnName": "text", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "source", 31 | "columnName": "source", 32 | "affinity": "TEXT", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "author", 37 | "columnName": "author", 38 | "affinity": "TEXT", 39 | "notNull": true 40 | } 41 | ], 42 | "primaryKey": { 43 | "columnNames": [ 44 | "_id" 45 | ], 46 | "autoGenerate": true 47 | }, 48 | "indices": [ 49 | { 50 | "name": "index_collections_md5", 51 | "unique": true, 52 | "columnNames": [ 53 | "md5" 54 | ], 55 | "orders": [], 56 | "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_collections_md5` ON `${TABLE_NAME}` (`md5`)" 57 | } 58 | ], 59 | "foreignKeys": [] 60 | } 61 | ], 62 | "views": [], 63 | "setupQueries": [ 64 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 65 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '00f7bcd6e8da0d3e7945ecb7646a880a')" 66 | ] 67 | } 68 | } -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.modules.custom.database.CustomQuoteDatabase/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 2, 5 | "identityHash": "ff66d3fae9b8fd86e99ed57b279ebb97", 6 | "entities": [ 7 | { 8 | "tableName": "quotes", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `text` TEXT NOT NULL, `source` TEXT NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "text", 19 | "columnName": "text", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "source", 25 | "columnName": "source", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | } 29 | ], 30 | "primaryKey": { 31 | "columnNames": [ 32 | "_id" 33 | ], 34 | "autoGenerate": true 35 | }, 36 | "indices": [], 37 | "foreignKeys": [] 38 | } 39 | ], 40 | "views": [], 41 | "setupQueries": [ 42 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 43 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ff66d3fae9b8fd86e99ed57b279ebb97')" 44 | ] 45 | } 46 | } -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.modules.custom.database.CustomQuoteDatabase/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 3, 5 | "identityHash": "4cf1685b37d25ac9ac87e41a55841229", 6 | "entities": [ 7 | { 8 | "tableName": "quotes", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `text` TEXT NOT NULL, `source` TEXT NOT NULL, `AUTHOR` TEXT NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "text", 19 | "columnName": "text", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "source", 25 | "columnName": "source", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "author", 31 | "columnName": "AUTHOR", 32 | "affinity": "TEXT", 33 | "notNull": true 34 | } 35 | ], 36 | "primaryKey": { 37 | "columnNames": [ 38 | "_id" 39 | ], 40 | "autoGenerate": true 41 | }, 42 | "indices": [], 43 | "foreignKeys": [] 44 | } 45 | ], 46 | "views": [], 47 | "setupQueries": [ 48 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 49 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4cf1685b37d25ac9ac87e41a55841229')" 50 | ] 51 | } 52 | } -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.modules.custom.database.CustomQuoteDatabase/4.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 4, 5 | "identityHash": "57e3eb27c82c6d345bd1c198e605ac89", 6 | "entities": [ 7 | { 8 | "tableName": "quotes", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `text` TEXT NOT NULL, `source` TEXT NOT NULL, `author` TEXT NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "text", 19 | "columnName": "text", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "source", 25 | "columnName": "source", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "author", 31 | "columnName": "author", 32 | "affinity": "TEXT", 33 | "notNull": true 34 | } 35 | ], 36 | "primaryKey": { 37 | "columnNames": [ 38 | "_id" 39 | ], 40 | "autoGenerate": true 41 | }, 42 | "indices": [], 43 | "foreignKeys": [] 44 | } 45 | ], 46 | "views": [], 47 | "setupQueries": [ 48 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 49 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '57e3eb27c82c6d345bd1c198e605ac89')" 50 | ] 51 | } 52 | } -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.modules.custom.database.CustomQuoteDatabase/5.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 5, 5 | "identityHash": "f0f4078f4f44e3fde34cd27124dc020c", 6 | "entities": [ 7 | { 8 | "tableName": "quotes", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `text` TEXT NOT NULL, `source` TEXT NOT NULL, `author` TEXT NOT NULL, `provider` TEXT NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "text", 19 | "columnName": "text", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "source", 25 | "columnName": "source", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "author", 31 | "columnName": "author", 32 | "affinity": "TEXT", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "provider", 37 | "columnName": "provider", 38 | "affinity": "TEXT", 39 | "notNull": true 40 | } 41 | ], 42 | "primaryKey": { 43 | "autoGenerate": true, 44 | "columnNames": [ 45 | "_id" 46 | ] 47 | }, 48 | "indices": [], 49 | "foreignKeys": [] 50 | } 51 | ], 52 | "views": [], 53 | "setupQueries": [ 54 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 55 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f0f4078f4f44e3fde34cd27124dc020c')" 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.modules.fortune.database.FortuneQuoteDatabase/4.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 4, 5 | "identityHash": "1808864c0e2252ee1961b79995829c47", 6 | "entities": [ 7 | { 8 | "tableName": "fortune", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `md5` TEXT NOT NULL, `text` TEXT NOT NULL, `source` TEXT NOT NULL, `author` TEXT NOT NULL, `category` TEXT NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "md5", 19 | "columnName": "md5", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "text", 25 | "columnName": "text", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "source", 31 | "columnName": "source", 32 | "affinity": "TEXT", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "author", 37 | "columnName": "author", 38 | "affinity": "TEXT", 39 | "notNull": true 40 | }, 41 | { 42 | "fieldPath": "category", 43 | "columnName": "category", 44 | "affinity": "TEXT", 45 | "notNull": true 46 | } 47 | ], 48 | "primaryKey": { 49 | "columnNames": [ 50 | "_id" 51 | ], 52 | "autoGenerate": true 53 | }, 54 | "indices": [ 55 | { 56 | "name": "index_fortune_md5", 57 | "unique": true, 58 | "columnNames": [ 59 | "md5" 60 | ], 61 | "orders": [], 62 | "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_fortune_md5` ON `${TABLE_NAME}` (`md5`)" 63 | } 64 | ], 65 | "foreignKeys": [] 66 | } 67 | ], 68 | "views": [], 69 | "setupQueries": [ 70 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 71 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1808864c0e2252ee1961b79995829c47')" 72 | ] 73 | } 74 | } -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.modules.fortune.database.FortuneQuoteDatabase/5.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 5, 5 | "identityHash": "b304650a611c7f18525ebcc4521dbaca", 6 | "entities": [ 7 | { 8 | "tableName": "fortune", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `text` TEXT NOT NULL, `source` TEXT NOT NULL, `author` TEXT NOT NULL, `category` TEXT NOT NULL, `uid` TEXT NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "text", 19 | "columnName": "text", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "source", 25 | "columnName": "source", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "author", 31 | "columnName": "author", 32 | "affinity": "TEXT", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "category", 37 | "columnName": "category", 38 | "affinity": "TEXT", 39 | "notNull": true 40 | }, 41 | { 42 | "fieldPath": "uid", 43 | "columnName": "uid", 44 | "affinity": "TEXT", 45 | "notNull": true 46 | } 47 | ], 48 | "primaryKey": { 49 | "autoGenerate": true, 50 | "columnNames": [ 51 | "_id" 52 | ] 53 | }, 54 | "indices": [ 55 | { 56 | "name": "index_fortune_uid", 57 | "unique": true, 58 | "columnNames": [ 59 | "uid" 60 | ], 61 | "orders": [], 62 | "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_fortune_uid` ON `${TABLE_NAME}` (`uid`)" 63 | } 64 | ], 65 | "foreignKeys": [] 66 | } 67 | ], 68 | "views": [], 69 | "setupQueries": [ 70 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 71 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b304650a611c7f18525ebcc4521dbaca')" 72 | ] 73 | } 74 | } -------------------------------------------------------------------------------- /app/schemas/com.crossbowffs.quotelock.data.modules.openai.OpenAIUsageDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "56f49147e19f56a03019b2fa3a1bb569", 6 | "entities": [ 7 | { 8 | "tableName": "usage", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `api_key` TEXT NOT NULL, `model` TEXT NOT NULL, `tokens` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL)", 10 | "fields": [ 11 | { 12 | "fieldPath": "id", 13 | "columnName": "_id", 14 | "affinity": "INTEGER", 15 | "notNull": false 16 | }, 17 | { 18 | "fieldPath": "apiKey", 19 | "columnName": "api_key", 20 | "affinity": "TEXT", 21 | "notNull": true 22 | }, 23 | { 24 | "fieldPath": "model", 25 | "columnName": "model", 26 | "affinity": "TEXT", 27 | "notNull": true 28 | }, 29 | { 30 | "fieldPath": "tokens", 31 | "columnName": "tokens", 32 | "affinity": "INTEGER", 33 | "notNull": true 34 | }, 35 | { 36 | "fieldPath": "timestamp", 37 | "columnName": "timestamp", 38 | "affinity": "INTEGER", 39 | "notNull": true 40 | } 41 | ], 42 | "primaryKey": { 43 | "autoGenerate": true, 44 | "columnNames": [ 45 | "_id" 46 | ] 47 | }, 48 | "indices": [], 49 | "foreignKeys": [] 50 | } 51 | ], 52 | "views": [], 53 | "setupQueries": [ 54 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 55 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '56f49147e19f56a03019b2fa3a1bb569')" 56 | ] 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/androidTest/assets/Inter-VariableFont_slnt,wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/androidTest/assets/Inter-VariableFont_slnt,wght.ttf -------------------------------------------------------------------------------- /app/src/androidTest/assets/android_13_wallpaper_1_ytechb.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/androidTest/assets/android_13_wallpaper_1_ytechb.webp -------------------------------------------------------------------------------- /app/src/androidTest/assets/fortune/pratchett: -------------------------------------------------------------------------------- 1 | He hated being thought of as one of those people that wore stupid 2 | ornamental armour. It was gilt by association. 3 | -- Terry Pratchett, "Night Watch" 4 | % 5 | The Assassin moved quietly from roof to roof until he was well away from 6 | the excitement around the Watch House. His movements could be called 7 | cat-like, except that he did not stop to spray urine up against things. 8 | -- Terry Pratchett, "Night Watch" 9 | % 10 | "THERE'S NO JUSTICE. THERE'S JUST ME." 11 | -- Death in Terry Pratchett's https://en.wikipedia.org/wiki/Mort 12 | % 13 | -------------------------------------------------------------------------------- /app/src/androidTest/assets/fortune/translate-me: -------------------------------------------------------------------------------- 1 | A fellow bought a new car, a Nissan, and was quite happy with his purchase. 2 | He was something of an animist, however, and felt that the car really ought 3 | to have a name. This presented a problem, as he was not sure if the name 4 | should be masculine or feminine. 5 | After considerable thought, he settled on an naming the car either 6 | Belchazar or Beaumadine, but remained in a quandry about the final choice. 7 | "Is a Nissan male or female?" he began asking his friends. Most of 8 | them looked at him peculiarly, mumbled things about urgent appointments, and 9 | went on their way rather quickly. 10 | He finally broached the question to a lady he knew who held a black 11 | belt in judo. She thought for a moment and answered "Feminine." 12 | The swiftness of her response puzzled him. "You're sure of that?" he 13 | asked. 14 | "Certainly," she replied. "They wouldn't sell very well if they were 15 | masculine." 16 | "Unhhh... Well, why not?" 17 | "Because people want a car with a reputation for going when you want 18 | it to. And, if Nissan's are female, it's like they say... `Each Nissan, she 19 | go!'" 20 | 21 | [No, we WON'T explain it; go ask someone who practices an oriental 22 | martial art. (Tai Chi Chuan probably doesn't count.) Ed.] 23 | % 24 | Aliquid melius quam pessimum optimum non est. 25 | % 26 | Ego sum ens omnipotens. 27 | % 28 | Hodie natus est radici frater. 29 | 30 | [ Unto the root is born a brother ] 31 | % 32 | Honi soit la vache qui rit. 33 | % 34 | Klatu barada nikto. 35 | % 36 | Mieux vaut tard que jamais! 37 | 38 | [ Better late than never ] 39 | % 40 | Quid me anxius sum? 41 | 42 | [ What? Me, worry? ] 43 | % 44 | semper en excretus 45 | % 46 | SEMPER UBI SUB UBI!!!! 47 | 48 | [ Always wear underwater ] 49 | % 50 | sillema sillema nika su 51 | % 52 | Tout choses sont dites deja, mais comme personne n'ecoute, il faut 53 | toujours recommencer. 54 | -- A. Gide 55 | 56 | [ All things have already been said, but since no one listens, one 57 | must always start again. ] 58 | % 59 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/crossbowffs/quotelock/CustomTestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.test.runner.AndroidJUnitRunner 6 | import dagger.hilt.android.testing.HiltTestApplication 7 | 8 | class CustomTestRunner : AndroidJUnitRunner() { 9 | 10 | override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application { 11 | return super.newApplication(cl, HiltTestApplication::class.java.name, context) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/crossbowffs/quotelock/data/QuoteDatabaseSamples.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data 2 | 3 | 4 | const val TEST_DB = "migration-test" 5 | const val SAMPLE_ID = 1 6 | const val SAMPLE_TEXT = "sample-text" 7 | const val SAMPLE_SOURCE = "sample-source" 8 | const val SAMPLE_AUTHOR = "sample-author" 9 | const val SAMPLE_UID = "sample-uid" -------------------------------------------------------------------------------- /app/src/androidTest/java/com/crossbowffs/quotelock/data/modules/wikiquote/WikiquoteRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.wikiquote 2 | 3 | import android.content.Context 4 | import com.crossbowffs.quotelock.utils.Xlog 5 | import dagger.hilt.android.qualifiers.ApplicationContext 6 | import dagger.hilt.android.testing.HiltAndroidRule 7 | import dagger.hilt.android.testing.HiltAndroidTest 8 | import kotlinx.coroutines.runBlocking 9 | import org.junit.Assert 10 | import org.junit.Before 11 | import org.junit.Rule 12 | import org.junit.Test 13 | import javax.inject.Inject 14 | 15 | @HiltAndroidTest 16 | class WikiquoteRepositoryTest { 17 | 18 | @get:Rule 19 | var hiltRule = HiltAndroidRule(this) 20 | 21 | @Inject 22 | @ApplicationContext 23 | lateinit var context: Context 24 | 25 | @Inject 26 | lateinit var repository: WikiquoteRepository 27 | 28 | @Before 29 | fun init() { 30 | hiltRule.inject() 31 | } 32 | 33 | @Test 34 | fun testRequestAllQuotes() { 35 | runBlocking { 36 | sequenceOf( 37 | "English", 38 | "中文", 39 | "日本语", 40 | "Deutsch", 41 | "Español", 42 | "Français", 43 | "Italiano", 44 | "Português", 45 | "Русский", 46 | "Esperanto" 47 | ).forEach { 48 | repository.language = it 49 | val quote = repository.fetchWikiquote() 50 | Xlog.d("WikiquoteRepositoryTest", "quote in $it: $quote") 51 | Assert.assertNotNull("Quote in $it is null", quote) 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/assets/database/fortune_quotes.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/assets/database/fortune_quotes.db -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | com.crossbowffs.quotelock.xposed.LockscreenHook 2 | com.crossbowffs.quotelock.xposed.XposedUtilsHook 3 | -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/account/authenticator/AuthenticationService.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.account.authenticator 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import android.os.IBinder 6 | import com.crossbowffs.quotelock.utils.Xlog 7 | import com.crossbowffs.quotelock.utils.className 8 | 9 | /** 10 | * @author Yubyf 11 | */ 12 | class AuthenticationService : Service() { 13 | 14 | private lateinit var mAuthenticator: Authenticator 15 | 16 | override fun onCreate() { 17 | Xlog.v(TAG, "SampleSyncAdapter Authentication Service started.") 18 | mAuthenticator = Authenticator(this) 19 | } 20 | 21 | override fun onDestroy() { 22 | Xlog.v(TAG, "SampleSyncAdapter Authentication Service stopped.") 23 | } 24 | 25 | override fun onBind(intent: Intent): IBinder? { 26 | Xlog.v(TAG, "getBinder()... returning the AccountAuthenticator binder for intent " 27 | + intent) 28 | return mAuthenticator.iBinder 29 | } 30 | 31 | companion object { 32 | private val TAG = className() 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/account/google/GoogleAccountManager.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.account.google 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import com.crossbowffs.quotelock.data.api.GoogleAccount 6 | import com.google.android.gms.auth.api.signin.GoogleSignInAccount 7 | import dagger.hilt.android.qualifiers.ApplicationContext 8 | import javax.inject.Inject 9 | 10 | /** 11 | * @author Yubyf 12 | */ 13 | class GoogleAccountManager @Inject constructor( 14 | @ApplicationContext private val context: Context, 15 | ) { 16 | 17 | fun checkGooglePlayService(): Boolean { 18 | return GoogleAccountHelper.checkGooglePlayService(context) 19 | } 20 | 21 | fun getGoogleAccount(): GoogleSignInAccount = GoogleAccountHelper.getGoogleAccount(context) 22 | 23 | fun isGoogleAccountSignedIn(): Boolean = GoogleAccountHelper.isGoogleAccountSignedIn(context) 24 | 25 | fun getSignedInGoogleAccount(): GoogleAccount? = 26 | GoogleAccountHelper.getSignedInGoogleAccount(context) 27 | 28 | fun getSignInIntent(): Intent = GoogleAccountHelper.getSignInIntent(context) 29 | 30 | suspend fun signOutAccount() = GoogleAccountHelper.signOutAccount(context) 31 | 32 | suspend fun handleSignInResult(result: Intent?): GoogleAccount? = 33 | GoogleAccountHelper.handleSignInResult(result) 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/account/syncadapter/SyncService.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.account.syncadapter 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Service 5 | import android.content.Intent 6 | import android.os.IBinder 7 | 8 | /** 9 | * Service to handle Account sync. This is invoked with an intent with action 10 | * ACTION_AUTHENTICATOR_INTENT. It instantiates the syncadapter and returns its IBinder. 11 | *

12 | * Reference: [aosp/platform_development](https://github.com/aosp-mirror/platform_development/blob/2f18dab43e/samples/SampleSyncAdapter/src/com/example/android/samplesync/syncadapter/SyncService.java) 13 | */ 14 | class SyncService : Service() { 15 | 16 | override fun onCreate() { 17 | synchronized(sSyncAdapterLock) { 18 | if (sSyncAdapter == null) { 19 | sSyncAdapter = SyncAdapter(applicationContext, true) 20 | } 21 | } 22 | } 23 | 24 | override fun onBind(intent: Intent): IBinder? { 25 | return sSyncAdapter?.syncAdapterBinder 26 | } 27 | 28 | companion object { 29 | private val sSyncAdapterLock = Any() 30 | 31 | @SuppressLint("StaticFieldLeak") 32 | private var sSyncAdapter: SyncAdapter? = null 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/App.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app 2 | 3 | import android.app.Application 4 | import com.crossbowffs.quotelock.account.SyncAccountManager 5 | import com.crossbowffs.quotelock.account.google.GoogleAccountHelper.getSignedInGoogleAccountEmail 6 | import com.crossbowffs.quotelock.account.google.GoogleAccountHelper.isGoogleAccountSignedIn 7 | import com.google.android.material.color.DynamicColors 8 | import dagger.hilt.android.HiltAndroidApp 9 | import javax.inject.Inject 10 | 11 | /** 12 | * @author Yubyf 13 | * @date 2021/6/20. 14 | */ 15 | @HiltAndroidApp 16 | class App : Application() { 17 | 18 | @Inject 19 | lateinit var syncAccountManager: SyncAccountManager 20 | 21 | override fun onCreate() { 22 | super.onCreate() 23 | DynamicColors.applyToActivitiesIfAvailable(this) 24 | instance = this 25 | syncAccountManager.initialize() 26 | if (isGoogleAccountSignedIn(this)) { 27 | getSignedInGoogleAccountEmail(this).takeIf { !it.isNullOrEmpty() }?.let { account -> 28 | syncAccountManager.addOrUpdateAccount(account) 29 | } 30 | } 31 | } 32 | 33 | companion object { 34 | lateinit var instance: App 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/CommonReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.crossbowffs.quotelock.data.ConfigurationRepository 7 | import com.crossbowffs.quotelock.data.modules.QuoteRepository 8 | import com.crossbowffs.quotelock.utils.WorkUtils 9 | import com.crossbowffs.quotelock.utils.Xlog 10 | import com.crossbowffs.quotelock.utils.className 11 | import dagger.hilt.android.AndroidEntryPoint 12 | import javax.inject.Inject 13 | 14 | @AndroidEntryPoint 15 | class CommonReceiver : BroadcastReceiver() { 16 | @Inject 17 | lateinit var quoteRepository: QuoteRepository 18 | 19 | @Inject 20 | lateinit var configurationRepository: ConfigurationRepository 21 | 22 | override fun onReceive(context: Context, intent: Intent) { 23 | val action = intent.action 24 | Xlog.d(TAG, "Received action: %s", action ?: "-") 25 | if (Intent.ACTION_BOOT_COMPLETED == action) { 26 | // Notify LockscreenHook to show current quotes after booting. 27 | quoteRepository.notifyBooted() 28 | WorkUtils.createQuoteDownloadWork(context, 29 | configurationRepository.refreshInterval, 30 | configurationRepository.isRequireInternet, 31 | configurationRepository.isUnmeteredNetworkOnly, 32 | false) 33 | } 34 | } 35 | 36 | companion object { 37 | private val TAG = className() 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/UiEvents.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app 2 | 3 | import androidx.compose.material3.SnackbarDuration 4 | import com.crossbowffs.quotelock.data.api.AndroidString 5 | 6 | /** 7 | * UI snack bar event. 8 | */ 9 | data class SnackBarEvent( 10 | val message: AndroidString? = null, 11 | val duration: SnackbarDuration = SnackbarDuration.Short, 12 | val actionText: AndroidString? = null, 13 | ) { 14 | override fun equals(other: Any?): Boolean { 15 | if (this === other) return true 16 | if (javaClass != other?.javaClass) return false 17 | 18 | other as SnackBarEvent 19 | 20 | if (message != other.message) return false 21 | if (duration != other.duration) return false 22 | return actionText == other.actionText 23 | } 24 | 25 | override fun hashCode(): Int { 26 | var result = message?.hashCode() ?: 0 27 | result = 31 * result + duration.hashCode() 28 | result = 31 * result + (actionText?.hashCode() ?: 0) 29 | return result 30 | } 31 | } 32 | 33 | val emptySnackBarEvent = SnackBarEvent() -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/about/AboutNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.about 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavHostController 5 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 6 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 7 | 8 | object AboutDestination : QuoteNavigationDestination { 9 | override val screen: String = "about" 10 | override val route: String = screen 11 | } 12 | 13 | fun NavGraphBuilder.aboutGraph( 14 | onBack: () -> Unit, 15 | ) { 16 | standardPageComposable(route = AboutDestination.route) { 17 | AboutRoute( 18 | onBack = onBack 19 | ) 20 | } 21 | } 22 | 23 | fun NavHostController.navigateToAbout() = 24 | navigate(AboutDestination.route) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/collections/CollectionNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.collections 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavHostController 5 | import com.crossbowffs.quotelock.data.api.QuoteDataWithCollectState 6 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 7 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 8 | 9 | object CollectionDestination : QuoteNavigationDestination { 10 | override val screen: String = "collection" 11 | override val route: String = screen 12 | } 13 | 14 | fun NavGraphBuilder.collectionGraph( 15 | onItemClick: (QuoteDataWithCollectState) -> Unit, 16 | onBack: () -> Unit, 17 | ) { 18 | standardPageComposable(route = CollectionDestination.route) { 19 | QuoteCollectionRoute(onItemClick = onItemClick, onBack = onBack) 20 | } 21 | } 22 | 23 | fun NavHostController.navigateToCollection() = 24 | navigate(CollectionDestination.route) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/ConfigsNavigations.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavHostController 5 | import com.crossbowffs.quotelock.app.configs.brainyquote.brainyQuoteGraph 6 | import com.crossbowffs.quotelock.app.configs.fortune.fortuneGraph 7 | import com.crossbowffs.quotelock.app.configs.hitokoto.hitkotoGraph 8 | import com.crossbowffs.quotelock.app.configs.openai.openaiGraph 9 | import com.crossbowffs.quotelock.app.configs.wikiquote.wikiquoteGraph 10 | import com.crossbowffs.quotelock.data.modules.Modules 11 | 12 | fun NavGraphBuilder.configGraphs(onBack: () -> Unit) { 13 | hitkotoGraph(onBack) 14 | brainyQuoteGraph(onBack) 15 | wikiquoteGraph(onBack) 16 | fortuneGraph(onBack) 17 | openaiGraph(onBack) 18 | } 19 | 20 | fun NavHostController.navigateToConfigScreen(route: String) = 21 | Modules.values().find { it.getConfigRoute() == route }?.getConfigRoute()?.let { 22 | navigate(it) 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/ConfigsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.crossbowffs.quotelock.app.configs.brainyquote.BrainyQuotePrefKeys.PREF_BRAINY_TYPE_INT 5 | import com.crossbowffs.quotelock.app.configs.brainyquote.BrainyQuotePrefKeys.PREF_BRAINY_TYPE_STRING 6 | import com.crossbowffs.quotelock.app.configs.fortune.FortunePrefKeys.PREF_FORTUNE_CATEGORY_INT 7 | import com.crossbowffs.quotelock.app.configs.fortune.FortunePrefKeys.PREF_FORTUNE_CATEGORY_STRING 8 | import com.crossbowffs.quotelock.app.configs.hitokoto.HitokotoPrefKeys.PREF_HITOKOTO_LEGACY_TYPE_INT 9 | import com.crossbowffs.quotelock.app.configs.hitokoto.HitokotoPrefKeys.PREF_HITOKOTO_TYPES_STRING 10 | import com.crossbowffs.quotelock.di.BrainyDataStore 11 | import com.crossbowffs.quotelock.di.FortuneDataStore 12 | import com.crossbowffs.quotelock.di.HitokotoDataStore 13 | import com.yubyf.datastore.DataStoreDelegate 14 | import dagger.hilt.android.lifecycle.HiltViewModel 15 | import kotlinx.coroutines.runBlocking 16 | import javax.inject.Inject 17 | 18 | @HiltViewModel 19 | class ConfigsViewModel @Inject constructor( 20 | @HitokotoDataStore private val hitokotoDataStore: DataStoreDelegate, 21 | @BrainyDataStore private val brainyDataStore: DataStoreDelegate, 22 | @FortuneDataStore private val fortuneDataStore: DataStoreDelegate, 23 | ) : ViewModel() { 24 | 25 | fun loadHitokotoTypeIndex() = runBlocking { 26 | hitokotoDataStore.getIntSuspend(PREF_HITOKOTO_LEGACY_TYPE_INT, -1) 27 | } 28 | 29 | fun loadHitokotoTypesString() = runBlocking { 30 | hitokotoDataStore.getStringSetSuspend(PREF_HITOKOTO_TYPES_STRING) 31 | } 32 | 33 | fun selectHitokotoTypes(types: Set) { 34 | hitokotoDataStore.put(PREF_HITOKOTO_TYPES_STRING, types) 35 | } 36 | 37 | fun loadBrainyQuoteTypeIndex() = runBlocking { 38 | brainyDataStore.getIntSuspend(PREF_BRAINY_TYPE_INT, 0) 39 | } 40 | 41 | fun selectBrainyQuoteType(index: Int, type: String) { 42 | brainyDataStore.put(PREF_BRAINY_TYPE_INT, index) 43 | brainyDataStore.put(PREF_BRAINY_TYPE_STRING, type) 44 | } 45 | 46 | fun loadFortuneCategoryIndex() = runBlocking { 47 | fortuneDataStore.getIntSuspend(PREF_FORTUNE_CATEGORY_INT, 0) 48 | } 49 | 50 | fun selectFortuneCategory(index: Int, type: String) { 51 | fortuneDataStore.put(PREF_FORTUNE_CATEGORY_INT, index) 52 | fortuneDataStore.put(PREF_FORTUNE_CATEGORY_STRING, type) 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/brainyquote/BrainyQuoteNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs.brainyquote 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 5 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 6 | 7 | object BrainyQuoteDestination : QuoteNavigationDestination { 8 | override val screen: String = "brainy_quote" 9 | override val route: String = screen 10 | } 11 | 12 | fun NavGraphBuilder.brainyQuoteGraph(onBack: () -> Unit) { 13 | standardPageComposable(route = BrainyQuoteDestination.route) { 14 | BrainyQuoteRoute(onBack = onBack) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/brainyquote/BrainyQuotePrefKeys.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs.brainyquote 2 | 3 | object BrainyQuotePrefKeys { 4 | const val PREF_BRAINY = "brainy" 5 | const val PREF_BRAINY_TYPE_INT = "pref_quotes_brainy_type_int" 6 | const val PREF_BRAINY_TYPE_STRING = "pref_quotes_brainy_type_string" 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/custom/CustomQuoteNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs.custom 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import com.crossbowffs.quotelock.data.api.QuoteDataWithCollectState 5 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 6 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 7 | 8 | object CustomQuoteDestination : QuoteNavigationDestination { 9 | override val screen: String = "custom_quote" 10 | override val route: String = screen 11 | } 12 | 13 | fun NavGraphBuilder.customQuoteGraph( 14 | onItemClick: (QuoteDataWithCollectState) -> Unit, 15 | onBack: () -> Unit, 16 | ) { 17 | standardPageComposable(route = CustomQuoteDestination.route) { 18 | CustomQuoteRoute(onItemClick = onItemClick, onBack = onBack) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/fortune/FortuneNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs.fortune 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 5 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 6 | 7 | object FortuneNavigation : QuoteNavigationDestination { 8 | override val screen: String = "fortune" 9 | override val route: String = screen 10 | } 11 | 12 | fun NavGraphBuilder.fortuneGraph(onBack: () -> Unit) { 13 | standardPageComposable(route = FortuneNavigation.route) { 14 | FortuneRoute(onBack = onBack) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/fortune/FortunePrefKeys.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs.fortune 2 | 3 | object FortunePrefKeys { 4 | const val PREF_FORTUNE = "fortune" 5 | const val PREF_FORTUNE_DEFAULT_CATEGORY = "all" 6 | const val PREF_FORTUNE_CATEGORY_INT = "pref_quotes_fortune_category_int" 7 | const val PREF_FORTUNE_CATEGORY_STRING = "pref_quotes_fortune_category_string" 8 | const val FORTUNE_QUOTE_MAX_LENGTH = 160 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/hitokoto/HitkotoNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs.hitokoto 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 5 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 6 | 7 | object HitkotoNavigation : QuoteNavigationDestination { 8 | override val screen: String = "hitokoto" 9 | override val route: String = screen 10 | } 11 | 12 | fun NavGraphBuilder.hitkotoGraph(onBack: () -> Unit) { 13 | standardPageComposable(route = HitkotoNavigation.route) { 14 | HitokotoRoute(onBack = onBack) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/hitokoto/HitokotoPrefKeys.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs.hitokoto 2 | 3 | object HitokotoPrefKeys { 4 | const val PREF_HITOKOTO = "hitoko" 5 | 6 | const val PREF_HITOKOTO_LEGACY_TYPE_INT = "pref_quotes_hitoko_type_int" 7 | 8 | const val PREF_HITOKOTO_LEGACY_TYPE_STRING = "pref_quotes_hitoko_type_string" 9 | const val PREF_HITOKOTO_TYPES_STRING = "pref_quotes_hitoko_types_string" 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/openai/OpenAINavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs.openai 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 5 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 6 | 7 | object OpenAINavigation : QuoteNavigationDestination { 8 | override val screen: String = "openai" 9 | override val route: String = screen 10 | } 11 | 12 | fun NavGraphBuilder.openaiGraph(onBack: () -> Unit) { 13 | standardPageComposable(route = OpenAINavigation.route) { 14 | OpenAIRoute(onBack = onBack) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/wikiquote/WikiquoteNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs.wikiquote 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 5 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 6 | 7 | object WikiquoteDestination : QuoteNavigationDestination { 8 | override val screen: String = "wiki_quote" 9 | override val route: String = screen 10 | } 11 | 12 | fun NavGraphBuilder.wikiquoteGraph(onBack: () -> Unit) { 13 | standardPageComposable(route = WikiquoteDestination.route) { 14 | WikiquoteRoute(onBack = onBack) 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/wikiquote/WikiquotePrefKeys.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs.wikiquote 2 | 3 | object WikiquotePrefKeys { 4 | const val PREF_WIKIQUOTE = "wikiquote" 5 | const val PREF_WIKIQUOTE_LANGUAGE = "pref_quotes_wikiquote_language" 6 | const val PREF_WIKIQUOTE_LANGUAGE_DEFAULT = "English" 7 | 8 | const val PREF_WIKIQUOTE_ENGLISH_URL = "https://en.m.wikiquote.org/wiki/Main_Page" 9 | const val PREF_WIKIQUOTE_CHINESE_URL = 10 | "https://zh.m.wikiquote.org/zh-cn/Wikiquote:%E9%A6%96%E9%A1%B5" 11 | const val PREF_WIKIQUOTE_JAPANESE_URL = 12 | "https://ja.wikiquote.org/wiki/%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8" 13 | const val PREF_WIKIQUOTE_ITALIANO_URL = "https://it.m.wikiquote.org/wiki/Pagina_principale" 14 | const val PREF_WIKIQUOTE_RUSSIAN_URL = 15 | "https://ru.m.wikiquote.org/wiki/%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0" 16 | const val PREF_WIKIQUOTE_DEUTSCH_URL = "https://de.m.wikiquote.org/wiki/Hauptseite" 17 | const val PREF_WIKIQUOTE_SPANISH_URL = "https://es.m.wikiquote.org/wiki/Portada" 18 | const val PREF_WIKIQUOTE_ESPERANTO_URL = "https://eo.m.wikiquote.org/wiki/%C4%88efpa%C4%9Do" 19 | const val PREF_WIKIQUOTE_FRENCH_URL = "https://fr.m.wikiquote.org/wiki/Wikiquote:Accueil" 20 | const val PREF_WIKIQUOTE_PORTUGUESE_URL = 21 | "https://pt.m.wikiquote.org/wiki/P%C3%A1gina_principal" 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/configs/wikiquote/WikiquoteViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.configs.wikiquote 2 | 3 | import androidx.compose.runtime.State 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.crossbowffs.quotelock.data.modules.wikiquote.WikiquoteRepository 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import kotlinx.coroutines.flow.launchIn 10 | import kotlinx.coroutines.flow.onEach 11 | import kotlinx.coroutines.launch 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class WikiquoteViewModel @Inject constructor( 16 | private val wikiquoteRepository: WikiquoteRepository, 17 | ) : ViewModel() { 18 | 19 | private val _language = mutableStateOf(wikiquoteRepository.language) 20 | val language: State = _language 21 | 22 | init { 23 | viewModelScope.launch { 24 | wikiquoteRepository.wikiquoteLanguageFlow.onEach { 25 | _language.value = it 26 | }.launchIn(this) 27 | } 28 | } 29 | 30 | fun selectLanguage(language: String) { 31 | wikiquoteRepository.language = language 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/detail/DetailNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.detail 2 | 3 | import androidx.navigation.NavHostController 4 | import com.crossbowffs.quotelock.app.detail.jinrishici.DetailJinrishiciDestination 5 | import com.crossbowffs.quotelock.data.api.QuoteData 6 | import com.crossbowffs.quotelock.data.modules.jinrishici.JinrishiciQuoteModule 7 | import com.crossbowffs.quotelock.utils.hexString 8 | 9 | fun NavHostController.navigateToDetail(quote: QuoteData) = when (quote.provider) { 10 | JinrishiciQuoteModule.PREF_JINRISHICI -> navigate( 11 | "${DetailJinrishiciDestination.screen}/${quote.extra?.hexString()}" 12 | ) 13 | 14 | else -> {} 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/detail/jinrishici/DetailJinrishiciNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.detail.jinrishici 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavType 5 | import androidx.navigation.navArgument 6 | import com.crossbowffs.quotelock.app.detail.jinrishici.DetailJinrishiciDestination.JINRISHICI_DETAIL_ARG 7 | import com.crossbowffs.quotelock.data.modules.jinrishici.detail.JinrishiciDetailData 8 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 9 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 10 | 11 | object DetailJinrishiciDestination : QuoteNavigationDestination { 12 | const val JINRISHICI_DETAIL_ARG = "jinrishici_detail" 13 | override val screen: String = "detail_jinrishici" 14 | override val route: String = "$screen/{$JINRISHICI_DETAIL_ARG}" 15 | } 16 | 17 | fun NavGraphBuilder.detailJinrishiciGraph(onBack: () -> Unit) { 18 | standardPageComposable( 19 | route = DetailJinrishiciDestination.route, 20 | arguments = listOf( 21 | navArgument(JINRISHICI_DETAIL_ARG) { type = NavType.StringType }, 22 | ) 23 | ) { 24 | val quoteDetailData = it.arguments?.getString(JINRISHICI_DETAIL_ARG)?.let( 25 | JinrishiciDetailData.Companion::fromByteString 26 | ) 27 | DetailJinrishiciRoute(detailData = quoteDetailData, onBack = onBack) 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/detail/jinrishici/DetailJinrishiciViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.detail.jinrishici 2 | 3 | import androidx.compose.runtime.State 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.crossbowffs.quotelock.data.CardStyleRepository 8 | import com.crossbowffs.quotelock.data.api.CardStyle 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.flow.launchIn 11 | import kotlinx.coroutines.flow.onEach 12 | import javax.inject.Inject 13 | 14 | data class DetailJinrishiciUiState( 15 | val cardStyle: CardStyle, 16 | ) 17 | 18 | @HiltViewModel 19 | class DetailJinrishiciViewModel @Inject constructor( 20 | cardStyleRepository: CardStyleRepository, 21 | ) : ViewModel() { 22 | 23 | private val _uiState = 24 | mutableStateOf(DetailJinrishiciUiState(CardStyle())) 25 | val uiState: State = _uiState 26 | 27 | init { 28 | cardStyleRepository.cardStyleFlow.onEach { cardStyle -> 29 | _uiState.value = _uiState.value.copy(cardStyle = cardStyle) 30 | }.launchIn(viewModelScope) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/font/FontManagementNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.font 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavHostController 5 | import androidx.navigation.NavType 6 | import androidx.navigation.navArgument 7 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 8 | import com.crossbowffs.quotelock.ui.navigation.standalonePageComposable 9 | 10 | object FontManagementDestination : QuoteNavigationDestination { 11 | const val TAB_ARG = "tab" 12 | 13 | override val screen: String = "font_management" 14 | override val route: String = "${screen}/{$TAB_ARG}" 15 | } 16 | 17 | fun NavGraphBuilder.fontManagementGraph(onBack: () -> Unit) { 18 | standalonePageComposable( 19 | route = FontManagementDestination.route, 20 | arguments = listOf( 21 | navArgument(FontManagementDestination.TAB_ARG) { type = NavType.IntType } 22 | ) 23 | ) { 24 | val tab = it.arguments?.getInt(FontManagementDestination.TAB_ARG) ?: 0 25 | FontManagementRoute(initialTab = tab, onBack = onBack) 26 | } 27 | } 28 | 29 | fun NavHostController.navigateToFontManagement(tab: Int = 0) = 30 | navigate("${FontManagementDestination.screen}/$tab") -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/history/HistoryNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.history 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavHostController 5 | import com.crossbowffs.quotelock.data.api.QuoteDataWithCollectState 6 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 7 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 8 | 9 | object HistoryDestination : QuoteNavigationDestination { 10 | override val screen: String = "history" 11 | override val route: String = screen 12 | } 13 | 14 | fun NavGraphBuilder.historyGraph( 15 | onItemClick: (QuoteDataWithCollectState) -> Unit, 16 | onBack: () -> Unit, 17 | ) { 18 | standardPageComposable(route = HistoryDestination.route) { 19 | QuoteHistoryRoute(onItemClick = onItemClick, onBack = onBack) 20 | } 21 | } 22 | 23 | fun NavHostController.navigateToHistory() = 24 | navigate(HistoryDestination.route) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/lockscreen/styles/LockscreenStylesNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.lockscreen.styles 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavHostController 5 | import com.crossbowffs.quotelock.data.api.QuoteDataWithCollectState 6 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 7 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 8 | 9 | object LockscreenStylesDestination : QuoteNavigationDestination { 10 | override val screen: String = "lockscreen_styles" 11 | override val route: String = screen 12 | } 13 | 14 | fun NavGraphBuilder.lockscreenStylesGraph( 15 | onPreviewClick: (QuoteDataWithCollectState) -> Unit, 16 | onFontCustomize: () -> Unit, 17 | onBack: () -> Unit, 18 | ) { 19 | standardPageComposable(route = LockscreenStylesDestination.route) { 20 | LockscreenStylesRoute( 21 | onPreviewClick = onPreviewClick, 22 | onFontCustomize = onFontCustomize, 23 | onBack = onBack 24 | ) 25 | } 26 | } 27 | 28 | fun NavHostController.navigateToLockscreenStyles() = 29 | navigate(LockscreenStylesDestination.route) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/lockscreen/styles/PreviewViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.lockscreen.styles 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.crossbowffs.quotelock.data.ConfigurationRepository 6 | import com.crossbowffs.quotelock.data.api.QuoteDataWithCollectState 7 | import com.crossbowffs.quotelock.data.api.QuoteStyle 8 | import com.crossbowffs.quotelock.data.modules.QuoteRepository 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.flow.MutableStateFlow 11 | import kotlinx.coroutines.flow.asStateFlow 12 | import kotlinx.coroutines.flow.launchIn 13 | import kotlinx.coroutines.flow.onEach 14 | import javax.inject.Inject 15 | 16 | /** 17 | * UI state for the preview screen in the settings page. 18 | */ 19 | data class PreviewUiState(val quoteData: QuoteDataWithCollectState, val quoteStyle: QuoteStyle) 20 | 21 | /** 22 | * @author Yubyf 23 | */ 24 | @HiltViewModel 25 | class PreviewViewModel @Inject constructor( 26 | configurationRepository: ConfigurationRepository, 27 | quoteRepository: QuoteRepository, 28 | ) : ViewModel() { 29 | 30 | private val _uiState: MutableStateFlow = 31 | MutableStateFlow(PreviewUiState(quoteRepository.getCurrentQuote(), QuoteStyle())) 32 | val uiState = _uiState.asStateFlow() 33 | 34 | init { 35 | quoteRepository.quoteDataFlow.onEach { 36 | _uiState.value = _uiState.value.copy(quoteData = it) 37 | }.launchIn(viewModelScope) 38 | configurationRepository.quoteStyleFlow.onEach { 39 | _uiState.value = _uiState.value.copy(quoteStyle = it) 40 | }.launchIn(viewModelScope) 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/main/MainActivityViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.main 2 | 3 | import android.content.Context 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.crossbowffs.quotelock.data.ConfigurationRepository 7 | import com.crossbowffs.quotelock.data.WidgetRepository 8 | import com.crossbowffs.quotelock.data.version.UpdateInfo 9 | import com.crossbowffs.quotelock.data.version.VersionRepository 10 | import com.crossbowffs.quotelock.utils.WorkUtils 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import dagger.hilt.android.qualifiers.ApplicationContext 13 | import kotlinx.coroutines.flow.MutableSharedFlow 14 | import kotlinx.coroutines.flow.asSharedFlow 15 | import kotlinx.coroutines.flow.launchIn 16 | import kotlinx.coroutines.flow.onEach 17 | import kotlinx.coroutines.launch 18 | import javax.inject.Inject 19 | 20 | /** 21 | * @author Yubyf 22 | */ 23 | @HiltViewModel 24 | class MainActivityViewModel @Inject constructor( 25 | @ApplicationContext context: Context, 26 | configurationRepository: ConfigurationRepository, 27 | widgetRepository: WidgetRepository, 28 | versionRepository: VersionRepository, 29 | ) : ViewModel() { 30 | 31 | private val _uiInstallEvent = MutableSharedFlow() 32 | val uiInstallEvent = _uiInstallEvent.asSharedFlow() 33 | 34 | init { 35 | // In case the user opens the app for the first time *after* rebooting, 36 | // we want to make sure the background work has been created. 37 | with(context) { 38 | WorkUtils.createQuoteDownloadWork( 39 | this, 40 | configurationRepository.refreshInterval, 41 | configurationRepository.isRequireInternet, 42 | configurationRepository.isUnmeteredNetworkOnly, 43 | false 44 | ) 45 | WorkUtils.createVersionCheckWork(this) 46 | } 47 | // Trigger the initialization of the widget repository 48 | widgetRepository.placeholder() 49 | 50 | versionRepository.updateInfoFlow.onEach { 51 | if (it is UpdateInfo.LocalUpdate && it.instantInstall) { 52 | _uiInstallEvent.emit(it.url) 53 | } 54 | }.launchIn(viewModelScope) 55 | } 56 | 57 | fun installEventConsumed() = viewModelScope.launch { 58 | _uiInstallEvent.emit(null) 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/main/MainNavigation.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalAnimationApi::class) 2 | 3 | package com.crossbowffs.quotelock.app.main 4 | 5 | import androidx.compose.animation.ExperimentalAnimationApi 6 | import androidx.navigation.NavGraphBuilder 7 | import com.crossbowffs.quotelock.data.api.QuoteData 8 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 9 | import com.google.accompanist.navigation.animation.composable 10 | 11 | object MainDestination : QuoteNavigationDestination { 12 | override val screen: String = "main" 13 | override val route: String = screen 14 | } 15 | 16 | fun NavGraphBuilder.mainGraph( 17 | onSettingsItemClick: () -> Unit, 18 | onLockscreenStylesItemClick: () -> Unit, 19 | onCollectionItemClicked: () -> Unit, 20 | onHistoryItemClicked: () -> Unit, 21 | onFontCustomize: () -> Unit, 22 | onShare: () -> Unit, 23 | onDetail: (QuoteData) -> Unit, 24 | ) { 25 | composable(route = MainDestination.route) { 26 | MainRoute( 27 | onSettingsItemClick = onSettingsItemClick, 28 | onLockscreenStylesItemClick = onLockscreenStylesItemClick, 29 | onCollectionItemClick = onCollectionItemClicked, 30 | onHistoryItemClick = onHistoryItemClicked, 31 | onFontCustomize = onFontCustomize, 32 | onShare = onShare, 33 | onDetail = onDetail 34 | ) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/quote/QuoteNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.quote 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavHostController 5 | import androidx.navigation.NavType 6 | import androidx.navigation.navArgument 7 | import com.crossbowffs.quotelock.data.api.QuoteData 8 | import com.crossbowffs.quotelock.data.api.QuoteDataWithCollectState 9 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 10 | import com.crossbowffs.quotelock.ui.navigation.standalonePageComposable 11 | 12 | object QuoteDestination : QuoteNavigationDestination { 13 | const val QUOTE_CONTENT_ARG = "quote_content" 14 | const val COLLECT_STATE_ARG = "collect_state" 15 | 16 | override val screen: String = "quote" 17 | override val route: String = 18 | "$screen/{$QUOTE_CONTENT_ARG}?$COLLECT_STATE_ARG={$COLLECT_STATE_ARG}" 19 | } 20 | 21 | fun NavGraphBuilder.quoteGraph( 22 | onFontCustomize: () -> Unit, 23 | onShare: () -> Unit, 24 | onDetail: (QuoteData) -> Unit, 25 | onBack: () -> Unit, 26 | ) { 27 | standalonePageComposable( 28 | route = QuoteDestination.route, 29 | arguments = listOf( 30 | navArgument(QuoteDestination.QUOTE_CONTENT_ARG) { type = NavType.StringType }, 31 | navArgument(QuoteDestination.COLLECT_STATE_ARG) { 32 | type = NavType.StringType 33 | nullable = true 34 | } 35 | ) 36 | ) { 37 | val quote = 38 | QuoteData.fromByteString( 39 | it.arguments?.getString(QuoteDestination.QUOTE_CONTENT_ARG).orEmpty() 40 | ) 41 | val collectState = 42 | it.arguments?.getString(QuoteDestination.COLLECT_STATE_ARG) 43 | QuoteRoute( 44 | quote = quote, 45 | initialCollectState = collectState?.toBooleanStrictOrNull(), 46 | onFontCustomize = onFontCustomize, 47 | onShare = onShare, 48 | onDetail = onDetail, 49 | onBack = onBack 50 | ) 51 | } 52 | } 53 | 54 | fun NavHostController.navigateToQuote(quote: QuoteDataWithCollectState) = navigate( 55 | "${QuoteDestination.screen}/${quote.quote.byteString}" + 56 | "?${QuoteDestination.COLLECT_STATE_ARG}=${quote.collectState}" 57 | ) 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/settings/DarkModeNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.settings 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavHostController 5 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 6 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 7 | 8 | object DarkModeNavigation : QuoteNavigationDestination { 9 | override val screen: String = "dark_mode" 10 | override val route: String = screen 11 | } 12 | 13 | fun NavGraphBuilder.darkModeQuoteGraph(onBack: () -> Unit) { 14 | standardPageComposable(route = DarkModeNavigation.route) { 15 | DarkModeRoute(onBack = onBack) 16 | } 17 | } 18 | 19 | fun NavHostController.navigateToDarkMode() = 20 | navigate(DarkModeNavigation.route) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/settings/DarkModeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.settings 2 | 3 | import androidx.appcompat.app.AppCompatDelegate 4 | import androidx.appcompat.app.AppCompatDelegate.NightMode 5 | import androidx.compose.runtime.State 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.lifecycle.ViewModel 8 | import com.crossbowffs.quotelock.data.ConfigurationRepository 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import javax.inject.Inject 11 | 12 | data class DarkModeUiState(@NightMode val nightMode: Int) 13 | 14 | /** 15 | * @author Yubyf 16 | */ 17 | @HiltViewModel 18 | class DarkModeViewModel @Inject constructor( 19 | private val configurationRepository: ConfigurationRepository, 20 | ) : ViewModel() { 21 | 22 | private val _uiState = 23 | mutableStateOf(DarkModeUiState(configurationRepository.nightMode)) 24 | val uiState: State = _uiState 25 | 26 | fun setNightMode(@NightMode nightMode: Int) { 27 | configurationRepository.nightMode = nightMode 28 | _uiState.value = DarkModeUiState(nightMode) 29 | AppCompatDelegate.setDefaultNightMode(nightMode) 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/settings/LanguageNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.settings 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavHostController 5 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 6 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 7 | 8 | object LanguageDestination : QuoteNavigationDestination { 9 | override val screen: String = "language" 10 | override val route: String = screen 11 | } 12 | 13 | fun NavGraphBuilder.languageQuoteGraph(onBack: () -> Unit) { 14 | standardPageComposable(route = LanguageDestination.route) { 15 | LanguageRoute(onBack = onBack) 16 | } 17 | } 18 | 19 | fun NavHostController.navigateToLanguage() = 20 | navigate(LanguageDestination.route) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/settings/SettingsNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.settings 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavHostController 5 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 6 | import com.crossbowffs.quotelock.ui.navigation.standardPageComposable 7 | 8 | object SettingsDestination : QuoteNavigationDestination { 9 | override val screen: String = "settings" 10 | override val route: String = screen 11 | } 12 | 13 | fun NavGraphBuilder.settingsGraph( 14 | onLanguageItemClicked: () -> Unit, 15 | onDarkModeItemClicked: () -> Unit, 16 | onModuleConfigItemClicked: (String) -> Unit, 17 | onAboutItemClicked: () -> Unit, 18 | onBack: () -> Unit, 19 | ) { 20 | standardPageComposable(route = SettingsDestination.route) { 21 | SettingsRoute( 22 | onLanguageItemClicked = onLanguageItemClicked, 23 | onDarkModeItemClicked = onDarkModeItemClicked, 24 | onModuleConfigItemClicked = onModuleConfigItemClicked, 25 | onAboutItemClicked = onAboutItemClicked, 26 | onBack = onBack 27 | ) 28 | } 29 | } 30 | 31 | fun NavHostController.navigateToSettings() = 32 | navigate(SettingsDestination.route) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/app/share/ShareNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.app.share 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.NavHostController 5 | import com.crossbowffs.quotelock.ui.navigation.QuoteNavigationDestination 6 | import com.crossbowffs.quotelock.ui.navigation.standalonePageComposable 7 | 8 | object ShareDestination : QuoteNavigationDestination { 9 | override val screen: String = "share" 10 | override val route: String = screen 11 | } 12 | 13 | fun NavGraphBuilder.shareGraph(onBack: () -> Unit) { 14 | standalonePageComposable( 15 | route = ShareDestination.route, 16 | ) { 17 | ShareRoute(onBack = onBack) 18 | } 19 | } 20 | 21 | fun NavHostController.navigateToShare() = 22 | navigate(ShareDestination.screen) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/consts/Urls.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.consts 2 | 3 | object Urls { 4 | const val XPOSED_FORUM = "http://forum.xda-developers.com/xposed" 5 | const val GITHUB_QUOTELOCK = "https://github.com/Yubyf/QuoteLockX" 6 | const val GITHUB_QUOTELOCK_CURRENT_ISSUES = "$GITHUB_QUOTELOCK/issues" 7 | 8 | const val GITHUB_QUOTELOCK_CUSTOM_FONTS_RELEASE = 9 | "https://github.com/Yubyf/QuoteLockX-CustomFonts/releases/latest" 10 | 11 | const val VERSION_JSON_URL = 12 | "https://yubyf.github.io/QuoteLockX-Files/release.json" 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/AsyncResult.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data 2 | 3 | import com.crossbowffs.quotelock.data.api.AndroidString 4 | 5 | /** 6 | * @author Yubyf 7 | */ 8 | sealed class AsyncResult { 9 | data class Success(val data: T) : AsyncResult() 10 | sealed class Error : AsyncResult() { 11 | data class ExceptionWrapper(val exception: Exception) : Error() 12 | data class Message(val message: AndroidString) : Error() 13 | } 14 | 15 | data class Loading(val message: AndroidString) : AsyncResult() 16 | } 17 | 18 | val AsyncResult<*>.succeeded get() = this is AsyncResult.Success && data != null 19 | val AsyncResult<*>.failed get() = this is AsyncResult.Error 20 | val AsyncResult<*>?.exceptionMessage 21 | get() = if (this is AsyncResult.Error.ExceptionWrapper) exception.message else "" -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/api/AndroidString.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.api 2 | 3 | import android.content.Context 4 | import com.crossbowffs.quotelock.app.font.FontInfo 5 | 6 | sealed class AndroidString { 7 | data class StringText(val string: String) : AndroidString() 8 | data class StringRes( 9 | val stringRes: Int, 10 | val args: Array = emptyArray(), 11 | ) : AndroidString() { 12 | override fun equals(other: Any?): Boolean { 13 | if (this === other) return true 14 | if (javaClass != other?.javaClass) return false 15 | 16 | other as StringRes 17 | 18 | if (stringRes != other.stringRes) return false 19 | return args.contentEquals(other.args) 20 | } 21 | 22 | override fun hashCode(): Int { 23 | var result = stringRes 24 | result = 31 * result + args.contentHashCode() 25 | return result 26 | } 27 | } 28 | } 29 | 30 | fun AndroidString?.contextString(context: Context): String { 31 | return when (this) { 32 | is AndroidString.StringText -> string 33 | is AndroidString.StringRes -> { 34 | val firstArg = args.firstOrNull() 35 | if (args.size == 1 && firstArg is FontInfo) { 36 | val arg = with(firstArg) { context.resources.configuration.localeName } 37 | context.getString(stringRes, arg) 38 | } else { 39 | context.getString(stringRes, *args) 40 | } 41 | } 42 | 43 | null -> "" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/api/CardStyle.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.api 2 | 3 | import com.crossbowffs.quotelock.consts.PREF_CARD_STYLE_CARD_PADDING_DEFAULT 4 | import com.crossbowffs.quotelock.consts.PREF_CARD_STYLE_FONT_SIZE_SOURCE_DEFAULT 5 | import com.crossbowffs.quotelock.consts.PREF_CARD_STYLE_FONT_SIZE_TEXT_DEFAULT 6 | import com.crossbowffs.quotelock.consts.PREF_CARD_STYLE_FONT_STYLE_SOURCE_DEFAULT 7 | import com.crossbowffs.quotelock.consts.PREF_CARD_STYLE_FONT_STYLE_TEXT_DEFAULT 8 | import com.crossbowffs.quotelock.consts.PREF_CARD_STYLE_LINE_SPACING_DEFAULT 9 | 10 | data class CardStyle( 11 | val quoteSize: Int = PREF_CARD_STYLE_FONT_SIZE_TEXT_DEFAULT, 12 | val sourceSize: Int = PREF_CARD_STYLE_FONT_SIZE_SOURCE_DEFAULT, 13 | val lineSpacing: Int = PREF_CARD_STYLE_LINE_SPACING_DEFAULT, 14 | val cardPadding: Int = PREF_CARD_STYLE_CARD_PADDING_DEFAULT, 15 | val quoteFontStyle: TextFontStyle = PREF_CARD_STYLE_FONT_STYLE_TEXT_DEFAULT, 16 | val sourceFontStyle: TextFontStyle = PREF_CARD_STYLE_FONT_STYLE_SOURCE_DEFAULT, 17 | ) { 18 | val fontFamily: String 19 | get() = quoteFontStyle.family 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/api/GoogleAccount.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.api 2 | 3 | import android.net.Uri 4 | 5 | data class GoogleAccount(val email: String, val avatar: Uri?) 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/api/OpenAIConfigs.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.api 2 | 3 | import com.crossbowffs.quotelock.app.configs.openai.OpenAIPrefKeys.PREF_OPENAI_LANGUAGE_DEFAULT 4 | import com.crossbowffs.quotelock.app.configs.openai.OpenAIPrefKeys.PREF_OPENAI_MODEL_DEFAULT 5 | import com.crossbowffs.quotelock.app.configs.openai.OpenAIPrefKeys.PREF_OPENAI_QUOTE_TYPE_DEFAULT 6 | 7 | data class OpenAIConfigs( 8 | val language: String = PREF_OPENAI_LANGUAGE_DEFAULT, 9 | val model: String = PREF_OPENAI_MODEL_DEFAULT, 10 | val quoteType: Int = PREF_OPENAI_QUOTE_TYPE_DEFAULT, 11 | val apiHost: String? = null, 12 | val apiKey: String? = null, 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/api/QuoteConfigs.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.api 2 | 3 | import com.crossbowffs.quotelock.consts.PREF_COMMON_QUOTE_MODULE_DEFAULT 4 | import com.crossbowffs.quotelock.consts.PREF_COMMON_REFRESH_RATE_DEFAULT 5 | import com.crossbowffs.quotelock.consts.PREF_COMMON_UNMETERED_ONLY_DEFAULT 6 | 7 | data class QuoteConfigs( 8 | val module: String = PREF_COMMON_QUOTE_MODULE_DEFAULT, 9 | val displayOnAod: Boolean = false, 10 | val refreshRate: Int = PREF_COMMON_REFRESH_RATE_DEFAULT.toInt(), 11 | val unmeteredOnly: Boolean = PREF_COMMON_UNMETERED_ONLY_DEFAULT, 12 | val refreshRateOverride: Int? = null, 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/api/QuoteModuleData.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.api 2 | 3 | /** 4 | * @author Yubyf 5 | * @date 2022/8/8. 6 | */ 7 | data class QuoteModuleData( 8 | val displayName: String, 9 | val configRoute: String?, 10 | val minimumRefreshInterval: Int, 11 | val requiresInternetConnectivity: Boolean, 12 | ) 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/api/QuoteStyle.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.api 2 | 3 | import com.crossbowffs.quotelock.consts.PREF_COMMON_FONT_SIZE_SOURCE_DEFAULT 4 | import com.crossbowffs.quotelock.consts.PREF_COMMON_FONT_SIZE_TEXT_DEFAULT 5 | import com.crossbowffs.quotelock.consts.PREF_COMMON_FONT_STYLE_SOURCE_DEFAULT 6 | import com.crossbowffs.quotelock.consts.PREF_COMMON_FONT_STYLE_TEXT_DEFAULT 7 | import com.crossbowffs.quotelock.consts.PREF_COMMON_PADDING_BOTTOM_DEFAULT 8 | import com.crossbowffs.quotelock.consts.PREF_COMMON_PADDING_TOP_DEFAULT 9 | import com.crossbowffs.quotelock.consts.PREF_COMMON_QUOTE_SPACING_DEFAULT 10 | 11 | data class QuoteStyle( 12 | val quoteSize: Int = PREF_COMMON_FONT_SIZE_TEXT_DEFAULT.toInt(), 13 | val sourceSize: Int = PREF_COMMON_FONT_SIZE_SOURCE_DEFAULT.toInt(), 14 | // Font properties 15 | val quoteFontStyle: TextFontStyle = PREF_COMMON_FONT_STYLE_TEXT_DEFAULT, 16 | val sourceFontStyle: TextFontStyle = PREF_COMMON_FONT_STYLE_SOURCE_DEFAULT, 17 | // Quote spacing 18 | val quoteSpacing: Int = PREF_COMMON_QUOTE_SPACING_DEFAULT.toInt(), 19 | // Layout padding 20 | val paddingTop: Int = PREF_COMMON_PADDING_TOP_DEFAULT.toInt(), 21 | val paddingBottom: Int = PREF_COMMON_PADDING_BOTTOM_DEFAULT.toInt(), 22 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/api/QuoteViewData.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.api 2 | 3 | data class QuoteViewData(val text: String = "", val source: String = "") -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/api/VersionData.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.api 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class AndroidUpdateInfo( 8 | @SerialName("versionCode") 9 | val versionCode: Int, 10 | @SerialName("versionName") 11 | val versionName: String, 12 | @SerialName("link") 13 | val link: String, 14 | @SerialName("changelog") 15 | val changelog: String, 16 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/history/QuoteHistoryRepository.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.history 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.withContext 7 | 8 | class QuoteHistoryRepository internal constructor( 9 | private val historyDao: QuoteHistoryDao, 10 | private val dispatcher: CoroutineDispatcher = Dispatchers.IO, 11 | ) { 12 | 13 | fun getAll(): Flow> = historyDao.getAllStream() 14 | 15 | fun search(keyword: String): Flow> = historyDao.searchStream(keyword) 16 | 17 | fun count(): Flow = historyDao.countStream() 18 | 19 | suspend fun insert(quote: QuoteHistoryEntity): Long? = withContext(dispatcher) { 20 | historyDao.insert(quote) 21 | } 22 | 23 | suspend fun delete(quote: QuoteHistoryEntity): Int = withContext(dispatcher) { 24 | historyDao.delete(quote) 25 | } 26 | 27 | suspend fun delete(id: Long): Int = withContext(dispatcher) { 28 | historyDao.delete(id) 29 | } 30 | 31 | suspend fun deleteAll() = withContext(dispatcher) { 32 | historyDao.deleteAll() 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/ModuleNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules 2 | 3 | class ModuleNotFoundException(detailMessage: String?) : RuntimeException(detailMessage) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/Modules.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules 2 | 3 | import com.crossbowffs.quotelock.data.api.QuoteModule 4 | import com.crossbowffs.quotelock.data.modules.brainyquote.BrainyQuoteQuoteModule 5 | import com.crossbowffs.quotelock.data.modules.collections.CollectionsQuoteModule 6 | import com.crossbowffs.quotelock.data.modules.custom.CustomQuoteModule 7 | import com.crossbowffs.quotelock.data.modules.fortune.FortuneQuoteModule 8 | import com.crossbowffs.quotelock.data.modules.freakuotes.FreakuotesQuoteModule 9 | import com.crossbowffs.quotelock.data.modules.hitokoto.HitokotoQuoteModule 10 | import com.crossbowffs.quotelock.data.modules.jinrishici.JinrishiciQuoteModule 11 | import com.crossbowffs.quotelock.data.modules.libquotes.LibquotesQuoteModule 12 | import com.crossbowffs.quotelock.data.modules.natune.NatuneQuoteModule 13 | import com.crossbowffs.quotelock.data.modules.openai.OpenAIQuoteModule 14 | import com.crossbowffs.quotelock.data.modules.wikiquote.WikiquoteQuoteModule 15 | 16 | object Modules { 17 | private val sModules: MutableMap = LinkedHashMap() 18 | 19 | init { 20 | addLocalModule(HitokotoQuoteModule()) 21 | addLocalModule(WikiquoteQuoteModule()) 22 | addLocalModule(JinrishiciQuoteModule()) 23 | addLocalModule(FreakuotesQuoteModule()) 24 | addLocalModule(NatuneQuoteModule()) 25 | addLocalModule(BrainyQuoteQuoteModule()) 26 | addLocalModule(LibquotesQuoteModule()) 27 | addLocalModule(FortuneQuoteModule()) 28 | addLocalModule(OpenAIQuoteModule()) 29 | addLocalModule(CustomQuoteModule()) 30 | addLocalModule(CollectionsQuoteModule()) 31 | } 32 | 33 | private fun addLocalModule(module: QuoteModule) { 34 | val className = module::class.qualifiedName ?: "" 35 | sModules[className] = module 36 | } 37 | 38 | private fun getModule(className: String): QuoteModule { 39 | val module = sModules[className] 40 | return module ?: throw ModuleNotFoundException("Module not found for class: $className") 41 | } 42 | 43 | fun values(): List { 44 | return ArrayList(sModules.values) 45 | } 46 | 47 | operator fun get(className: String): QuoteModule = getModule(className) 48 | 49 | operator fun contains(className: String): Boolean = sModules.containsKey(className) 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/QuoteRemoteSource.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules 2 | 3 | import android.content.Context 4 | import com.crossbowffs.quotelock.consts.PREF_COMMON_QUOTE_MODULE 5 | import com.crossbowffs.quotelock.consts.PREF_COMMON_QUOTE_MODULE_DEFAULT 6 | import com.crossbowffs.quotelock.data.api.QuoteData 7 | import com.crossbowffs.quotelock.data.api.QuoteModule 8 | import com.crossbowffs.quotelock.di.CommonDataStore 9 | import com.crossbowffs.quotelock.di.IoDispatcher 10 | import com.crossbowffs.quotelock.utils.Xlog 11 | import com.yubyf.datastore.DataStoreDelegate 12 | import dagger.hilt.android.qualifiers.ApplicationContext 13 | import kotlinx.coroutines.CancellationException 14 | import kotlinx.coroutines.CoroutineDispatcher 15 | import kotlinx.coroutines.withContext 16 | import javax.inject.Inject 17 | import javax.inject.Singleton 18 | 19 | @Singleton 20 | class QuoteRemoteSource @Inject constructor( 21 | @ApplicationContext private val context: Context, 22 | @CommonDataStore private val commonDataStore: DataStoreDelegate, 23 | @IoDispatcher private val dispatcher: CoroutineDispatcher, 24 | ) { 25 | 26 | @Throws(CancellationException::class) 27 | suspend fun downloadQuote(): QuoteData? = withContext(dispatcher) { 28 | context.fetchQuote().also { 29 | Xlog.d(TAG, "QuoteDownloader success: ${it != null}") 30 | } 31 | } 32 | 33 | private suspend fun Context.fetchQuote(): QuoteData? { 34 | val moduleName: String = commonDataStore.getStringSuspend( 35 | PREF_COMMON_QUOTE_MODULE, 36 | PREF_COMMON_QUOTE_MODULE_DEFAULT 37 | )!! 38 | 39 | Xlog.d(TAG, "Attempting to download new quote...") 40 | val module: QuoteModule = try { 41 | Modules[moduleName] 42 | } catch (e: ModuleNotFoundException) { 43 | Xlog.e(TAG, "Selected module not found", e) 44 | return null 45 | } 46 | Xlog.d(TAG, "Provider: ${module.getDisplayName(this)}") 47 | return runCatching { 48 | module.run { 49 | this@fetchQuote.getQuote() 50 | } 51 | }.onFailure { 52 | Xlog.e(TAG, "Quote download failed", it) 53 | }.getOrNull() 54 | } 55 | 56 | companion object { 57 | private const val TAG = "QuoteRemoteSource" 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/brainyquote/BrainyQuoteQuoteModule.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.brainyquote 2 | 3 | import android.content.Context 4 | import com.crossbowffs.quotelock.app.configs.brainyquote.BrainyQuoteDestination 5 | import com.crossbowffs.quotelock.app.configs.brainyquote.BrainyQuotePrefKeys 6 | import com.crossbowffs.quotelock.app.configs.brainyquote.BrainyQuotePrefKeys.PREF_BRAINY_TYPE_STRING 7 | import com.crossbowffs.quotelock.data.api.QuoteData 8 | import com.crossbowffs.quotelock.data.api.QuoteModule 9 | import com.crossbowffs.quotelock.data.api.QuoteModule.Companion.CHARACTER_TYPE_LATIN 10 | import com.crossbowffs.quotelock.data.api.QuoteModule.Companion.httpClient 11 | import com.crossbowffs.quotelock.di.QuoteModuleEntryPoint 12 | import com.crossbowffs.quotelock.utils.fetchXml 13 | import com.yubyf.quotelockx.R 14 | import dagger.hilt.android.EntryPointAccessors 15 | import java.io.IOException 16 | 17 | class BrainyQuoteQuoteModule : QuoteModule { 18 | override fun getDisplayName(context: Context): String { 19 | return context.getString(R.string.module_brainy_name) 20 | } 21 | 22 | override fun getConfigRoute(): String = BrainyQuoteDestination.route 23 | 24 | override fun getMinimumRefreshInterval(context: Context): Int { 25 | return 86400 26 | } 27 | 28 | override fun requiresInternetConnectivity(context: Context): Boolean { 29 | return true 30 | } 31 | 32 | @Throws(IOException::class) 33 | override suspend fun Context.getQuote(): QuoteData { 34 | val dataStore = 35 | EntryPointAccessors.fromApplication(applicationContext) 36 | .brainyDataStore() 37 | val type = dataStore.getStringSuspend(PREF_BRAINY_TYPE_STRING, "BR") 38 | val url = "https://feeds.feedburner.com/brainyquote/QUOTE$type" 39 | val document = httpClient.fetchXml(url) 40 | val quoteText = document.select("item > description").first()?.text().orEmpty().let { 41 | it.substring(1, it.length - 1) 42 | } 43 | val quoteAuthor = document.select("item > title").first()?.text().orEmpty() 44 | return QuoteData( 45 | quoteText = quoteText, 46 | quoteSource = "", 47 | quoteAuthor = quoteAuthor, 48 | provider = BrainyQuotePrefKeys.PREF_BRAINY 49 | ) 50 | } 51 | 52 | override val characterType: Int 53 | get() = CHARACTER_TYPE_LATIN 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/collections/CollectionsQuoteModule.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.collections 2 | 3 | import android.content.Context 4 | import com.crossbowffs.quotelock.app.collections.CollectionDestination 5 | import com.crossbowffs.quotelock.data.api.QuoteData 6 | import com.crossbowffs.quotelock.data.api.QuoteModule 7 | import com.crossbowffs.quotelock.data.api.QuoteModule.Companion.CHARACTER_TYPE_DEFAULT 8 | import com.crossbowffs.quotelock.di.QuoteModuleEntryPoint 9 | import com.yubyf.quotelockx.R 10 | import dagger.hilt.android.EntryPointAccessors 11 | 12 | /** 13 | * @author Yubyf 14 | */ 15 | class CollectionsQuoteModule : QuoteModule { 16 | override fun getDisplayName(context: Context): String { 17 | return context.getString(R.string.module_collections_name) 18 | } 19 | 20 | override fun getConfigRoute(): String = CollectionDestination.route 21 | 22 | override fun getMinimumRefreshInterval(context: Context): Int { 23 | return 0 24 | } 25 | 26 | override fun requiresInternetConnectivity(context: Context): Boolean { 27 | return false 28 | } 29 | 30 | @Throws(Exception::class) 31 | override suspend fun Context.getQuote(): QuoteData { 32 | val repository = EntryPointAccessors.fromApplication( 33 | applicationContext 34 | ).collectionRepository() 35 | return repository.getRandomItem()?.let { 36 | QuoteData(it.text, it.source, it.author, it.provider, it.uid) 37 | } ?: QuoteData( 38 | getString(R.string.module_collections_setup_line1), 39 | getString(R.string.module_collections_setup_line2) 40 | ) 41 | } 42 | 43 | override val characterType: Int 44 | get() = CHARACTER_TYPE_DEFAULT 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/custom/CustomQuoteModule.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.custom 2 | 3 | import android.content.Context 4 | import com.crossbowffs.quotelock.app.configs.custom.CustomQuoteDestination 5 | import com.crossbowffs.quotelock.data.api.QuoteData 6 | import com.crossbowffs.quotelock.data.api.QuoteModule 7 | import com.crossbowffs.quotelock.data.api.QuoteModule.Companion.CHARACTER_TYPE_DEFAULT 8 | import com.crossbowffs.quotelock.data.modules.custom.database.CustomQuoteContract 9 | import com.crossbowffs.quotelock.di.QuoteModuleEntryPoint 10 | import com.yubyf.quotelockx.R 11 | import dagger.hilt.android.EntryPointAccessors 12 | 13 | class CustomQuoteModule : QuoteModule { 14 | override fun getDisplayName(context: Context): String { 15 | return context.getString(R.string.module_custom_name) 16 | } 17 | 18 | override fun getConfigRoute(): String = CustomQuoteDestination.route 19 | 20 | override fun getMinimumRefreshInterval(context: Context): Int { 21 | return 0 22 | } 23 | 24 | override fun requiresInternetConnectivity(context: Context): Boolean { 25 | return false 26 | } 27 | 28 | @Throws(Exception::class) 29 | override suspend fun Context.getQuote(): QuoteData { 30 | val repository = EntryPointAccessors.fromApplication( 31 | applicationContext 32 | ).customQuoteRepository() 33 | return repository.getRandomItem()?.let { 34 | QuoteData(quoteText = it.text, quoteSource = it.source, provider = it.provider) 35 | } ?: QuoteData( 36 | quoteText = getString(R.string.module_custom_setup_line1), 37 | quoteSource = getString(R.string.module_custom_setup_line2), 38 | provider = CustomQuoteContract.PROVIDER_VALUE, 39 | ) 40 | } 41 | 42 | override val characterType: Int 43 | get() = CHARACTER_TYPE_DEFAULT 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/custom/CustomQuoteRepository.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.custom 2 | 3 | import com.crossbowffs.quotelock.data.modules.custom.database.CustomQuoteDao 4 | import com.crossbowffs.quotelock.data.modules.custom.database.CustomQuoteEntity 5 | import kotlinx.coroutines.CoroutineDispatcher 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.withContext 9 | 10 | class CustomQuoteRepository internal constructor( 11 | private val customQuoteDao: CustomQuoteDao, 12 | private val dispatcher: CoroutineDispatcher = Dispatchers.IO, 13 | ) { 14 | 15 | fun getAll(): Flow> = customQuoteDao.getAllStream() 16 | 17 | suspend fun getById(id: Long): CustomQuoteEntity? = withContext(dispatcher) { 18 | customQuoteDao.getById(id) 19 | } 20 | 21 | suspend fun getRandomItem(): CustomQuoteEntity? = withContext(dispatcher) { 22 | customQuoteDao.getRandomItem() 23 | } 24 | 25 | suspend fun update(quote: CustomQuoteEntity) = withContext(dispatcher) { 26 | customQuoteDao.update(quote) 27 | } 28 | 29 | suspend fun insert(quote: CustomQuoteEntity): Long? = withContext(dispatcher) { 30 | customQuoteDao.insert(quote) 31 | } 32 | 33 | suspend fun delete(id: Long): Int = withContext(dispatcher) { 34 | customQuoteDao.delete(id) 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/fortune/FortuneQuoteModule.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.fortune 2 | 3 | import android.content.Context 4 | import com.crossbowffs.quotelock.app.configs.fortune.FortuneNavigation 5 | import com.crossbowffs.quotelock.app.configs.fortune.FortunePrefKeys 6 | import com.crossbowffs.quotelock.app.configs.fortune.FortunePrefKeys.PREF_FORTUNE_DEFAULT_CATEGORY 7 | import com.crossbowffs.quotelock.data.api.QuoteData 8 | import com.crossbowffs.quotelock.data.api.QuoteModule 9 | import com.crossbowffs.quotelock.data.api.QuoteModule.Companion.CHARACTER_TYPE_DEFAULT 10 | import com.crossbowffs.quotelock.di.QuoteModuleEntryPoint 11 | import com.yubyf.quotelockx.R 12 | import dagger.hilt.android.EntryPointAccessors 13 | import kotlinx.coroutines.flow.firstOrNull 14 | import java.io.IOException 15 | 16 | class FortuneQuoteModule : QuoteModule { 17 | override fun getDisplayName(context: Context): String { 18 | return context.getString(R.string.module_fortune_name) 19 | } 20 | 21 | override fun getConfigRoute(): String = FortuneNavigation.route 22 | 23 | override fun getMinimumRefreshInterval(context: Context): Int { 24 | return 0 25 | } 26 | 27 | override fun requiresInternetConnectivity(context: Context): Boolean { 28 | return false 29 | } 30 | 31 | @Throws(IOException::class) 32 | override suspend fun Context.getQuote(): QuoteData { 33 | val dataStore = 34 | EntryPointAccessors.fromApplication(applicationContext) 35 | .fortuneDataStore() 36 | val database = EntryPointAccessors.fromApplication( 37 | applicationContext 38 | ).fortuneDatabase() 39 | val type = 40 | dataStore.getStringSuspend( 41 | FortunePrefKeys.PREF_FORTUNE_CATEGORY_STRING, 42 | PREF_FORTUNE_DEFAULT_CATEGORY 43 | )!! 44 | return if (type == PREF_FORTUNE_DEFAULT_CATEGORY) { 45 | database.dao().getRandomItem().firstOrNull() 46 | } else { 47 | database.dao().getRandomItemByCategory(type).firstOrNull() 48 | }?.let { QuoteData(it.text, it.source, "", it.provider, it.uid) } ?: QuoteData() 49 | } 50 | 51 | override val characterType: Int 52 | get() = CHARACTER_TYPE_DEFAULT 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/freakuotes/FreakuotesQuoteModule.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.freakuotes 2 | 3 | import android.content.Context 4 | import com.crossbowffs.quotelock.data.api.QuoteData 5 | import com.crossbowffs.quotelock.data.api.QuoteModule 6 | import com.crossbowffs.quotelock.data.api.QuoteModule.Companion.httpClient 7 | import com.crossbowffs.quotelock.utils.Xlog 8 | import com.crossbowffs.quotelock.utils.className 9 | import com.crossbowffs.quotelock.utils.fetchXml 10 | import com.yubyf.quotelockx.R 11 | 12 | class FreakuotesQuoteModule : QuoteModule { 13 | 14 | companion object { 15 | private val TAG = className() 16 | } 17 | 18 | override fun getDisplayName(context: Context): String { 19 | return context.getString(R.string.module_freakuotes_name) 20 | } 21 | 22 | override fun getConfigRoute(): String? = null 23 | 24 | override fun getMinimumRefreshInterval(context: Context): Int { 25 | return 0 26 | } 27 | 28 | override fun requiresInternetConnectivity(context: Context): Boolean { 29 | return true 30 | } 31 | 32 | @Throws(Exception::class) 33 | override suspend fun Context.getQuote(): QuoteData? { 34 | val document = httpClient.fetchXml("https://freakuotes.com/frase/aleatoria") 35 | val quoteContainer = document.select(".quote-container > blockquote").first() 36 | val quoteText = quoteContainer?.getElementsByTag("p")?.text().orEmpty() 37 | if (quoteText.isEmpty()) { 38 | Xlog.e(TAG, "Failed to find quote text") 39 | return null 40 | } 41 | val sourceLeft = quoteContainer?.select("footer > span")?.text().orEmpty() 42 | val sourceRight = quoteContainer?.select("footer > cite")?.attr("title").orEmpty() 43 | val quoteSource: String = when { 44 | sourceLeft.isEmpty() && sourceRight.isEmpty() -> { 45 | Xlog.w(TAG, "Quote source not found") 46 | "" 47 | } 48 | sourceLeft.isEmpty() -> sourceRight 49 | sourceRight.isEmpty() -> sourceLeft 50 | else -> "$sourceLeft, $sourceRight" 51 | } 52 | return QuoteData(quoteText, quoteSource, "freakuotes") 53 | } 54 | 55 | override val characterType: Int 56 | get() = QuoteModule.CHARACTER_TYPE_LATIN 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/jinrishici/detail/JinrishiciDetailData.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.jinrishici.detail 2 | 3 | import com.crossbowffs.quotelock.utils.decodeHex 4 | import com.crossbowffs.quotelock.utils.readTlvString 5 | import com.crossbowffs.quotelock.utils.readTlvStringList 6 | import com.crossbowffs.quotelock.utils.writeTlvString 7 | import com.crossbowffs.quotelock.utils.writeTlvStringList 8 | import java.io.ByteArrayOutputStream 9 | import java.nio.ByteBuffer 10 | 11 | data class JinrishiciDetailData( 12 | val title: String, 13 | val dynasty: String, 14 | val author: String, 15 | val content: List, 16 | val translate: List?, 17 | val tags: List?, 18 | ) { 19 | val bytes: ByteArray 20 | get() = ByteArrayOutputStream().use { stream -> 21 | stream.run { 22 | writeTlvString(title) 23 | writeTlvString(dynasty) 24 | writeTlvString(author) 25 | writeTlvStringList(content) 26 | writeTlvStringList(translate ?: emptyList()) 27 | writeTlvStringList(tags ?: emptyList()) 28 | toByteArray() 29 | } 30 | } 31 | 32 | companion object { 33 | fun fromBytes(bytes: ByteArray): JinrishiciDetailData? = 34 | ByteBuffer.wrap(bytes).runCatching { 35 | JinrishiciDetailData( 36 | title = readTlvString().orEmpty(), 37 | dynasty = readTlvString().orEmpty(), 38 | author = readTlvString().orEmpty(), 39 | content = readTlvStringList().orEmpty(), 40 | translate = readTlvStringList(), 41 | tags = readTlvStringList(), 42 | ) 43 | }.getOrNull() 44 | 45 | fun fromByteString(byteString: String): JinrishiciDetailData? = 46 | byteString.decodeHex().let(::fromBytes) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/libquotes/LibquotesQuoteModule.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.libquotes 2 | 3 | import android.content.Context 4 | import com.crossbowffs.quotelock.data.api.QuoteData 5 | import com.crossbowffs.quotelock.data.api.QuoteModule 6 | import com.crossbowffs.quotelock.data.api.QuoteModule.Companion.CHARACTER_TYPE_CJK 7 | import com.crossbowffs.quotelock.data.api.QuoteModule.Companion.httpClient 8 | import com.crossbowffs.quotelock.utils.Xlog 9 | import com.crossbowffs.quotelock.utils.className 10 | import com.crossbowffs.quotelock.utils.fetchXml 11 | import com.yubyf.quotelockx.R 12 | import java.io.IOException 13 | 14 | class LibquotesQuoteModule : QuoteModule { 15 | 16 | companion object { 17 | private val TAG = className() 18 | } 19 | 20 | override fun getDisplayName(context: Context): String { 21 | return context.getString(R.string.module_libquotes_name) 22 | } 23 | 24 | override fun getConfigRoute(): String? = null 25 | 26 | override fun getMinimumRefreshInterval(context: Context): Int { 27 | return 86400 28 | } 29 | 30 | override fun requiresInternetConnectivity(context: Context): Boolean { 31 | return true 32 | } 33 | 34 | @Throws(IOException::class) 35 | override suspend fun Context.getQuote(): QuoteData? { 36 | val document = httpClient.fetchXml("https://feeds.feedburner.com/libquotes/QuoteOfTheDay") 37 | val qotdItem = document.select("item").first() 38 | return qotdItem?.let { 39 | Xlog.d(TAG, "Downloaded qotd: ${it.text()}") 40 | val quoteText = it.select("description").text() 41 | val quoteAuthor = it.select("title").text() 42 | QuoteData( 43 | quoteText = quoteText, 44 | quoteSource = "", 45 | quoteAuthor = quoteAuthor, 46 | provider = "libquotes" 47 | ) 48 | } 49 | } 50 | 51 | override val characterType: Int 52 | get() = CHARACTER_TYPE_CJK 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/natune/NatuneQuoteModule.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.natune 2 | 3 | import android.content.Context 4 | import com.crossbowffs.quotelock.data.api.QuoteData 5 | import com.crossbowffs.quotelock.data.api.QuoteModule 6 | import com.crossbowffs.quotelock.data.api.QuoteModule.Companion.CHARACTER_TYPE_LATIN 7 | import com.crossbowffs.quotelock.data.api.QuoteModule.Companion.httpClient 8 | import com.crossbowffs.quotelock.utils.fetchXml 9 | import com.yubyf.quotelockx.R 10 | 11 | class NatuneQuoteModule : QuoteModule { 12 | override fun getDisplayName(context: Context): String { 13 | return context.getString(R.string.module_natune_name) 14 | } 15 | 16 | override fun getConfigRoute(): String? = null 17 | 18 | override fun getMinimumRefreshInterval(context: Context): Int { 19 | return 0 20 | } 21 | 22 | override fun requiresInternetConnectivity(context: Context): Boolean { 23 | return true 24 | } 25 | 26 | @Throws(Exception::class) 27 | override suspend fun Context.getQuote(): QuoteData { 28 | val document = httpClient.fetchXml("https://natune.net/zitate/Zufalls5") 29 | val quoteLi = document.select(".quotes > li").first() 30 | val quoteText = quoteLi?.getElementsByClass("quote_text")?.first()?.text().orEmpty() 31 | val quoteAuthor = quoteLi?.getElementsByClass("quote_author")?.first()?.text().orEmpty() 32 | return QuoteData( 33 | quoteText = quoteText, 34 | quoteSource = "", 35 | quoteAuthor = quoteAuthor, 36 | provider = "natune" 37 | ) 38 | } 39 | 40 | override val characterType: Int 41 | get() = CHARACTER_TYPE_LATIN 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/openai/OpenAIQuoteModule.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.openai 2 | 3 | import android.content.Context 4 | import com.crossbowffs.quotelock.app.configs.openai.OpenAINavigation 5 | import com.crossbowffs.quotelock.app.configs.openai.OpenAIPrefKeys.PREF_OPENAI 6 | import com.crossbowffs.quotelock.data.api.QuoteData 7 | import com.crossbowffs.quotelock.data.api.QuoteModule 8 | import com.crossbowffs.quotelock.data.api.QuoteModule.Companion.CHARACTER_TYPE_DEFAULT 9 | import com.crossbowffs.quotelock.di.OpenAIEntryPoint 10 | import com.yubyf.quotelockx.R 11 | import dagger.hilt.android.EntryPointAccessors 12 | import org.json.JSONException 13 | import java.io.IOException 14 | 15 | class OpenAIQuoteModule : QuoteModule { 16 | override fun getDisplayName(context: Context): String { 17 | return context.getString(R.string.module_openai_name) 18 | } 19 | 20 | override fun getConfigRoute(): String = OpenAINavigation.route 21 | 22 | override fun getMinimumRefreshInterval(context: Context): Int { 23 | return 0 24 | } 25 | 26 | override fun requiresInternetConnectivity(context: Context): Boolean { 27 | return true 28 | } 29 | 30 | @Throws(IOException::class, JSONException::class) 31 | override suspend fun Context.getQuote(): QuoteData { 32 | val openAIRepository = 33 | EntryPointAccessors.fromApplication(applicationContext) 34 | .openAIRepository() 35 | if (openAIRepository.apiKey.isNullOrBlank()) { 36 | return QuoteData( 37 | quoteText = getString(R.string.module_openai_setup_line1), 38 | quoteSource = getString(R.string.module_openai_setup_line2), 39 | provider = PREF_OPENAI, 40 | ) 41 | } 42 | val openAIQuote = openAIRepository.requestQuote() 43 | return openAIQuote.let { 44 | QuoteData( 45 | quoteText = it?.quote.orEmpty(), 46 | quoteSource = it?.source.orEmpty(), 47 | provider = PREF_OPENAI, 48 | ) 49 | } 50 | } 51 | 52 | override val characterType: Int 53 | get() = CHARACTER_TYPE_DEFAULT 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/openai/chat/OpenAIChatInput.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalSerializationApi::class) 2 | 3 | package com.crossbowffs.quotelock.data.modules.openai.chat 4 | 5 | import com.crossbowffs.quotelock.app.configs.openai.OpenAIPrefKeys 6 | import kotlinx.serialization.EncodeDefault 7 | import kotlinx.serialization.ExperimentalSerializationApi 8 | import kotlinx.serialization.SerialName 9 | import kotlinx.serialization.Serializable 10 | 11 | @Serializable 12 | data class OpenAIChatInput( 13 | @SerialName("model") 14 | @EncodeDefault 15 | val model: String = OpenAIPrefKeys.PREF_OPENAI_MODEL_DEFAULT, 16 | @SerialName("messages") 17 | val messages: List, 18 | @SerialName("temperature") 19 | @EncodeDefault 20 | val temperature: Float = 0.9F, 21 | @SerialName("top_p") 22 | @EncodeDefault 23 | val topP: Float = 1.0F, 24 | @SerialName("max_tokens") 25 | @EncodeDefault 26 | val maxTokens: Int = 4096, 27 | ) 28 | 29 | @Serializable 30 | data class OpenAIChatMessage( 31 | @SerialName("role") 32 | val role: String, 33 | @SerialName("content") 34 | val content: String, 35 | ) { 36 | companion object { 37 | fun system(content: String) = OpenAIChatMessage("system", content) 38 | fun user(content: String) = OpenAIChatMessage("user", content) 39 | fun assistant(content: String) = OpenAIChatMessage("assistant", content) 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/openai/chat/OpenAIChatResponse.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.openai.chat 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class OpenAIChatResponse( 8 | @SerialName("id") 9 | val id: String, 10 | @SerialName("object") 11 | val objectX: String, 12 | @SerialName("created") 13 | val created: Long, 14 | @SerialName("model") 15 | val model: String, 16 | @SerialName("choices") 17 | val choices: List, 18 | @SerialName("usage") 19 | val usage: OpenAIChatUsage, 20 | ) 21 | 22 | @Serializable 23 | data class OpenAIChatChoice( 24 | @SerialName("index") 25 | val index: Int, 26 | @SerialName("message") 27 | val message: OpenAIChatMessage, 28 | @SerialName("finish_reason") 29 | val finishReason: String, 30 | ) 31 | 32 | @Serializable 33 | data class OpenAIChatUsage( 34 | @SerialName("prompt_tokens") 35 | val promptTokens: Int, 36 | @SerialName("completion_tokens") 37 | val completionTokens: Int, 38 | @SerialName("total_tokens") 39 | val totalTokens: Int, 40 | ) 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/openai/chat/OpenAIModelsResponse.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.openai.chat 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class OpenAIModelsResponse( 8 | @SerialName("object") 9 | val objectX: String, 10 | @SerialName("data") 11 | val dataX: List, 12 | ) 13 | 14 | @Serializable 15 | data class OpenAIModelData( 16 | @SerialName("id") 17 | val id: String, 18 | @SerialName("object") 19 | val objectX: String, 20 | @SerialName("created") 21 | val created: Long, 22 | @SerialName("owned_by") 23 | val ownedBy: String, 24 | @SerialName("root") 25 | val root: String, 26 | ) 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/openai/chat/OpenAIQuote.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalSerializationApi::class) 2 | 3 | package com.crossbowffs.quotelock.data.modules.openai.chat 4 | 5 | import kotlinx.serialization.EncodeDefault 6 | import kotlinx.serialization.ExperimentalSerializationApi 7 | import kotlinx.serialization.SerialName 8 | import kotlinx.serialization.Serializable 9 | 10 | @Serializable 11 | data class OpenAIQuote( 12 | @SerialName("quote") 13 | val quote: String, 14 | @SerialName("source") 15 | @EncodeDefault 16 | val source: String = "", 17 | @SerialName("category") 18 | @EncodeDefault 19 | val category: String = "", 20 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/openai/geo/OpenAITraceResponse.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.openai.geo 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class OpenAITraceResponse( 7 | val fl: String, 8 | val h: String, 9 | val ip: String, 10 | val ts: String, 11 | val visitScheme: String, 12 | val uag: String, 13 | val colo: String, 14 | val sliver: String, 15 | val http: String, 16 | val loc: String, 17 | val tls: String, 18 | val sni: String, 19 | val warp: String, 20 | val gateway: String, 21 | val rbi: String, 22 | val kex: String, 23 | ) 24 | 25 | fun parseTraceResponse(response: String): OpenAITraceResponse { 26 | val lines = response.split("\n") 27 | val map = mutableMapOf() 28 | for (line in lines) { 29 | val parts = line.split("=") 30 | if (parts.size != 2) continue 31 | map[parts[0]] = parts[1] 32 | } 33 | return OpenAITraceResponse( 34 | fl = map["fl"] ?: "", 35 | h = map["h"] ?: "", 36 | ip = map["ip"] ?: "", 37 | ts = map["ts"] ?: "", 38 | visitScheme = map["visit_scheme"] ?: "", 39 | uag = map["uag"] ?: "", 40 | colo = map["colo"] ?: "", 41 | sliver = map["sliver"] ?: "", 42 | http = map["http"] ?: "", 43 | loc = map["loc"] ?: "", 44 | tls = map["tls"] ?: "", 45 | sni = map["sni"] ?: "", 46 | warp = map["warp"] ?: "", 47 | gateway = map["gateway"] ?: "", 48 | rbi = map["rbi"] ?: "", 49 | kex = map["kex"] ?: "", 50 | ) 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/modules/wikiquote/WikiquoteQuoteModule.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.wikiquote 2 | 3 | import android.content.Context 4 | import com.crossbowffs.quotelock.app.configs.wikiquote.WikiquoteDestination 5 | import com.crossbowffs.quotelock.app.configs.wikiquote.WikiquotePrefKeys.PREF_WIKIQUOTE 6 | import com.crossbowffs.quotelock.data.api.QuoteData 7 | import com.crossbowffs.quotelock.data.api.QuoteModule 8 | import com.crossbowffs.quotelock.data.api.QuoteModule.Companion.CHARACTER_TYPE_CJK 9 | import com.crossbowffs.quotelock.di.WikiquoteEntryPoint 10 | import com.crossbowffs.quotelock.utils.className 11 | import com.yubyf.quotelockx.R 12 | import dagger.hilt.android.EntryPointAccessors 13 | import java.io.IOException 14 | 15 | class WikiquoteQuoteModule : QuoteModule { 16 | 17 | companion object { 18 | private val TAG = className() 19 | } 20 | 21 | override fun getDisplayName(context: Context): String { 22 | return context.getString(R.string.module_wikiquote_name) 23 | } 24 | 25 | override fun getConfigRoute(): String = WikiquoteDestination.route 26 | 27 | override fun getMinimumRefreshInterval(context: Context): Int { 28 | return 86400 29 | } 30 | 31 | override fun requiresInternetConnectivity(context: Context): Boolean { 32 | return true 33 | } 34 | 35 | @Throws(IOException::class) 36 | override suspend fun Context.getQuote(): QuoteData? { 37 | val wikiquoteRepository = 38 | EntryPointAccessors.fromApplication(applicationContext) 39 | .wikiquoteRepository() 40 | return wikiquoteRepository.fetchWikiquote()?.let { 41 | QuoteData( 42 | quoteText = it.first, 43 | quoteSource = it.third, 44 | quoteAuthor = it.second, 45 | provider = PREF_WIKIQUOTE 46 | ) 47 | } 48 | } 49 | 50 | override val characterType: Int 51 | get() = CHARACTER_TYPE_CJK 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/data/version/VersionRemoteSource.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.version 2 | 3 | import com.crossbowffs.quotelock.consts.Urls 4 | import com.crossbowffs.quotelock.data.api.AndroidUpdateInfo 5 | import com.crossbowffs.quotelock.utils.fetchFile 6 | import com.crossbowffs.quotelock.utils.fetchFileInfo 7 | import com.crossbowffs.quotelock.utils.fetchJson 8 | import com.crossbowffs.quotelock.utils.fetchString 9 | import io.ktor.client.HttpClient 10 | import kotlinx.serialization.SerialName 11 | import kotlinx.serialization.Serializable 12 | import java.io.File 13 | import javax.inject.Inject 14 | import javax.inject.Singleton 15 | 16 | @Singleton 17 | class VersionRemoteSource @Inject constructor(private val httpClient: HttpClient) { 18 | 19 | suspend fun fetchUpdate(): VersionResponse = 20 | httpClient.fetchJson(Urls.VERSION_JSON_URL) 21 | 22 | suspend fun fetchChangelog(url: String): String = httpClient.fetchString(url = url) 23 | 24 | suspend fun fetchUpdateFileInfo(url: String) = httpClient.fetchFileInfo(url = url) 25 | 26 | suspend fun fetchUpdateFile(url: String, file: File) = 27 | httpClient.fetchFile(url = url, file = file) 28 | 29 | companion object { 30 | private const val TAG = "VersionRemoteSource" 31 | } 32 | } 33 | 34 | @Serializable 35 | data class VersionResponse( 36 | @SerialName("android") 37 | val androidUpdateInfo: AndroidUpdateInfo, 38 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/di/QuoteModuleEntryPoint.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.di 2 | 3 | import com.crossbowffs.quotelock.data.modules.collections.QuoteCollectionRepository 4 | import com.crossbowffs.quotelock.data.modules.custom.CustomQuoteRepository 5 | import com.crossbowffs.quotelock.data.modules.fortune.database.FortuneQuoteDatabase 6 | import com.yubyf.datastore.DataStoreDelegate 7 | import dagger.hilt.EntryPoint 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | 11 | @EntryPoint 12 | @InstallIn(SingletonComponent::class) 13 | interface QuoteModuleEntryPoint { 14 | fun collectionRepository(): QuoteCollectionRepository 15 | 16 | @BrainyDataStore 17 | fun brainyDataStore(): DataStoreDelegate 18 | 19 | fun customQuoteRepository(): CustomQuoteRepository 20 | 21 | @FortuneDataStore 22 | fun fortuneDataStore(): DataStoreDelegate 23 | 24 | fun fortuneDatabase(): FortuneQuoteDatabase 25 | 26 | @HitokotoDataStore 27 | fun hitokotoDataStore(): DataStoreDelegate 28 | 29 | @JinrishiciDataStore 30 | fun jinrishiciDataStore(): DataStoreDelegate 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/provider/PreferenceProvider.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.provider 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import android.database.Cursor 6 | import android.net.Uri 7 | import com.crossbowffs.quotelock.consts.PREF_COMMON 8 | import com.crossbowffs.quotelock.consts.PREF_QUOTES 9 | import com.crossbowffs.remotepreferences.RemotePreferenceProvider 10 | import com.yubyf.datastore.DataStorePreferences.Companion.getDataStorePreferences 11 | import com.yubyf.quotelockx.BuildConfig 12 | 13 | class PreferenceProvider : RemotePreferenceProvider(AUTHORITY, arrayOf(PREF_COMMON, PREF_QUOTES)) { 14 | 15 | override fun getSharedPreferences(context: Context, prefFileName: String): SharedPreferences { 16 | return context.getDataStorePreferences(prefFileName) 17 | } 18 | 19 | override fun checkAccess(prefName: String, prefKey: String, write: Boolean): Boolean { 20 | return !write 21 | } 22 | 23 | override fun query( 24 | uri: Uri, 25 | projection: Array?, 26 | selection: String?, 27 | selectionArgs: Array?, 28 | sortOrder: String?, 29 | ): Cursor? { 30 | return super.query(uri, projection, selection, selectionArgs, sortOrder) 31 | } 32 | 33 | companion object { 34 | const val AUTHORITY = BuildConfig.APPLICATION_ID + ".preferences" 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/provider/QuoteCollectionStubProvider.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.provider 2 | 3 | import android.content.ContentProvider 4 | import android.content.ContentValues 5 | import android.database.Cursor 6 | import android.net.Uri 7 | 8 | /** 9 | * Stub content provider for [com.crossbowffs.quotelock.account.syncadapter.SyncAdapter]. 10 | */ 11 | class QuoteCollectionStubProvider : ContentProvider() { 12 | override fun onCreate(): Boolean = true 13 | 14 | override fun getType(uri: Uri): String? = null 15 | 16 | override fun query( 17 | uri: Uri, 18 | projection: Array?, 19 | selection: String?, 20 | selectionArgs: Array?, 21 | sortOrder: String?, 22 | ): Cursor? = null 23 | 24 | override fun insert(uri: Uri, values: ContentValues?): Uri? = null 25 | 26 | override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int = 0 27 | 28 | override fun update( 29 | uri: Uri, 30 | values: ContentValues?, 31 | selection: String?, 32 | selectionArgs: Array?, 33 | ): Int = 0 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/ui/components/ContentAlpha.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.ui.components 2 | 3 | /** 4 | * 5 | * FIXME: Pre-define content alpha values since ContentAlpha is not able in the Material3 library 1.0.0-alpha16. 6 | * 7 | * See [Document](https://developer.android.com/reference/kotlin/androidx/compose/material/package-summary#LocalContentAlpha()) 8 | * 9 | * @author Yubyf 10 | */ 11 | object ContentAlpha { 12 | const val high = 0.87F 13 | const val medium = 0.60F 14 | const val disabled = 0.38F 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/ui/components/Shapes.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.ui.components 2 | 3 | import androidx.compose.foundation.layout.height 4 | import androidx.compose.foundation.layout.width 5 | import androidx.compose.material3.Surface 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.geometry.CornerRadius 9 | import androidx.compose.ui.geometry.RoundRect 10 | import androidx.compose.ui.geometry.Size 11 | import androidx.compose.ui.graphics.Outline 12 | import androidx.compose.ui.graphics.Path 13 | import androidx.compose.ui.graphics.Shape 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import androidx.compose.ui.unit.Density 16 | import androidx.compose.ui.unit.Dp 17 | import androidx.compose.ui.unit.LayoutDirection 18 | import androidx.compose.ui.unit.dp 19 | import com.crossbowffs.quotelock.ui.theme.QuoteLockTheme 20 | 21 | class BubbleShape( 22 | private val cornerSize: Dp, 23 | private val arrowSize: Dp, 24 | ) : Shape { 25 | override fun createOutline( 26 | size: Size, 27 | layoutDirection: LayoutDirection, 28 | density: Density, 29 | ): Outline { 30 | val cornerRadius = CornerRadius(with(density) { cornerSize.toPx() } 31 | .coerceAtMost(size.minDimension / 2)) 32 | val arrowSize = with(density) { arrowSize.toPx().coerceAtMost(8.dp.toPx()) } 33 | val path = Path().apply { 34 | addRoundRect(RoundRect(0F, 35 | 0F, 36 | size.width - arrowSize, 37 | size.height, 38 | cornerRadius, 39 | cornerRadius, 40 | cornerRadius, 41 | cornerRadius) 42 | ) 43 | moveTo(size.width - arrowSize, size.height / 2 - arrowSize) 44 | lineTo(size.width - arrowSize * 0.2F, size.height / 2 - arrowSize * 0.2F) 45 | quadraticBezierTo(size.width, size.height / 2, 46 | size.width - arrowSize * 0.2F, size.height / 2 + arrowSize * 0.2F) 47 | lineTo(size.width - arrowSize, size.height / 2 + arrowSize) 48 | close() 49 | } 50 | return Outline.Generic(path) 51 | } 52 | } 53 | 54 | @Preview 55 | @Composable 56 | fun BubbleShapePreview() { 57 | QuoteLockTheme { 58 | Surface(shape = BubbleShape(4.dp, 6.dp), modifier = Modifier 59 | .width(200.dp) 60 | .height(30.dp)) { 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/ui/navigation/QuoteNavigationDestination.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.ui.navigation 2 | 3 | interface QuoteNavigationDestination { 4 | 5 | val screen: String 6 | 7 | val route: String 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/utils/Coroutine.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.utils 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.MainScope 6 | import kotlinx.coroutines.SupervisorJob 7 | 8 | val mainScope = MainScope() 9 | val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/utils/DataStoreExtension.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.utils 2 | 3 | import com.yubyf.datastore.DataStoreDelegate 4 | 5 | @Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY") 6 | suspend fun DataStoreDelegate.getValueByDefault(key: String, default: T): T = when (default) { 7 | is Int -> getIntSuspend(key, default) 8 | is String, 9 | is String?, 10 | -> getStringSuspend(key) ?: default 11 | is Boolean -> getBooleanSuspend(key, default) 12 | is Float -> getFloatSuspend(key, default) 13 | is Long -> getLongSuspend(key, default) 14 | is Set<*>?, 15 | is Set<*>, 16 | -> getStringSetSuspend(key) 17 | ?: default as Set 18 | else -> throw IllegalArgumentException("Type not supported: ${default?.let { it::class } ?: "null"}") 19 | } as T -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/utils/DeviceUtils.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("DeviceUtils") 2 | 3 | package com.crossbowffs.quotelock.utils 4 | 5 | import android.os.Build 6 | 7 | /** 1900/1905:OP7 China, 1910 OP7Pro China, 1911: India, 1913: EU, 8 | * 1915: Tmobile, 1917: global/US unlocked, 1920: EU 5G 9 | */ 10 | private val OP7_DEVICE_MODELS: List = listOf( 11 | "GM1900", 12 | "GM1905", 13 | "GM1910", 14 | "GM1911", 15 | "GM1913", 16 | "GM1915", 17 | "GM1917", 18 | "GM1920", 19 | ) 20 | 21 | /** 22 | * @return True if current device belongs to OnePlus 7 series. 23 | */ 24 | val isOnePlus7Series: Boolean 25 | get() = OP7_DEVICE_MODELS.contains(Build.MODEL) -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/utils/DpUtils.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("DpUtils") 2 | 3 | package com.crossbowffs.quotelock.utils 4 | 5 | import android.content.res.Resources 6 | import android.util.TypedValue 7 | import androidx.compose.ui.unit.Dp 8 | import androidx.compose.ui.unit.dp 9 | 10 | private val displayMetrics = Resources.getSystem().displayMetrics 11 | 12 | fun Float.dp2px(): Float = TypedValue.applyDimension( 13 | TypedValue.COMPLEX_UNIT_DIP, 14 | this, 15 | displayMetrics 16 | ) 17 | 18 | fun Int.dp2px(): Float = TypedValue.applyDimension( 19 | TypedValue.COMPLEX_UNIT_DIP, 20 | this.toFloat(), 21 | displayMetrics 22 | ) 23 | 24 | fun Int.px2dp(): Dp = (this.toFloat() / displayMetrics.density).dp -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/utils/FileSizeFormater.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.utils 2 | 3 | import java.text.CharacterIterator 4 | import java.text.StringCharacterIterator 5 | import kotlin.math.abs 6 | 7 | /** 8 | * [Reference](https://stackoverflow.com/a/3758880/4985530) 9 | */ 10 | fun humanReadableByteCountBin(bytes: Long): String { 11 | val absB = if (bytes == Long.MIN_VALUE) Long.MAX_VALUE else abs(bytes) 12 | if (absB < 1024) { 13 | return "$bytes B" 14 | } 15 | var value = absB 16 | val ci: CharacterIterator = StringCharacterIterator("KMGTPE") 17 | var i = 40 18 | while (i >= 0 && absB > 0xfffccccccccccccL shr i) { 19 | value = value shr 10 20 | ci.next() 21 | i -= 10 22 | } 23 | value *= java.lang.Long.signum(bytes).toLong() 24 | return String.format("%.1f %cB", value / 1024.0, ci.current()) 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/utils/InstallUtils.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.utils 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.os.Build 7 | import androidx.core.content.FileProvider 8 | import com.crossbowffs.quotelock.consts.PREF_SHARE_FILE_AUTHORITY 9 | import java.io.File 10 | 11 | fun Context.installApk(path: String) = File(path).takeIf { it.exists() }?.let { 12 | val fileUri: Uri = 13 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 14 | FileProvider.getUriForFile( 15 | this, 16 | PREF_SHARE_FILE_AUTHORITY, 17 | it 18 | ) 19 | } else { 20 | Uri.fromFile(it) 21 | } 22 | val intent = Intent(Intent.ACTION_VIEW).apply { 23 | setDataAndType(fileUri, "application/vnd.android.package-archive") 24 | addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 25 | addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 26 | } 27 | applicationContext.startActivity(intent) 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/utils/Md5Utils.kt: -------------------------------------------------------------------------------- 1 | @file:JvmName("Md5Utils") 2 | 3 | package com.crossbowffs.quotelock.utils 4 | 5 | import androidx.annotation.Size 6 | import java.io.File 7 | import java.io.FileInputStream 8 | import java.security.MessageDigest 9 | import java.security.NoSuchAlgorithmException 10 | 11 | @Size(16) 12 | @Throws(Exception::class) 13 | private fun File.md5(): ByteArray { 14 | FileInputStream(this).use { 15 | val buffer = ByteArray(1024) 16 | val complete = MessageDigest.getInstance("MD5") 17 | var numRead: Int 18 | do { 19 | numRead = it.read(buffer) 20 | if (numRead > 0) { 21 | complete.update(buffer, 0, numRead) 22 | } 23 | } while (numRead != -1) 24 | return complete.digest() 25 | } 26 | } 27 | 28 | @OptIn(ExperimentalUnsignedTypes::class) 29 | fun ByteArray.hexString() = asUByteArray().joinToString("") { it.toString(16).padStart(2, '0') } 30 | 31 | fun String.decodeHex(): ByteArray { 32 | check(length % 2 == 0) { "Must have an even length" } 33 | return chunked(2) 34 | .map { it.toInt(16).toByte() } 35 | .toByteArray() 36 | } 37 | 38 | @Throws(Exception::class) 39 | fun File.md5String(): String = md5().hexString() 40 | 41 | fun String.md5(): String { 42 | try { 43 | // Create MD5 Hash 44 | val digest = MessageDigest 45 | .getInstance("MD5") 46 | digest.update(toByteArray()) 47 | val messageDigest = digest.digest() 48 | 49 | // Create Hex String 50 | val hexString = StringBuilder() 51 | messageDigest.forEach { 52 | var h = Integer.toHexString(0xFF and it.toInt()) 53 | while (h.length < 2) { 54 | h = "0$h" 55 | } 56 | hexString.append(h) 57 | } 58 | return hexString.toString() 59 | } catch (e: NoSuchAlgorithmException) { 60 | e.printStackTrace() 61 | } 62 | return "" 63 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/utils/ProcessUtils.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.utils 2 | 3 | import java.io.BufferedReader 4 | import java.io.InputStreamReader 5 | 6 | fun findProcessAndKill(processName: String) = runCatching { 7 | var pid: String? = null 8 | executeShellCommand("ps -e \n", true, 9 | { outputs -> outputs.find { out -> out.contains(processName) }?.let { pid = getPid(it) } }, 10 | { errors -> errors.forEach { Xlog.e("ProcessUtils", "[Error] $it") } } 11 | ) 12 | kill(pid) 13 | } 14 | 15 | private fun getPid(line: String): String? { 16 | return Regex("\\s+").split(line).let { 17 | if (it.isEmpty()) { 18 | null 19 | } else { 20 | it[1] 21 | } 22 | } 23 | } 24 | 25 | private fun kill(pid: String?) { 26 | if (pid.isNullOrBlank()) { 27 | return 28 | } 29 | executeShellCommand("kill $pid \n", true, null) { errors -> 30 | errors.forEach { Xlog.e("ProcessUtils", "[Error] $it") } 31 | } 32 | } 33 | 34 | fun executeShellCommand( 35 | command: String, 36 | su: Boolean, 37 | outAction: ((List) -> Unit)? = null, 38 | errorAction: ((List) -> Unit)? = null, 39 | ): Boolean { 40 | val process = Runtime.getRuntime().run { 41 | if (su) { 42 | exec("su").also { process -> 43 | process.outputStream.use { 44 | it.write(command.toByteArray()) 45 | it.flush() 46 | it.write("exit\n".toByteArray()) 47 | } 48 | } 49 | } else { 50 | exec(command).also { process -> 51 | process.outputStream.close() 52 | } 53 | } 54 | } 55 | val stderr = process.errorStream 56 | val stdout = process.inputStream 57 | outAction?.let { BufferedReader(InputStreamReader(stdout)).readLines().run(it) } 58 | errorAction?.let { BufferedReader(InputStreamReader(stderr)).readLines().run(it) } 59 | return (process.waitFor() == 0 && process.exitValue() == 0).also { process.destroy() } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/utils/StringExt.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.utils 2 | 3 | import android.content.Context 4 | import android.content.res.Configuration 5 | import androidx.annotation.StringRes 6 | import java.util.Locale 7 | 8 | val supportedLocales = 9 | sequenceOf(Locale.ENGLISH, Locale.SIMPLIFIED_CHINESE, Locale.TRADITIONAL_CHINESE) 10 | 11 | fun String.prefix(prefix: String): String { 12 | return prefix + this 13 | } 14 | 15 | fun Context.getStringForSupportLocales(@StringRes id: Int): Sequence = 16 | Configuration().let { configuration -> 17 | supportedLocales.map { locale -> 18 | configuration.setLocale(locale) 19 | createConfigurationContext(configuration).resources.getString(id) 20 | } 21 | } 22 | 23 | fun Context.isStringMatchesResource(text: String?, @StringRes id: Int): Boolean = 24 | !text.isNullOrBlank() && getStringForSupportLocales(id).any { it == text } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/utils/Xlog.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.utils 2 | 3 | import android.util.Log 4 | import com.yubyf.quotelockx.BuildConfig 5 | 6 | 7 | private const val LOG_TAG = BuildConfig.LOG_TAG 8 | private const val LOG_LEVEL = BuildConfig.LOG_LEVEL 9 | private const val LOG_TO_XPOSED = BuildConfig.LOG_TO_XPOSED 10 | 11 | inline fun className(): String { 12 | return T::class.simpleName ?: T::class.java.simpleName 13 | } 14 | 15 | object Xlog { 16 | private fun log(priority: Int, tag: String?, message: String, vararg args: Any) { 17 | if (priority < LOG_LEVEL) { 18 | return 19 | } 20 | val msg = if (args.isNotEmpty()) { 21 | String.format(message, *args).let { 22 | if (args[args.size - 1] is Throwable) { 23 | val throwable = args[args.size - 1] as Throwable 24 | val stacktraceStr = Log.getStackTraceString(throwable) 25 | "$it\n$stacktraceStr" 26 | } else { 27 | it 28 | } 29 | } 30 | } else { 31 | message 32 | } 33 | Log.println(priority, LOG_TAG, "${tag?.let { "$it:" }} $msg") 34 | if (LOG_TO_XPOSED) { 35 | Log.println(priority, "Xposed", "$LOG_TAG: ${tag?.let { "$it: " }}$msg") 36 | } 37 | } 38 | 39 | fun v(tag: String?, message: String, vararg args: Any) { 40 | log(Log.VERBOSE, tag, message, *args) 41 | } 42 | 43 | fun d(tag: String?, message: String, vararg args: Any) { 44 | log(Log.DEBUG, tag, message, *args) 45 | } 46 | 47 | fun i(tag: String?, message: String, vararg args: Any) { 48 | log(Log.INFO, tag, message, *args) 49 | } 50 | 51 | fun w(tag: String?, message: String, vararg args: Any) { 52 | log(Log.WARN, tag, message, *args) 53 | } 54 | 55 | fun e(tag: String?, message: String, vararg args: Any) { 56 | log(Log.ERROR, tag, message, *args) 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/worker/VersionWorker.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.worker 2 | 3 | import android.content.Context 4 | import androidx.work.CoroutineWorker 5 | import androidx.work.WorkerParameters 6 | import com.crossbowffs.quotelock.di.VersionEntryPoint 7 | import com.crossbowffs.quotelock.utils.Xlog 8 | import com.crossbowffs.quotelock.utils.className 9 | import dagger.hilt.android.EntryPointAccessors 10 | 11 | /** 12 | * @author Yubyf 13 | */ 14 | class VersionWorker(context: Context, workerParams: WorkerParameters) : 15 | CoroutineWorker(context, workerParams) { 16 | 17 | private val versionRepository = EntryPointAccessors.fromApplication( 18 | context.applicationContext, 19 | VersionEntryPoint::class.java 20 | ).versionRepository() 21 | 22 | override suspend fun doWork(): Result { 23 | return runCatching { 24 | Xlog.d(TAG, "Version check update work started") 25 | versionRepository.fetchUpdate() 26 | Result.success() 27 | }.getOrElse { 28 | Xlog.e(TAG, "Version check update work failed", it) 29 | if (runAttemptCount < 10) { 30 | // Exponential backoff strategy will avoid the request to repeat 31 | // too fast in case of failures. 32 | Result.retry() 33 | } else { 34 | Result.failure() 35 | } 36 | } 37 | } 38 | 39 | companion object { 40 | val TAG = className() 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/xposed/XSafeModuleResources.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.xposed 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.res.Resources.NotFoundException 5 | import android.content.res.XModuleResources 6 | import android.content.res.XResources 7 | import android.graphics.Typeface 8 | import android.graphics.drawable.Drawable 9 | import android.os.Build 10 | import android.view.View 11 | import androidx.annotation.RequiresApi 12 | import com.yubyf.quotelockx.BuildConfig 13 | import org.xmlpull.v1.XmlPullParser 14 | 15 | class XSafeModuleResources private constructor(private val mModuleRes: XModuleResources) { 16 | 17 | private fun getResId(resName: String, resType: String): Int { 18 | val resId = mModuleRes.getIdentifier(resName, resType, PACKAGE_NAME) 19 | if (resId == 0) { 20 | throw NotFoundException("Could not find $resType: $resName") 21 | } 22 | return resId 23 | } 24 | 25 | fun getString(resName: String): String { 26 | val resId = getResId(resName, "string") 27 | return mModuleRes.getString(resId) 28 | } 29 | 30 | @SuppressLint("UseCompatLoadingForDrawables") 31 | fun getDrawable(resName: String): Drawable { 32 | val resId = getResId(resName, "drawable") 33 | return mModuleRes.getDrawable(resId, null) 34 | } 35 | 36 | @RequiresApi(api = Build.VERSION_CODES.O) 37 | @SuppressLint("UseCompatLoadingForDrawables") 38 | fun getFont(resName: String): Typeface { 39 | val resId = getResId(resName, "font") 40 | return mModuleRes.getFont(resId) 41 | } 42 | 43 | fun getLayout(resName: String): XmlPullParser { 44 | val resId = getResId(resName, "layout") 45 | return mModuleRes.getLayout(resId) 46 | } 47 | 48 | fun findViewById(view: View, idName: String): View { 49 | val id = getResId(idName, "id") 50 | return view.findViewById(id) 51 | } 52 | 53 | companion object { 54 | private const val PACKAGE_NAME = BuildConfig.APPLICATION_ID 55 | fun createInstance(path: String?, origRes: XResources?): XSafeModuleResources { 56 | val moduleRes = XModuleResources.createInstance(path, origRes) 57 | return XSafeModuleResources(moduleRes) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/crossbowffs/quotelock/xposed/XposedUtilsHook.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.xposed 2 | 3 | import com.crossbowffs.quotelock.utils.Xlog 4 | import com.crossbowffs.quotelock.utils.XposedUtils 5 | import com.crossbowffs.quotelock.utils.className 6 | import com.yubyf.quotelockx.BuildConfig 7 | import de.robv.android.xposed.IXposedHookLoadPackage 8 | import de.robv.android.xposed.XC_MethodReplacement 9 | import de.robv.android.xposed.XposedHelpers 10 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam 11 | 12 | class XposedUtilsHook : IXposedHookLoadPackage { 13 | 14 | @Throws(Throwable::class) 15 | override fun handleLoadPackage(lpparam: LoadPackageParam) { 16 | if (QUOTELOCK_PACKAGE == lpparam.packageName) { 17 | try { 18 | hookXposedUtils(lpparam) 19 | } catch (e: Throwable) { 20 | Xlog.e(TAG, "Failed to hook Xposed module status checker", e) 21 | throw e 22 | } 23 | } 24 | } 25 | 26 | companion object { 27 | private val TAG = className() 28 | private const val QUOTELOCK_PACKAGE = BuildConfig.APPLICATION_ID 29 | private const val MODULE_VERSION = BuildConfig.MODULE_VERSION 30 | 31 | private fun hookXposedUtils(lpparam: LoadPackageParam) { 32 | val className = XposedUtils::class.java.name 33 | Xlog.i(TAG, "Hooking Xposed module status checker") 34 | 35 | // This is the version as fetched when the *module* is loaded 36 | // If the app is updated, this value will be changed within the 37 | // app, but will not be changed here. Thus, we can use this to 38 | // check whether the app and module versions are out of sync. 39 | XposedHelpers.findAndHookMethod(className, lpparam.classLoader, "getModuleVersion", 40 | XC_MethodReplacement.returnConstant(MODULE_VERSION)) 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/quote_widget_preview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/drawable-nodpi/quote_widget_preview.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v33/ic_launcher_monochrome.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-zh-rCN-nodpi/quote_widget_preview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/drawable-zh-rCN-nodpi/quote_widget_preview.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable-zh-rTW-nodpi/quote_widget_preview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/drawable-zh-rTW-nodpi/quote_widget_preview.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/anim_star.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 12 | 13 | 17 | 18 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_format_decrease_segment_spacing_24_dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_format_increase_segment_spacing_24_dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_format_page_large_padding_24_dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_format_page_padding_24_dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logo_openai.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_quotelockx.xml: -------------------------------------------------------------------------------- 1 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_refresh_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_star_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_star_border_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_text_style_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_variable_font_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_variable_italic_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_variable_weight_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/selector_star.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/quote_widget_loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/quote_widget_preview.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-nodpi/ic_github_identicon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-nodpi/ic_github_identicon.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-nodpi/ic_logo_brainyquote.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-nodpi/ic_logo_brainyquote.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-nodpi/ic_logo_freakuotes.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-nodpi/ic_logo_freakuotes.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-nodpi/ic_logo_hitokoto.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-nodpi/ic_logo_hitokoto.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-nodpi/ic_logo_jinrishici.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-nodpi/ic_logo_jinrishici.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-nodpi/ic_logo_natune.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-nodpi/ic_logo_natune.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rCN/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 分钟 5 | 5 分钟 6 | 15 分钟 7 | 30 分钟 8 | 1 小时 9 | 6 小时 10 | 24 小时 11 | 12 | 13 | 无衬线体 14 | 衬线体 15 | 16 | 17 | 18 | 动画 19 | 漫画 20 | 游戏 21 | 文学 22 | 原创 23 | 来自网络 24 | 其他 25 | 影视 26 | 诗词 27 | 网易云 28 | 哲学 29 | 抖机灵 30 | 31 | 32 | 33 | AI 创作 34 | 现有 35 | 36 | 37 | 38 | 应用内 39 | 系统 40 | 41 | 42 | 43 | 跟随系统 44 | 浅色 45 | 深色 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rTW/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 分鐘 5 | 5 分鐘 6 | 15 分鐘 7 | 30 分鐘 8 | 1 小時 9 | 6 小時 10 | 24 小時 11 | 12 | 13 | 無襯線體 14 | 襯線體 15 | 16 | 17 | 18 | 動畫 19 | 漫畫 20 | 遊戲 21 | 文學 22 | 原創 23 | 來自網路 24 | 其他 25 | 影音 26 | 詩詞 27 | 網易雲 28 | 哲學 29 | 抖機靈 30 | 31 | 32 | 33 | AI 創作 34 | 現有 35 | 36 | 37 | 38 | 應用內 39 | 系統 40 | 41 | 42 | 43 | 跟隨系統 44 | 淺色 45 | 深色 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/xml-v31/quote_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/xml/authenticator.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/xml/file_provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/xml/locales_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/quote_widget.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/xml/syncadapter.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/test/java/com/crossbowffs/quotelock/data/api/QuoteDataTest.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.api 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | class QuoteDataTest { 7 | 8 | private val quote = QuoteData( 9 | "落霞与孤鹜齐飞,秋水共长天一色", 10 | "《滕王阁序》", 11 | "王勃", 12 | "jinrishici", 13 | "**************", 14 | byteArrayOf(0x00, 0x00, 0x00, 0x2d) 15 | ) 16 | 17 | private val byteString = 18 | "0000002de890bde99c9ee4b88ee5ada4e9b99ce9bd90e9a39eefbc8ce7a78be6b0b4e585b1e995bfe5a4" + 19 | "a9e4b880e889b200000012e3808ae6bb95e78e8be99881e5ba8fe3808b00000006e78e8be58b83" + 20 | "0000000a6a696e726973686963690000000e2a2a2a2a2a2a2a2a2a2a2a2a2a2a000000040000002d" 21 | 22 | @Test 23 | fun testByteString() { 24 | assertEquals(byteString, quote.byteString) 25 | } 26 | 27 | @Test 28 | fun testFromByteString() { 29 | assertEquals(quote, QuoteData.fromByteString(byteString)) 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/test/java/com/crossbowffs/quotelock/data/modules/jinrishici/detail/JinrishiciDetailDataTest.kt: -------------------------------------------------------------------------------- 1 | package com.crossbowffs.quotelock.data.modules.jinrishici.detail 2 | 3 | import com.crossbowffs.quotelock.utils.decodeHex 4 | import com.crossbowffs.quotelock.utils.hexString 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | 8 | class JinrishiciDetailDataTest { 9 | 10 | private val detailData = JinrishiciDetailData( 11 | "小池", 12 | "宋代", 13 | "杨万里", 14 | listOf("泉眼无声惜细流,树阴照水爱晴柔。", "小荷才露尖尖角,早有蜻蜓立上头。"), 15 | listOf( 16 | "泉眼悄然无声是因舍不得细细的水流,树荫倒映水面是喜爱晴天和风的轻柔。", 17 | "娇嫩的小荷叶刚从水面露出尖尖的角,早有一只调皮的小蜻蜓立在它的上头。" 18 | ), 19 | listOf("白天") 20 | ) 21 | 22 | private val byteString = 23 | "0100000006e5b08fe6b1a00100000006e5ae8be4bba30100000009e69da8e4b887e9878c02000000020100000030e6b389e79cbce697a0e5a3b0e6839ce7bb86e6b581efbc8ce6a091e998b4e785a7e6b0b4e788b1e699b4e69f94e380820100000030e5b08fe88db7e6898de99cb2e5b096e5b096e8a792efbc8ce697a9e69c89e89cbbe89c93e7ab8be4b88ae5a4b4e3808202000000020100000066e6b389e79cbce68284e784b6e697a0e5a3b0e698afe59ba0e8888de4b88de5be97e7bb86e7bb86e79a84e6b0b4e6b581efbc8ce6a091e88dabe58092e698a0e6b0b4e99da2e698afe5969ce788b1e699b4e5a4a9e5928ce9a38ee79a84e8bdbbe69f94e380820100000066e5a887e5aba9e79a84e5b08fe88db7e58fb6e5889ae4bb8ee6b0b4e99da2e99cb2e587bae5b096e5b096e79a84e8a792efbc8ce697a9e69c89e4b880e58faae8b083e79aaee79a84e5b08fe89cbbe89c93e7ab8be59ca8e5ae83e79a84e4b88ae5a4b4e3808202000000010100000006e799bde5a4a9" 24 | 25 | @Test 26 | fun testBytes() { 27 | assertEquals(byteString, detailData.bytes.hexString()) 28 | } 29 | 30 | @Test 31 | fun testFromBytes() { 32 | assertEquals(detailData, JinrishiciDetailData.fromBytes(byteString.decodeHex())) 33 | } 34 | 35 | @Test 36 | fun testFromByteString() { 37 | assertEquals(detailData, JinrishiciDetailData.fromByteString(byteString)) 38 | } 39 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | @Suppress("DSL_SCOPE_VIOLATION") 3 | plugins { 4 | alias(libs.plugins.android.application).apply(false) 5 | alias(libs.plugins.android.kotlin).apply(false) 6 | alias(libs.plugins.serialization).apply(false) 7 | alias(libs.plugins.kapt).apply(false) 8 | alias(libs.plugins.ksp).apply(false) 9 | alias(libs.plugins.android.hilt).apply(false) 10 | alias(libs.plugins.gradle.versions.plugin) 11 | } 12 | 13 | tasks.register("clean", Delete::class) { 14 | delete(rootProject.buildDir) 15 | } 16 | -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/Configs.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.JavaVersion 2 | 3 | object Configs { 4 | const val compileSdk = 33 5 | const val minSdk = 21 6 | const val targetSdk = 33 7 | 8 | const val namespace = "com.yubyf.quotelockx" 9 | const val versionCode = 29 10 | const val versionName = "3.2.1" 11 | 12 | val javaVersion = JavaVersion.VERSION_11 13 | 14 | val JavaVersion.versionCode: Int 15 | get() = ordinal + 1 16 | } 17 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=false 20 | android.defaults.buildfeatures.buildconfig=true 21 | android.nonTransitiveRClass=true 22 | android.nonFinalResIds=true 23 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Nov 29 17:03:41 CST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip 7 | -------------------------------------------------------------------------------- /repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9-javadoc.jar -------------------------------------------------------------------------------- /repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9-javadoc.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: BCPG v1.68 3 | 4 | iQIzBAABCAAdFiEEbXX2QOBwOpSjva5WgOc/t4lpqiAFAmIskykACgkQgOc/t4lp 5 | qiAJxg//QbYm+s2ddid/sHw4Yfl/cQZFcK/GdvzBSQVjoP3x07lodW74lRFyLxBf 6 | zugZE1vyA+YStEwkIe7n8WSjAVQlTArxoDePyb31VHQ37yRyTtlpkPGhaXN3PmIn 7 | i5cBIN0GJxNo6i4gXP840fOEnuokckJKRlUJQktC8CixqT3QYfPk4Hic+a42uKe4 8 | 6Y3/HJSh7xSMdglOSPm/FYQBQYOIkLjQ+Hs9K0gea4pIzMKDPEekqobcwf4bSH2n 9 | nn9ZIfnz8ZXHENUsqV8MhRh7/zPwG5wYujdfT6ZUwzm1KSZldortWRuwMhNf2oer 10 | aRAy4Cw6cqRgeubP3kkg52SO7JSUR08Hu7KunkmgLB7UODVgGtBg8bVKdQ83fOWL 11 | Dyq1EeqCA1JRisYOeXskALUEXqcVp+FUXQjgq3k06xnNyDCG7VkpHyZDl1AQlB0h 12 | 6rP+gUt3D9JbIAl0GrfdUXYB3gOZ0yWlumM9Ce66LUaKDNZkmeadsUqNxt0mmDg4 13 | pyRnneHYv5deVW9+YCEh3a1i1bP2YGouFfmG2VKzbBBAyhCwxwj+TYO8lICik0tC 14 | gbEv4VmLwoQvF2yZjbJiCvX2i7EpdlNDBn5i0k32aFJ9Um4O8q3oA7fhjCmHon2X 15 | JtfK3bYprhNQH7OnRpUp5aP8knbAAbYVi4sl/UXYscup9y9S3v8= 16 | =PZJz 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9-sources.jar -------------------------------------------------------------------------------- /repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9-sources.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: BCPG v1.68 3 | 4 | iQIyBAABCAAdFiEEbXX2QOBwOpSjva5WgOc/t4lpqiAFAmIskygACgkQgOc/t4lp 5 | qiALzg/4unjqSQwCIjg72ONuZ/kdeAp1MNgt4EO2tlAdVCBVU42IEGk1MfmkV62D 6 | 5OyFuOaWb7TkBcg1z1FLXRzJgtUHNVmAkiOtL73ot4n1scJCxCg0pilvWr3WGqOT 7 | IAUVyFKGKD7fX860sPMyN0jWvbAKk03qPCoDKpxKXogKTVXjmADF6HEQt7FEDW8P 8 | u94VxhCcGWD+fhpBOZKnfcNyklFMVbWVCVcexN95n6dVxXNj9t4Q+MdK4LK14IYd 9 | 6ZUULJoZW2Ya8MoW/7Kp8DjAXcfNChaJczxJSiTHDmzjs34B7DRAJaBsEBuhR6zT 10 | 9aj+wRLRRHt+X2Jbt4I+KH1ytGNH70op6g6SpAGpYeGKMhD71WJROM7H5AGoX+S3 11 | k1t1q/ND8OozXnAWaF/bykVIKgT6SkyAfAwGRcS173JRR2xhJNDztxn95Fy/eV2o 12 | F0/AHGQptj+N7qbu2DEZqfKzHNfliy6CPRyw8R9B5dn1o6ei2u579f9a+3CXMMyU 13 | aUiqrH6prZ2dcOsNlEPQ5tdupQz+2frCT94FTmgP0ui5XOP8wJas0JiM8pwWHLPy 14 | 4fgR9EDcrk07g9/rtXnE/JYgUdNtP7H3XwB1GjXhNXv1zHygtqGxS0U7AEwj3+oh 15 | /5x/CGjudtnA+hBDcANmoYoxThME+CIryOj1blEFQ+fklIwWkQ== 16 | =dfpU 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9.aar -------------------------------------------------------------------------------- /repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9.aar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: BCPG v1.68 3 | 4 | iQIzBAABCAAdFiEEbXX2QOBwOpSjva5WgOc/t4lpqiAFAmIskygACgkQgOc/t4lp 5 | qiDQUw/8C2RtCjfUlYp40pV3v/yCx636atY2Y/5KQdQqxzXMEY3r5974E1a8qpZo 6 | Xf9UaSqMfNQptAxSf8b0w0ESGJDdbAAlDsjlTyIMlmqHP42YgYLORiXKiHDc6WuJ 7 | aiT+9/5Ra/lZEO8BHFFIi6X3fT0g9Q7B3OldAf1qWO4Cs7UVk8MLDUMtMLcIswv4 8 | oBoLTpbGv3YhxGbFVfEIYurnsrygwz/Jrap+E0NzmCdjYs50a5j9jlrGVu3p+BZJ 9 | jDVtwycaNYfoFOFx4Yq85xIoY1COfY55+M3/4QFtlILiirbc+UCgZRL7GnQjdeuG 10 | FyIzGwzuej5txJZ59OUVjmI53m86wYipVfnA1rTPi4OGOJo7dQ7mE15UMHt2WnnH 11 | VTBwxAItZeFLFREOU7wQFK/RF2IT15yKqkQ8IUwD+Uk32glC8n6TIXf1VJTLAW+y 12 | F9/KBbIOWZ46x3U5pyrbaA7voTCCwIrhZscZhw8CEE/jmum8LkxBqWXlVVgZQtui 13 | aLsCkkqtR49ShJa3PW/aTogJ/Gmw496ugLnChfgQqYRgzX7yyHqaNk4mjV/KR6VH 14 | VJ13CZmyE/2wCy6ONmKHZsWXHv9nhSe/HHEMZovXV2HaCoqUdoSMyLuSZXMuBzxT 15 | 90NIoN0yc+EZLzwSoQDEyAiBjTTDJsJeMvx2dGhjTCF5gAiivZg= 16 | =BoM4 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9.module: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": "1.1", 3 | "component": { 4 | "group": "com.crossbowffs.remotepreferences", 5 | "module": "remotepreferences", 6 | "version": "0.9", 7 | "attributes": { 8 | "org.gradle.status": "release" 9 | } 10 | }, 11 | "createdBy": { 12 | "gradle": { 13 | "version": "7.2" 14 | } 15 | }, 16 | "variants": [ 17 | { 18 | "name": "releaseApiPublication", 19 | "attributes": { 20 | "org.gradle.category": "library", 21 | "org.gradle.dependency.bundling": "external", 22 | "org.gradle.libraryelements": "aar", 23 | "org.gradle.usage": "java-api" 24 | }, 25 | "files": [ 26 | { 27 | "name": "remotepreferences-0.9.aar", 28 | "url": "remotepreferences-0.9.aar", 29 | "size": 20878, 30 | "sha512": "bf2f81e0124112ae1cdad7f3d4f43cb8d8463cd1d48d2ec1b8b59aa1f1a3111c17e29a02040ff53691a485da5eabe340180bef7f38b261eec73a55260ea020d3", 31 | "sha256": "fffa4265c9178d20d4304f6d89091c92216f309aa76516af62c69da99a8019fa", 32 | "sha1": "21c7b426afc3070e5a95060394312cedf1d96f89", 33 | "md5": "4f9e05a93947c31477d986bba7ff784f" 34 | } 35 | ] 36 | }, 37 | { 38 | "name": "releaseRuntimePublication", 39 | "attributes": { 40 | "org.gradle.category": "library", 41 | "org.gradle.dependency.bundling": "external", 42 | "org.gradle.libraryelements": "aar", 43 | "org.gradle.usage": "java-runtime" 44 | }, 45 | "files": [ 46 | { 47 | "name": "remotepreferences-0.9.aar", 48 | "url": "remotepreferences-0.9.aar", 49 | "size": 20878, 50 | "sha512": "bf2f81e0124112ae1cdad7f3d4f43cb8d8463cd1d48d2ec1b8b59aa1f1a3111c17e29a02040ff53691a485da5eabe340180bef7f38b261eec73a55260ea020d3", 51 | "sha256": "fffa4265c9178d20d4304f6d89091c92216f309aa76516af62c69da99a8019fa", 52 | "sha1": "21c7b426afc3070e5a95060394312cedf1d96f89", 53 | "md5": "4f9e05a93947c31477d986bba7ff784f" 54 | } 55 | ] 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9.module.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: BCPG v1.68 3 | 4 | iQIzBAABCAAdFiEEbXX2QOBwOpSjva5WgOc/t4lpqiAFAmIskygACgkQgOc/t4lp 5 | qiDVdg/6Apx9+dS7nEKYajX2o3nZVrogREylRyb1I6Z/gbVtfIgVxXznXnUzOPXD 6 | f6sWVWi1ctAdOCyYCYOhScGhPmyIq0jk3meyigbuCs+sqfS4spgxklMYb9KpUdJR 7 | nF3tZTsw+uBRXqWGKB5MoZ6iDyqu8Mt7JCXpAZlqYPXbe24V6qauVIB3fVC/adOr 8 | O5+8tBfKIR1PdyDUuzQ1S9JVtYEspvcacn0baUTsFDkerLGO9IzMXeq7/SBzJXYV 9 | 9KoyvhMrs95E5QnhmPRyVeXg6zkCbBiealivr8PsimAOWJLbKZXwmf5NVbEphpAB 10 | SDlzl3wtWRHF2WCExLlo/X+hAxIMpGQxlDqBsxC21DlgBq/zAUjiglZUfE0tGcvB 11 | Hi/iBmoJRJXzZ/s5mvChSxMh/1BLoTNQMElHy3jLD8JZOMwofl2wEPMivAa9snzR 12 | +h8xZy05qJcwGeqzHowtqPSNM9TLc8dEhutRpFTwcnFhZ7yNU3DsDvpTLR1eNTbX 13 | ettzAT/15QDB8b1U70HyU1UDBLuLgHdykqpqShJNBar/OegCGVClv4n6xZbKsqmM 14 | jPO3eNbP2vjZ7eRD3Tof/6XYLi9atk/FpKFV8sRmpHbxcjGqhNS8su5i3/SPaL+h 15 | 4e0WSRzJYUxT3FUOq54PFpQevBF8ROdq0BWkYtANM2WwYoZxnQg= 16 | =eHjT 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 4.0.0 10 | com.crossbowffs.remotepreferences 11 | remotepreferences 12 | 0.9 13 | aar 14 | RemotePreferences 15 | A drop-in solution for inter-app access to SharedPreferences on Android. 16 | https://github.com/apsun/RemotePreferences 17 | 18 | 19 | MIT 20 | https://opensource.org/licenses/MIT 21 | 22 | 23 | 24 | 25 | Andrew Sun 26 | andrew@crossbowffs.com 27 | 28 | 29 | 30 | scm:git:https://github.com/apsun/RemotePreferences.git 31 | scm:git:https://github.com/apsun/RemotePreferences.git 32 | https://github.com/apsun/RemotePreferences 33 | 34 | 35 | -------------------------------------------------------------------------------- /repos/com/crossbowffs/remotepreferences/remotepreferences/0.9/remotepreferences-0.9.pom.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: BCPG v1.68 3 | 4 | iQIzBAABCAAdFiEEbXX2QOBwOpSjva5WgOc/t4lpqiAFAmIskygACgkQgOc/t4lp 5 | qiDCgw/7BkdaWeJUPWwaCkitg1/43QV2k0HYxoyxMI2jb3N7YgqF+He8HouQWrT4 6 | APkXlXmo2I/0DNbPwpKDV2enz56j9GNxXQrYmDsrEkYM/igImwZ+gH78jktEHwbg 7 | M2vrGn8AdcZxrVtReG70QMmEXc63DV/rmSMS8gnV06rInd4eC4uKuMk6lWfgYOBD 8 | mQ6KC/zGxwpjI5lFhDgOriGrlPO71I6AdxDHB3lxMCs5AxlgRe7rkEeUZXqS+zir 9 | kqH85Zul8WhroutLVQzaAIm1aGi/obd81mgYxhvK+2xbdYC6Bie+f81GBb5TB5No 10 | RNG12CI+30M5/SgoI1jXqF8XNM0x2v4J9s3JLkwnv6iGdt4vMEDoAIcR4I7gJ3rx 11 | N9eLm/G3fyRfRAHGpYVrQVHcS2gsy6CATJUL4SxqTprW0y4KrpOtZlQNU0yT18Rl 12 | iM6JXjkORjfhjs1FfD/2Sxxq6CAJ/zBh5cXs/qd+/WAOkEa/1adV84vtGqNiqNS1 13 | ui7/O1l2KXMieArrPsBoxi7og4b/4i7ftxkOsOf5RfXvw4VdtHkZVHDWWgQQd63r 14 | VslDutB/m2AcL97v/KGGcvampY9YL101LmrKtmFuIB41yD7BYCskA3n1ORdYKcv5 15 | +KzFHPr2eef1KxqT8r7xKmG79PrNCsqVrtcgcYI9ARfxJDrgArw= 16 | =4BGh 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/com/crossbowffs/remotepreferences/remotepreferences/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.crossbowffs.remotepreferences 4 | remotepreferences 5 | 6 | 0.9 7 | 0.9 8 | 9 | 0.9 10 | 11 | 20220312123345 12 | 13 | 14 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/53/api-53-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/repos/de/robv/android/xposed/api/53/api-53-sources.jar -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/53/api-53-sources.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: GnuPG v2 3 | 4 | iQIcBAABCAAGBQJW/tO5AAoJEIZbcU6FIQmqb3MQALQr5GMI6mMr/3s7fpcjgzAa 5 | an9ZIZE4Zy3Gx7CU9zM+RZtUk/y0P5i8hqGvq8VSENxlx5pVYv+FI2JevjQtRBYt 6 | kmTUKNx0lPH76wXheTKmFzFybyqrHsD3cM31FqbVNRauNpwZ7LfEZYa1efI3ecc9 7 | lET90SAzFknZ42WyHxaEfFlnqwkHQT0Sqw6bhLct5Zp/Z7agznQ/Bw9gGdp/GROt 8 | 3sbFh/X5qWibVot+Yb3tsR/udmMWno3G7LEnbXxIRTTgdKagITT7dFy8qEq8SHKh 9 | HCRMaFIlqnmc9aCwRJhmA/h8g3Eh6ppM7FZkjHOFHarxFaJdlkGgK1gZJBNPdjXf 10 | BXLIZKqk84cKpPAH4oqV+poY1cgF8wOJhSkQgNzhF9qmVR+SuJW6Eh40enoVKWag 11 | wxx0Vgj5oMwHVX9/7oiVP7k5aXKE6Ftdh5HzJmiivyaDcBzBfBki6zsQ+bdqLm6u 12 | 5evr7hN3xvrbmc1xPjh9mQqKgSQFLRh0ZFy5vcdBcaCnzCVXP2py/kkuLFgw66Ff 13 | Sgtf6dbHfSi8b2Cl2yiM5lnbs2xLAA03N43QnuCg2VXPHl/f7Z6ESkGR+iiaJjme 14 | p+GXwJG8TS2Y/+/lwwZBE7wRaqhpNkGKkdEoMk26IGza39PLUr52tNMzC4ekLC0l 15 | EElNFQIfzM4Tvmmv1hGD 16 | =RC+6 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/53/api-53.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/repos/de/robv/android/xposed/api/53/api-53.jar -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/53/api-53.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: GnuPG v2 3 | 4 | iQIcBAABCAAGBQJW/tOxAAoJEIZbcU6FIQmqxYcP+wTLJl+Yyx5PGh/2Lh3aQG50 5 | dC4WwqSkLfAh+X8kzMFLSV0krO9NFwoGS3UbepWxm0M6lgoKa/n7TEfCRXyN1zmC 6 | 6N0Zjy3IBbEWlcqbMxPOcGy7UNrF6q2+S02ZJM8W3lQHuj2QB5jeuF1yAHGi+cnw 7 | iUIJOyOOzLyJsy2hADeGaDC96qvQp9fS1M92WnKirSW8+9CdEXWyPCaIpjDovH1s 8 | DjUVKi/LYT08WYKvk0PAwem4DdsR0/cTs9qdYDY2MrpPUK2DFXt8gMt3K84/krp2 9 | N6aoR9/dFmBtbiX1n9uNQSWFFafeV9DTK4s4YQMqtA3jxCMoVDd+xzEILG5P4h4r 10 | pwr3zm9ArgPJ2C0zjTP5MPy1lQHuIMMPSFCP0uXLlEm3wKdprf43w8XFbM+y0uK1 11 | nACpbulZrpW9/+kRvXa9SfXI2hms8YUzlUfYIDYw6BUm2IShSP1F4qgNzO/Hilxd 12 | swYy4aSAg6ZOvmOBqFAz0tE2MIHUvM7T8FuPxYWAGHo+pKMahS96YXNgLjWZBKoL 13 | BA3ZRzb2oZO/L48+lD2cpaKEOinobQqlmgRL7+LOhPxCPocFad9tpjSP56Jplq3q 14 | OkUsey2kaV6hQbmHmUic/C5Ug2cd/mdDu2crUYkclYQPFXLhPgO70mvXnL4HXdBr 15 | GV8JAd+2bLnGUXJZy6iK 16 | =YQDd 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/53/api-53.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | de.robv.android.xposed 5 | api 6 | 53 7 | Xposed Framework APIs 8 | 9 | 10 | The Apache Software License, Version 2.0 11 | http://www.apache.org/licenses/LICENSE-2.0.txt 12 | repo 13 | 14 | 15 | 16 | 17 | rovo89 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/53/api-53.pom.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: GnuPG v2 3 | 4 | iQIcBAABCAAGBQJW/tO8AAoJEIZbcU6FIQmq3DAP/3GifOALajluiWHQj5xoXCQX 5 | c5N66Zz65NtMPqBBdA8cgpkPcBjdK8L/NdHl5su+7WQVmcC6J6fcbjHcYK+M6YN4 6 | 6V/+mdKjvWTC+MkBHJYxhjOnkTW/8d6C+boCD1OOzguOnYYPq7bneVIx2w6aPNH3 7 | R2MxsKEvCLBG5QNzdF37EAWBKIwFir6QwXq3Mx2uNvXyVfRFX7zuSK9ZHg4N6nQr 8 | Xu7VnKdDDZ3rF3gOiZWsOL2o2zH4VNVvKMXeKk8vwXlWLYh498MRQ75muZ/iM2Iu 9 | c6swGe2hxL2qO74SNJe2LThp5dXUtbd8ZyZbbyYnXWgMaXfkptjvdhaH6HABzsES 10 | cdxoKEUqkhiaBm9X/bHmlIAmoEF/QXVAJPHl3Ti2fVxc+EMekH445jbzUIQBfEco 11 | XNbiqM1Hr7sYkqrfZmiV1Ms3RX6zlMmF9Vq+WO/JKjex5KY8cEg7rgJ+dc/t0ZtM 12 | 3DGuexlPopbBua0nyxPrQjv3780zebUF4ZroWvG04dr8CCVjLJwX8cO6hnmOymjS 13 | wU2ezBZl7mlBWOYD6ri8CUGVWYxz8h8DAriUPbccDg1sEVDg/otdZx8nGq5fV+vJ 14 | kCdsLmzsXlDMfkEqajWGaNJdqRsIREu2ljoWZu4X5hf0LBzoIMi1G8y2ldHHRdhd 15 | rSWjPuGOBIyAHxlAjca5 16 | =VHed 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/81/api-81-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/repos/de/robv/android/xposed/api/81/api-81-sources.jar -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/81/api-81-sources.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: GnuPG v2 3 | 4 | iQIcBAABCAAGBQJXAWSDAAoJEIZbcU6FIQmqEHgQAI/p1UphJaRvfyI5BNLbVFmu 5 | H9xAXrZYVdzB1xMHWpBewkUl+Vgtd3vJ9iUSxC4Qm9FOb7oxVBKThwVS926bmyJx 6 | kxqtB3rkXNGQS2Li4+FJUHnzdSeZTffBkYGmX2NqZPJpllcnP950t5tFKwstQxSc 7 | JAUwNGBxLV/qmBhtkRqsx9dz5r/r54bzCaS0LktCVaDdkM0gEsg6krgcWaRsjE0p 8 | MK0rX5XzlhDHKj7qmjuJEmDhE5BMRpmlThNcILyy1mBR4M/fA74s3Yc+dSas7J4R 9 | qvE0mXPeAefUMeVRBfIKNJKKCqAUi560gjfGJKs2l+W+gLt4qE9nLxYwq/eoY/Si 10 | 5FCQptFTq8Yf9lV3clgv0MPKVtMq58Jb5mhKX1aunlPjG6DcjmCZfeJbOjWgL0NM 11 | wHM/Loqxi+mZsI6p3u8UOTXHyImNaZup2RQQYONAV+gcwv8ICPhHouBmNo+ncujy 12 | GN8CN+tjTg4qNDKQEF639sMM83Dt983jnL9nGmZ45Lm4V4bmNx9rebxaVhuVHjj0 13 | gQZ3cOAfVhJTdfVJWYeKMQxjS/pAG+nwVMOh2CtmiXmyzR15N0/DfjDJ42FEV766 14 | kEn6bT2E0fsoErD4dkeJNScaUFGIRaKiItYlC/byyd1sjrw4sbR0Vj0uQsGn50TK 15 | xevy9dSHdPQuWgwFhPFD 16 | =PNrY 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/81/api-81.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/repos/de/robv/android/xposed/api/81/api-81.jar -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/81/api-81.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: GnuPG v2 3 | 4 | iQIcBAABCAAGBQJXAWR+AAoJEIZbcU6FIQmqkEgQANlHpB9yfEuSiA3UFdETntJb 5 | tCCO0JNMVkthfQTMDtA7r1Qnl8bMYbw0Dh6qfCH5NP23IhoZYuQMW9sUgmLq/Fhy 6 | Eelz+VgiKqRi4xjLMpgbzbBVT90X1hIlj4LPyqhz7SH+QG35ExmPobiEou5Wv75h 7 | ZPQJvEkvJWXM2Ddbaor8zyYL+yZETl9pfQ7kpYgzDcKNQmpaNgA7JNbsBBSKKUys 8 | WMJ/Na5O76mVGmFQ7xrX0Yv4lk2u9lcW/ylMXaiEhT8zO6RPSs7ooabuzCOXoUb0 9 | l3ZMq1GUZJL2/UDm/B2DyGKDI4JTVz6kPyX4hAbG9vmykKcEXBz0tccuX9Nc9qeg 10 | /JfPunWZ9z3aBJZPwf8QuTkFPXi9hAmp4Rryg277fWc8/8YLRbWwiBkDzzMtlCac 11 | E5JlYRkR+cZ2+3kZoNrq7lN4rhploMBanPRBk/JaMuzMRvYJeYDdgK78YycKndw1 12 | HQkecS8po2OzPfcjrxbOPjrCczoci9l09Bm4JqUtw62UR7fY92KaTJn1PGo2GKsq 13 | rjgDM+uvaz0+KpNowj8yrd8oVduzYwxZdjPqCKob+Cxgceg1Arp57o2IBJaf5KEy 14 | YAt+W2ImY1Jx9zhuwqoSkDVg0rq9urPAZwoI4LxOU9JvTRO6YoXT3K2nU/ch1s2f 15 | +NfCxj7O8xI/J0gbvBAt 16 | =wHG/ 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/81/api-81.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | de.robv.android.xposed 5 | api 6 | 81 7 | Xposed Framework APIs 8 | 9 | 10 | The Apache Software License, Version 2.0 11 | http://www.apache.org/licenses/LICENSE-2.0.txt 12 | repo 13 | 14 | 15 | 16 | 17 | rovo89 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/81/api-81.pom.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: GnuPG v2 3 | 4 | iQIcBAABCAAGBQJXAWSGAAoJEIZbcU6FIQmq7GAQALoB0LbILQFWfm/qfsWJ/YtS 5 | Ntpn4snyJrNxJuJ9XaKWlhKMDoIiPULvL2jw4vaBvg3r8ZbiBi/iqeq04Snoap8S 6 | s7+YrgmMDQ06JYhnUQfRWrbcrasELEAYvphtK1qUjO8JomqV+U/vNtmY6xV7qBNV 7 | wrD/9kUtLPVpEf4JeOtpzADEL9jd0v9pdY/OnqYAPras5yX0vmAzRQLLzvEaievp 8 | JY/QvnL1+aPgqAkOytOK4/Lt2RSbUTbDPD1yShPbXXyZbRAN4LmlRPavXo0l52lf 9 | sOEbzDLKP8sNdRTek/emRs/c6bC+12bDmLGFD1Oi74eR6y+GLR07X/5lWGAAZ2co 10 | z+AZ8723ZvspYpYyNYVfUymTJaIsD93Cou6fjSw/9HYSPCPdbUkOC0fJkmoh4jqO 11 | f8fkXvWpBQctRnFmCi1hMuDAUS2E+pkU3sJFTWh9wS874PHAYnUlHfCBXLTTimWn 12 | jZDaGprozCCnFQeHRvMrp/Udq7yOl5Qa6NZA5XBRgYQ43acbp5Cl26Fc0j7lUYQS 13 | ME7iXWJjHUMalCSJa/Yw+cwJmu2xXVsSfeqWQSDWLnANHsDHPNIAUw4yRnwA0f6B 14 | zdKJ9oLqwWhr6d6APyjQxxFLdKu9YAtzHyOrF0JUUA/7TQtp08s0SrpFRiQykZIV 15 | s4bKHTSFdNAhHEIe1wFV 16 | =qUTK 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/82/api-82-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/repos/de/robv/android/xposed/api/82/api-82-sources.jar -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/82/api-82-sources.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: GnuPG v2 3 | 4 | iQIcBAABCAAGBQJXESNTAAoJEEAB51hULib1z3AP/RJN08iOA8UA5KZWnj7qPu+v 5 | VUcymfAaTrhePb1pssHpDvT7vRaBOoVuc+xsXzW4zA0Z8XwhYFrqqFdX2b8uRlZR 6 | tB0oLKZTHTcJhQ3zvwrJaTx3IyxgYhCKOTBlzb1V1VQzBQvPR1MJr2WZev4hEAUu 7 | lT2qPJpOSOpZIzVIH8ACLbxa1HzuqGZOgtBom8OA2VksjHcpkZ/qtNw+3DGP8rDE 8 | 3BGfIdvWCZkckbLNNQTbh56ViRsDpgr7ouRY4NVbITgGK0YzB4zJgjtelTuApLMi 9 | 0Z0LrV9tr5GZPgVM8xzU7+RglFRkEY3lIpWnng+kTHOj3sr3B1veOipHx9g6uImW 10 | 3oMzNCZwbRaQNinRowWUuNEiop9ukbqJ3TYPB0xpFMVxPpCKyGrlSLQzdGBVEAYf 11 | L5LxsTEoaFQtMQtO+9jO5fid/G/3ELo1Se5bemSk1m6CcofN9pPYLlB9NedKrEwI 12 | BclLIaFl22k8SoBkPIttf8+gbZsVwkZeQE1ixkGqInJBTYdj3N/bpSFRHFdYNBhg 13 | 3SpO8+9OPlJGeAA8+ilw/YfhEv0avbPOZCDelvrHrwyPthDQD1LenuSbY5Nhm2sV 14 | wujXdVrYB3ZoLqXfiUeS/hABM+IuhDdl1vsgkug767nw9Z/0lC397IX0LMysJ5CQ 15 | aIZpvbVhpZo13Qcawhgl 16 | =wyx+ 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/82/api-82.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/repos/de/robv/android/xposed/api/82/api-82.jar -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/82/api-82.jar.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: GnuPG v2 3 | 4 | iQIcBAABCAAGBQJXESNQAAoJEEAB51hULib1pO8P/jse7zfrayL8jAo249JucIZw 5 | QZwRPh1UR8Maib/5WN8ToxaoK2zy9SeMJqLcWPEcJcX0jablU8HFShAlS9LenqzV 6 | 4Sp3rQCTwpwi4OomqKxoEyFcvAq5bvMjEcX0Afa7zYs2CXmim5D/EmJcPW32St2h 7 | 32pkL5VL/B4INC+wXgdz4C7jUZL14h+9gU0QqzaA7eluMlF36u4uK3Hu+kEQ1dyg 8 | 6tAcZ1m3SHR/LI9Hntod2LdYRwIFwwaj38vCHdp7+JZF1CYhuPS0mwkq0I8VUE59 9 | kxUHGWv/SM6p9g51qLIO2RwT6tCSjx2dbWSf/DpsrFDL8VHpGJU2JLaMP4iqwUGd 10 | ZoNITPoxBZTESQhi+JEzwsx/heMqXPofmDZnNMqbSaskVqYzjg2IYqCfCxPpi2J+ 11 | 4Pbcv4UjTq51R6WsVQAundqCuWd5bYLMT/MMoktdV1UtSqw5pt2xabvgWVtGpl8f 12 | cstp61PpuJBjRNW+IxNpD2VJWA7UKVQ4wVrFC/KseXgcr0OCn5Ea7RYzoiyK9nhL 13 | 8l4ehQBfnf1ZqjmPcsZus0YjqA1L7nnI3uVPQw2OU/gE3Uii5WR/aDHDTFZWcXwi 14 | cFHc/6rPE+YtimjObARA2T8FSQZjV7lzeTkqUUGWjDRGuW3jauNmYacdy2HydSyh 15 | dWECeVFc6Jglwt5sM8Rz 16 | =Xaxk 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/82/api-82.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | de.robv.android.xposed 5 | api 6 | 82 7 | Xposed Framework APIs 8 | 9 | 10 | The Apache Software License, Version 2.0 11 | http://www.apache.org/licenses/LICENSE-2.0.txt 12 | repo 13 | 14 | 15 | 16 | 17 | rovo89 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/82/api-82.pom.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | Version: GnuPG v2 3 | 4 | iQIcBAABCAAGBQJXESNVAAoJEEAB51hULib1VUkP+wYxZUpPzr2NPFIexTcU6p9H 5 | 4vgtQuH6OWqDgDc1K+DEghpzEyl6/vS1AnEJr53wIUdycYBlYpk8oT0v1GKJMHUB 6 | BghtaV2O5Q3umfAou3/zqPzi8xiPMJMejZEJeMLVufAfPJJLrcFXQamzileK0L2g 7 | uwli2QG5Iwcrub5LFs/+WSF1xLu7OPF8eKDCvToHZ8/ReUACmkAu7iga3A4QSSVp 8 | pwWSBZSVNfi1AVlBjUmjtzKaJGA2UasDlwHzhKOf0YqJzuj/Zhu9CBgVDTvKI1wp 9 | PQUXL1FMucn3fKQ7jIup8GHvA862f2/xBWGHZEJCuOgVQzxps+g4KHYCsgZh3/3s 10 | q1BkfMUoDfDfLM283+yFahiD4f1odg3CudNu0TLcgaWqUVeE3p6gORvtjx/DXybG 11 | sOkFSn+KJntEk80fKnHGXbFRzG7QEItRaNEWH7vFgLOx/C+07XNEvPOU/3h5BNiu 12 | 3NhJbJxhReVClzJnE6+SyG/564459WWwBDFWqx1FIq9KSq0MTH1BGFzd/5r6RuJn 13 | 04Slj88fK33IUrcdgHLTUwEkM9ZThV9cZ3XSs63XkegalZAW4iXTeKDvg8RXcST8 14 | 5wDrZmQSddJ5sW47DVazfTyoWOXkW2R/nSOdzlR/L38Du9dKuJFqa2wu3/aaqvmQ 15 | Lkzhp7KsGwRQN6OaA19S 16 | =G5sX 17 | -----END PGP SIGNATURE----- 18 | -------------------------------------------------------------------------------- /repos/de/robv/android/xposed/api/maven-metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | de.robv.android.xposed 4 | api 5 | 82 6 | 7 | 82 8 | 82 9 | 10 | 53 11 | 81 12 | 82 13 | 14 | 20160415182329 15 | 16 | 17 | -------------------------------------------------------------------------------- /screenshots/detail_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/screenshots/detail_page.png -------------------------------------------------------------------------------- /screenshots/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/screenshots/screenshot.png -------------------------------------------------------------------------------- /screenshots/showcase.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/screenshots/showcase.webp -------------------------------------------------------------------------------- /screenshots/variable_font_showcase.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yubyf/QuoteLockX/7e82ba2c535ed6f68a81c762805038f2b8bf0e4f/screenshots/variable_font_showcase.webp -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | 14 | // Local repos 15 | maven { 16 | url = uri("repos") 17 | } 18 | } 19 | } 20 | rootProject.name = "QuoteLockX" 21 | include(":app") 22 | --------------------------------------------------------------------------------