├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── ua │ │ └── polodarb │ │ └── gmsflags │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── aidl │ │ └── ua │ │ │ └── polodarb │ │ │ └── gmsflags │ │ │ └── IRootDatabase.aidl │ ├── assets │ │ ├── suggestedFlags.json │ │ ├── suggestedFlags_2.0.json │ │ └── suggestedFlags_2.0_for_beta.json │ ├── ic_launcher-playstore.png │ ├── java │ │ └── ua │ │ │ └── polodarb │ │ │ └── gmsflags │ │ │ ├── GMSApplication.kt │ │ │ ├── core │ │ │ └── platform │ │ │ │ └── activity │ │ │ │ └── BaseActivity.kt │ │ │ ├── data │ │ │ ├── AppInfo.kt │ │ │ ├── constants │ │ │ │ └── SortingTypeConstants.kt │ │ │ ├── databases │ │ │ │ ├── gms │ │ │ │ │ └── RootDatabase.kt │ │ │ │ └── local │ │ │ │ │ ├── AppDatabase.kt │ │ │ │ │ ├── dao │ │ │ │ │ ├── FlagsDAO.kt │ │ │ │ │ └── PackagesDAO.kt │ │ │ │ │ └── enities │ │ │ │ │ ├── SavedFlags.kt │ │ │ │ │ └── SavedPackages.kt │ │ │ ├── parser │ │ │ │ └── xml │ │ │ │ │ └── XmlFlagsParser.kt │ │ │ ├── prefs │ │ │ │ └── shared │ │ │ │ │ └── PreferencesManager.kt │ │ │ ├── remote │ │ │ │ ├── DefaultConfig.kt │ │ │ │ ├── Resource.kt │ │ │ │ ├── flags │ │ │ │ │ ├── FlagsApiService.kt │ │ │ │ │ ├── FlagsApiServiceImpl.kt │ │ │ │ │ └── dto │ │ │ │ │ │ └── SuggestedFlagInfo.kt │ │ │ │ ├── github │ │ │ │ │ ├── GithubApiService.kt │ │ │ │ │ ├── GithubApiServiceImpl.kt │ │ │ │ │ └── dto │ │ │ │ │ │ └── ReleaseInfo.kt │ │ │ │ └── googleUpdates │ │ │ │ │ ├── GoogleAppUpdatesService.kt │ │ │ │ │ ├── GoogleAppUpdatesServiceImpl.kt │ │ │ │ │ └── dto │ │ │ │ │ └── RssModels.kt │ │ │ ├── repo │ │ │ │ ├── AppsListRepository.kt │ │ │ │ ├── GmsDBRepository.kt │ │ │ │ ├── RoomDBRepository.kt │ │ │ │ ├── SettingsRepository.kt │ │ │ │ ├── interactors │ │ │ │ │ └── GmsDBInteractor.kt │ │ │ │ └── mappers │ │ │ │ │ ├── GoogleUpdatesMapper.kt │ │ │ │ │ └── MergeAllTypesFlags.kt │ │ │ └── workers │ │ │ │ └── GoogleUpdatesCheckWorker.kt │ │ │ ├── di │ │ │ ├── AppModule.kt │ │ │ ├── DatabaseModule.kt │ │ │ ├── InteractorsModule.kt │ │ │ ├── RemoteModule.kt │ │ │ ├── RepositoryModule.kt │ │ │ ├── ViewModelsModule.kt │ │ │ └── WorkersModule.kt │ │ │ ├── ui │ │ │ ├── CrashActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── animations │ │ │ │ └── ScreensAnimation.kt │ │ │ ├── components │ │ │ │ ├── UpdateDialog.kt │ │ │ │ ├── buttons │ │ │ │ │ └── fab │ │ │ │ │ │ └── GFlagsFAB.kt │ │ │ │ ├── chips │ │ │ │ │ ├── filter │ │ │ │ │ │ ├── GFlagFilterChip.kt │ │ │ │ │ │ └── GFlagFilterChipRow.kt │ │ │ │ │ └── types │ │ │ │ │ │ ├── GFlagTypesChip.kt │ │ │ │ │ │ └── GFlagTypesChipsRow.kt │ │ │ │ ├── dropDown │ │ │ │ │ ├── FlagChangeDropDown.kt │ │ │ │ │ └── FlagSelectDropDown.kt │ │ │ │ ├── inserts │ │ │ │ │ ├── ErrorLoadScreen.kt │ │ │ │ │ ├── LoadingProgressBar.kt │ │ │ │ │ ├── NotFoundContent.kt │ │ │ │ │ └── NotImplementedScreen.kt │ │ │ │ ├── searchBar │ │ │ │ │ └── GFlagsSearchBar.kt │ │ │ │ └── tabs │ │ │ │ │ ├── CustomTabIndicator.kt │ │ │ │ │ ├── CustomTabIndicatorAnimation.kt │ │ │ │ │ ├── GFlagsTab.kt │ │ │ │ │ └── GFlagsTabRow.kt │ │ │ ├── navigation │ │ │ │ ├── AppNavigation.kt │ │ │ │ ├── NavExtensions.kt │ │ │ │ ├── NavigationBarUI.kt │ │ │ │ └── RootAppNavigation.kt │ │ │ ├── screens │ │ │ │ ├── RootScreen.kt │ │ │ │ ├── UiStates.kt │ │ │ │ ├── firstStart │ │ │ │ │ ├── RequestNotificationPermissionScreen.kt │ │ │ │ │ ├── RootRequestScreen.kt │ │ │ │ │ └── WelcomeScreen.kt │ │ │ │ ├── flagChange │ │ │ │ │ ├── FlagChangeScreen.kt │ │ │ │ │ ├── FlagChangeScreenViewModel.kt │ │ │ │ │ ├── FlagsItemTypes.kt │ │ │ │ │ ├── dialogs │ │ │ │ │ │ ├── AddFlagDialog.kt │ │ │ │ │ │ ├── FlagChangeDialog.kt │ │ │ │ │ │ ├── ProgressDialog.kt │ │ │ │ │ │ ├── ReportFlagsDialog.kt │ │ │ │ │ │ └── SuggestFlagsDialog.kt │ │ │ │ │ ├── extScreens │ │ │ │ │ │ ├── AddMultipleFlags.kt │ │ │ │ │ │ ├── AddMultipleFlagsContent.kt │ │ │ │ │ │ └── AddMultipleFlagsViewModel.kt │ │ │ │ │ └── flagsType │ │ │ │ │ │ ├── BoolFlags.kt │ │ │ │ │ │ └── OtherTypesFlags.kt │ │ │ │ ├── history │ │ │ │ │ └── HistoryScreen.kt │ │ │ │ ├── loadFile │ │ │ │ │ ├── LoadFileScreen.kt │ │ │ │ │ └── LoadFileScreenViewModel.kt │ │ │ │ ├── packages │ │ │ │ │ ├── PackagesScreen.kt │ │ │ │ │ └── PackagesScreenViewModel.kt │ │ │ │ ├── saved │ │ │ │ │ ├── SavedFlagsScreen.kt │ │ │ │ │ ├── SavedPackagesScreen.kt │ │ │ │ │ ├── SavedScreen.kt │ │ │ │ │ └── SavedScreenViewModel.kt │ │ │ │ ├── search │ │ │ │ │ ├── SearchScreen.kt │ │ │ │ │ ├── SearchScreenViewModel.kt │ │ │ │ │ ├── dialog │ │ │ │ │ │ ├── AddPackageDialog.kt │ │ │ │ │ │ ├── SearchScreenDialog.kt │ │ │ │ │ │ └── SortAppsDialog.kt │ │ │ │ │ └── subScreens │ │ │ │ │ │ ├── SearchAppsScreen.kt │ │ │ │ │ │ ├── SearchFlagsScreen.kt │ │ │ │ │ │ └── SearchPackagesScreen.kt │ │ │ │ ├── settings │ │ │ │ │ ├── SettingsScreen.kt │ │ │ │ │ ├── SettingsViewModel.kt │ │ │ │ │ ├── common │ │ │ │ │ │ ├── ConfirmationDialog.kt │ │ │ │ │ │ └── HeaderWithIcon.kt │ │ │ │ │ └── screens │ │ │ │ │ │ ├── about │ │ │ │ │ │ └── AboutScreen.kt │ │ │ │ │ │ ├── resetFlags │ │ │ │ │ │ └── ResetFlagsScreen.kt │ │ │ │ │ │ ├── resetSaved │ │ │ │ │ │ └── ResetSavedScreen.kt │ │ │ │ │ │ └── startRoute │ │ │ │ │ │ └── ChangeNavigationScreen.kt │ │ │ │ ├── suggestions │ │ │ │ │ ├── SuggestedFlag.kt │ │ │ │ │ ├── SuggestionScreenViewModel.kt │ │ │ │ │ ├── SuggestionsScreen.kt │ │ │ │ │ └── dialog │ │ │ │ │ │ └── ResetFlagToDefaultDialog.kt │ │ │ │ └── updates │ │ │ │ │ ├── UpdatesScreen.kt │ │ │ │ │ └── UpdatesScreenViewModel.kt │ │ │ └── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ └── utils │ │ │ ├── Constants.kt │ │ │ └── Extensions.kt │ └── res │ │ ├── drawable │ │ ├── error_image.xml │ │ ├── ic_activate_all.xml │ │ ├── ic_add_flag_list.xml │ │ ├── ic_disable_selected.xml │ │ ├── ic_enable_selected.xml │ │ ├── ic_filter.xml │ │ ├── ic_flag_reset_new.xml │ │ ├── ic_force_stop.xml │ │ ├── ic_github.xml │ │ ├── ic_home.xml │ │ ├── ic_info.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_dark.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_launcher_light.xml │ │ ├── ic_navbar_apps.xml │ │ ├── ic_navbar_history.xml │ │ ├── ic_navbar_packages.xml │ │ ├── ic_navbar_suggestions_active.xml │ │ ├── ic_navbar_suggestions_inactive.xml │ │ ├── ic_next.xml │ │ ├── ic_notify_logo.xml │ │ ├── ic_open_app_settings.xml │ │ ├── ic_packages.xml │ │ ├── ic_question.xml │ │ ├── ic_refresh_flags_list.xml │ │ ├── ic_report.xml │ │ ├── ic_report_fill.xml │ │ ├── ic_reset.xml │ │ ├── ic_reset_flags.xml │ │ ├── ic_reset_saved.xml │ │ ├── ic_save_active.xml │ │ ├── ic_save_inactive.xml │ │ ├── ic_select_all.xml │ │ ├── ic_sort.xml │ │ ├── ic_suggestions.xml │ │ ├── ic_telegram.xml │ │ ├── ic_update_app.xml │ │ ├── ic_updates_active.xml │ │ ├── ic_updates_inactive.xml │ │ ├── img_settings_reset_flags.xml │ │ ├── img_settings_reset_saved.xml │ │ ├── notifiaction_request_welcome.xml │ │ ├── root_image.xml │ │ ├── star_background.xml │ │ └── welcome_image.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night-v31 │ │ └── splash.xml │ │ ├── values-night │ │ ├── splash.xml │ │ └── themes.xml │ │ ├── values-ru │ │ └── strings.xml │ │ ├── values-uk │ │ └── strings.xml │ │ ├── values-v31 │ │ ├── colors.xml │ │ └── splash.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── ic_launcher_background.xml │ │ ├── splash.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ ├── data_extraction_rules.xml │ │ └── filepaths.xml │ └── test │ └── java │ └── ua │ └── polodarb │ └── gmsflags │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── config └── detekt │ └── detekt.yml ├── gf_banner.png ├── gf_root.png ├── gh_download.png ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | keystore.properties 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Danyil Kobzar 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GMS-Flags 2 | ![Downloads](https://img.shields.io/github/downloads/polodarb/GMS-Flags/total?style=for-the-badge) 3 | ![Start](https://img.shields.io/github/stars/polodarb/GMS-Flags?style=for-the-badge) 4 | ![License](https://img.shields.io/github/license/polodarb/GMS-Flags?style=for-the-badge) 5 | [![Telegram](https://img.shields.io/badge/telegram-telegram?style=for-the-badge&logo=telegram&logoColor=white&label=GMS%20Flags&color=%23229ED9)](https://t.me/gmsflags) 6 | 7 | ![Banner](gf_banner.png) 8 | ![Root](gf_root.png) 9 | 10 | ## Features: 11 | - **Suggestions screen** - This is a screen that prompts the user to activate some interesting flags. The suggested flags allow the user to read some notes, open the application itself and its settings. It is possible to reset the flag value, see more detailed information or report some problem to the developer. 12 | - **Apps screen** - This screen generates a list of applications and their associated list of packages with flags within them. This allows you to quickly and conveniently find the appropriate flag. 13 | - **Saved screen** - Display lists of user-saved packages and flags. 14 | - **Packages screen** - Display a list of all packages in alphabetical order that contain flags. There is a search and an option to save the package for quick access. 15 | - **Flag Change screen** - The most important screen, which allows you to change the parameters of the flags. Flags are divided into 4 different types - Boolean/Int/Float/String. Screen functionality: 16 | - Ability to select some or all booleans flags and activate or deactivate them in one click. 17 | - Ability to save all selected flags in one click. 18 | - Ability to send a list of selected flags to another application. 19 | - Ability to suggest or report a problem about a flag or set of flags. 20 | - For Int/Float/String flags, it is possible to delete an overwritten value. 21 | - Filtering of flags by modified, activated and deactivated flags. 22 | - Ability to add a flag manually. 23 | - Ability to delete all flags changes, e.g. if it caused any problems. 24 | - **Add Multiple Flags screen** - Ability to record a large set of flags of all available types at once. 25 | - **Settings screen** - Settings allow you to delete ALL overwritten flags and saved flags/packages. It is also possible to assign a start screen when launching the application and the ability to view more detailed information about the GMS Flags. 26 | 27 | ## Note 28 | > [!IMPORTANT] 29 | > It usually takes 1 to 3 force-stops for a flag to be applied. If the flag still does not apply, you should wait 24 hours or reset the application data. 30 | 31 | # Download 32 | [Get it on GitHub](https://github.com/polodarb/GMS-Flags/releases/download/1.1.0/gms_flags_1.1.0.apk) 35 | 36 | ## License 37 | 38 | ```MIT License 39 | 40 | Copyright (c) 2023 Danyil Kobzar 41 | 42 | Permission is hereby granted, free of charge, to any person obtaining a copy 43 | of this software and associated documentation files (the "Software"), to deal 44 | in the Software without restriction, including without limitation the rights 45 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 46 | copies of the Software, and to permit persons to whom the Software is 47 | furnished to do so, subject to the following conditions: 48 | 49 | The above copyright notice and this permission notice shall be included in all 50 | copies or substantial portions of the Software. 51 | 52 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 53 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 54 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 55 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 56 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 57 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 58 | SOFTWARE. 59 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | google-services.json -------------------------------------------------------------------------------- /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.kts. 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 | -dontwarn org.slf4j.LoggerFactory 23 | -dontobfuscate 24 | -keepattributes LineNumberTable,SourceFile 25 | -renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/ua/polodarb/gmsflags/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("ui.polodarb.gmsflags", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 25 | 31 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 67 | 72 | 75 | 76 | 77 | 82 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /app/src/main/aidl/ua/polodarb/gmsflags/IRootDatabase.aidl: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags; 2 | 3 | interface IRootDatabase { 4 | Map getGmsPackages(); 5 | List getGooglePackages(); 6 | 7 | Map getBoolFlags(String pkgName); 8 | Map getIntFlags(String pkgName); 9 | Map getFloatFlags(String pkgName); 10 | Map getStringFlags(String pkgName); 11 | 12 | Map getOverriddenBoolFlagsByPackage(String pkgName); 13 | Map getOverriddenIntFlagsByPackage(String pkgName); 14 | Map getOverriddenFloatFlagsByPackage(String pkgName); 15 | Map getOverriddenStringFlagsByPackage(String pkgName); 16 | 17 | Map getAllBoolFlags(); 18 | Map getAllIntFlags(); 19 | Map getAllFloatFlags(); 20 | Map getAllStringFlags(); 21 | 22 | Map getAllOverriddenBoolFlags(); 23 | Map getAllOverriddenIntFlags(); 24 | Map getAllOverriddenFloatFlags(); 25 | Map getAllOverriddenStringFlags(); 26 | 27 | List getListByPackages(String pkgName); 28 | 29 | String androidPackage(String pkgName); 30 | List getUsers(); 31 | 32 | void deleteAllOverriddenFlagsFromGMS(); 33 | void deleteAllOverriddenFlagsFromPlayStore(); 34 | void deleteRowByFlagName(String packageName, String name); 35 | void deleteOverriddenFlagByPackage(String packageName); 36 | 37 | void overrideFlag( 38 | String packageName, 39 | String user, 40 | String name, 41 | int flagType, 42 | String intVal, 43 | String boolVal, 44 | String floatVal, 45 | String stringVal, 46 | String extensionVal, 47 | int committed 48 | ); 49 | } -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/core/platform/activity/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.core.platform.activity 2 | 3 | import android.Manifest 4 | import android.app.NotificationChannel 5 | import android.app.NotificationManager 6 | import android.content.Context 7 | import android.content.pm.PackageManager 8 | import android.os.Bundle 9 | import androidx.activity.ComponentActivity 10 | import androidx.core.app.ActivityCompat 11 | import androidx.core.app.NotificationCompat 12 | import androidx.core.app.NotificationManagerCompat 13 | import ua.polodarb.gmsflags.R 14 | 15 | const val CHANNEL_ID = "gms_flags_notify_channel" 16 | 17 | open class BaseActivity: ComponentActivity() { 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | createNotificationChannel() 22 | } 23 | 24 | private fun createNotificationChannel() { 25 | val name = "Google app updates" 26 | val importance = NotificationManager.IMPORTANCE_DEFAULT 27 | val channel = NotificationChannel(CHANNEL_ID, name, importance) 28 | 29 | val notificationManager: NotificationManager = 30 | getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 31 | notificationManager.createNotificationChannel(channel) 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data 2 | 3 | import android.content.pm.ApplicationInfo 4 | import android.content.pm.PackageInfo 5 | import android.content.pm.PackageManager 6 | import android.graphics.drawable.Drawable 7 | 8 | class AppInfo( 9 | pm: PackageManager, 10 | val applicationInfo: ApplicationInfo, 11 | val packageInfo: PackageInfo? 12 | ) { 13 | val appName: String = applicationInfo.loadLabel(pm) as String 14 | val icon: Drawable = pm.getApplicationIcon(applicationInfo.packageName) 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/constants/SortingTypeConstants.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.constants 2 | 3 | object SortingTypeConstants { 4 | 5 | const val APP_NAME = "APP_NAME" 6 | 7 | const val APP_NAME_REVERSED = "APP_NAME_REVERSED" 8 | 9 | const val LAST_UPDATE = "LAST_UPDATE" 10 | 11 | const val PACKAGE_NAME = "PACKAGE_NAME" 12 | 13 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/databases/local/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.databases.local 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import ua.polodarb.gmsflags.data.databases.local.dao.FlagsDAO 6 | import ua.polodarb.gmsflags.data.databases.local.dao.PackagesDAO 7 | import ua.polodarb.gmsflags.data.databases.local.enities.SavedFlags 8 | import ua.polodarb.gmsflags.data.databases.local.enities.SavedPackages 9 | 10 | 11 | @Database( 12 | entities = [SavedPackages::class, SavedFlags::class], 13 | version = 1 14 | ) 15 | abstract class AppDatabase : RoomDatabase() { 16 | 17 | abstract fun packagesDao(): PackagesDAO 18 | 19 | abstract fun flagsDao(): FlagsDAO 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/databases/local/dao/FlagsDAO.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.databases.local.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import kotlinx.coroutines.flow.Flow 8 | import ua.polodarb.gmsflags.data.databases.local.enities.SavedFlags 9 | 10 | @Dao 11 | interface FlagsDAO { 12 | @Query("SELECT * FROM saved_flags") 13 | fun getSavedFlags(): Flow> 14 | 15 | @Insert(entity = SavedFlags::class, onConflict = OnConflictStrategy.REPLACE) 16 | suspend fun saveFlag(flagName: SavedFlags) 17 | 18 | @Query("DELETE FROM saved_flags WHERE flag_name = :flagName AND pkg_name = :pkgName") 19 | suspend fun deleteSavedFlag(flagName: String, pkgName: String) 20 | 21 | @Query("DELETE FROM saved_flags") 22 | fun deleteAllSavedFlags() 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/databases/local/dao/PackagesDAO.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.databases.local.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import kotlinx.coroutines.flow.Flow 8 | import ua.polodarb.gmsflags.data.databases.local.enities.SavedPackages 9 | 10 | @Dao 11 | interface PackagesDAO { 12 | 13 | @Query("SELECT * FROM saved_packages") 14 | fun getSavedPackages(): Flow> 15 | 16 | @Insert(entity = SavedPackages::class, onConflict = OnConflictStrategy.REPLACE) 17 | suspend fun savePackage(pkgName: SavedPackages) 18 | 19 | @Query("DELETE FROM saved_packages WHERE pkg_name = :pkgName") 20 | suspend fun deleteSavedPackage(pkgName: String) 21 | 22 | @Query("DELETE FROM saved_packages") 23 | fun deleteAllSavedPackages() 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/databases/local/enities/SavedFlags.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.databases.local.enities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(tableName = "saved_flags") 8 | data class SavedFlags( 9 | @ColumnInfo(name = "pkg_name") val pkgName: String, 10 | @ColumnInfo(name = "flag_name") val flagName: String, 11 | @ColumnInfo(name = "flag_type") val type: String, 12 | @PrimaryKey(autoGenerate = true) val id: Int = 0 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/databases/local/enities/SavedPackages.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.databases.local.enities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(tableName = "saved_packages") 8 | data class SavedPackages( 9 | @PrimaryKey @ColumnInfo(name = "pkg_name") val pkgName: String 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/parser/xml/XmlFlagsParser.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.parser.xml 2 | 3 | class XmlFlagsParser { 4 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/prefs/shared/PreferencesManager.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.prefs.shared 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | 6 | object PreferenceConstants { 7 | const val PREFERENCES_NAME = "gms_flags_prefs" 8 | const val START_SCREEN_KEY = "settings_navigation" 9 | const val GOOGLE_LAST_UPDATE = "google_last_update" 10 | } 11 | 12 | class PreferencesManager(context: Context) { 13 | private val sharedPreferences: SharedPreferences = 14 | context.getSharedPreferences(PreferenceConstants.PREFERENCES_NAME, Context.MODE_PRIVATE) 15 | 16 | fun saveData(key: String, value: String) { 17 | val editor = sharedPreferences.edit() 18 | editor.putString(key, value) 19 | editor.apply() 20 | } 21 | 22 | fun getData(key: String, defaultValue: String): String { 23 | return sharedPreferences.getString(key, defaultValue) ?: defaultValue 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/remote/DefaultConfig.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.remote 2 | 3 | import android.util.Log 4 | import io.ktor.client.HttpClientConfig 5 | import io.ktor.client.plugins.HttpTimeout 6 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation 7 | import io.ktor.client.plugins.logging.LogLevel 8 | import io.ktor.client.plugins.logging.Logger 9 | import io.ktor.client.plugins.logging.Logging 10 | import io.ktor.serialization.kotlinx.json.json 11 | import kotlinx.serialization.json.Json 12 | import ua.polodarb.gmsflags.BuildConfig 13 | 14 | fun HttpClientConfig<*>.setConfig(tag: String) { 15 | install(Logging) { this.setConfig(tag = tag) } 16 | install(ContentNegotiation) { this.setConfig() } 17 | install(HttpTimeout) { this.setConfig() } 18 | } 19 | 20 | private fun Logging.Config.setConfig(tag: String) { 21 | this.level = if (BuildConfig.DEBUG) LogLevel.ALL else LogLevel.NONE 22 | this.logger = object: Logger { 23 | override fun log(message: String) { 24 | Log.e(tag, message) 25 | } 26 | } 27 | } 28 | 29 | private fun ContentNegotiation.Config.setConfig() { 30 | this.json(json = Json { 31 | ignoreUnknownKeys = true 32 | isLenient = true 33 | }) 34 | } 35 | 36 | private const val TIMEOUT = 5000L 37 | 38 | private fun HttpTimeout.HttpTimeoutCapabilityConfiguration.setConfig() { 39 | requestTimeoutMillis = TIMEOUT 40 | connectTimeoutMillis = TIMEOUT 41 | socketTimeoutMillis = TIMEOUT 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/remote/Resource.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.remote 2 | 3 | sealed class Resource(val data: T? = null, val message: String? = null) { 4 | class Success(data: T) : Resource(data) 5 | class Error(exception: Exception? = null, data: T? = null) : 6 | Resource(data, exception?.message) 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/remote/flags/FlagsApiService.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.remote.flags 2 | 3 | import ua.polodarb.gmsflags.data.remote.Resource 4 | import ua.polodarb.gmsflags.data.remote.flags.dto.SuggestedFlagTypes 5 | 6 | interface FlagsApiService { 7 | suspend fun getSuggestedFlags(): Resource 8 | } 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/remote/flags/FlagsApiServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.remote.flags 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.call.body 5 | import io.ktor.client.engine.HttpClientEngine 6 | import io.ktor.client.plugins.defaultRequest 7 | import io.ktor.client.request.get 8 | import io.ktor.client.request.url 9 | import kotlinx.serialization.json.Json 10 | import ua.polodarb.gmsflags.BuildConfig 11 | import ua.polodarb.gmsflags.data.remote.Resource 12 | import ua.polodarb.gmsflags.data.remote.flags.dto.SuggestedFlagTypes 13 | import ua.polodarb.gmsflags.data.remote.setConfig 14 | 15 | private const val BASE_URL = "https://raw.githubusercontent.com/polodarb/GMS-Flags/" 16 | private const val ASSETS_PATH = "/app/src/main/assets/" 17 | private const val LOG_TAG = "FlagsApiService" 18 | 19 | class FlagsApiServiceImpl( 20 | engine: HttpClientEngine 21 | ): FlagsApiService { 22 | private val client = HttpClient(engine) { 23 | this.setConfig(LOG_TAG) 24 | defaultRequest { 25 | url(BASE_URL + (if (BuildConfig.DEBUG) "develop" else "master") + ASSETS_PATH) 26 | } 27 | } 28 | 29 | override suspend fun getSuggestedFlags(): Resource { 30 | return try { 31 | val url = if (BuildConfig.VERSION_NAME.contains("beta")) { 32 | "suggestedFlags_2.0_for_beta.json" 33 | } else { 34 | "suggestedFlags_2.0.json" 35 | } 36 | 37 | val response: String = client.get { url(url) }.body() 38 | Resource.Success(Json.decodeFromString(response)) 39 | } catch (e: Exception) { 40 | Resource.Error(e) 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/remote/flags/dto/SuggestedFlagInfo.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.remote.flags.dto 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class SuggestedFlagTypes( 8 | @SerialName("primary") val primary: List, 9 | @SerialName("secondary") val secondary: List 10 | ) 11 | 12 | @Serializable 13 | data class Primary( 14 | @SerialName("primaryTag") val primaryTag: String, 15 | @SerialName("name") val name: String, 16 | @SerialName("source") val source: String?, 17 | @SerialName("note") val note: String?, 18 | @SerialName("flags") val flags: List, 19 | @SerialName("flagPackage") val flagPackage: String, 20 | @SerialName("appPackage") val appPackage: String, 21 | @SerialName("minAppVersionCode") val minVersionCode: Int?, 22 | @SerialName("minAndroidSdkCode") val minAndroidSdkCode: Int?, 23 | @SerialName("details") val details: String?, 24 | @SerialName("enabled") val enabled: Boolean 25 | ) 26 | 27 | @Serializable 28 | data class Secondary( 29 | @SerialName("name") val name: String, 30 | @SerialName("source") val source: String, 31 | @SerialName("note") val note: String?, 32 | @SerialName("flags") val flags: List, 33 | @SerialName("flagPackage") val flagPackage: String, 34 | @SerialName("appPackage") val appPackage: String, 35 | @SerialName("minAppVersionCode") val minVersionCode: Int?, 36 | @SerialName("minAndroidSdkCode") val minAndroidSdkCode: Int?, 37 | @SerialName("details") val details: String?, 38 | @SerialName("enabled") val enabled: Boolean 39 | ) 40 | 41 | @Serializable 42 | data class FlagInfo( 43 | @SerialName("tag") val tag: String, 44 | @SerialName("type") val type: FlagType, 45 | @SerialName("value") val value: String, 46 | ) 47 | 48 | enum class FlagType { 49 | @SerialName("bool") BOOL, 50 | @SerialName("int") INTEGER, 51 | @SerialName("float") FLOAT, 52 | @SerialName("string") STRING 53 | } 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubApiService.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.remote.github 2 | 3 | import ua.polodarb.gmsflags.data.remote.Resource 4 | import ua.polodarb.gmsflags.data.remote.github.dto.ReleaseInfo 5 | 6 | interface GithubApiService { 7 | suspend fun getLatestRelease(): Resource 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubApiServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.remote.github 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.call.body 5 | import io.ktor.client.engine.HttpClientEngine 6 | import io.ktor.client.plugins.defaultRequest 7 | import io.ktor.client.request.get 8 | import io.ktor.client.request.url 9 | import ua.polodarb.gmsflags.data.remote.Resource 10 | import ua.polodarb.gmsflags.data.remote.github.dto.ReleaseInfo 11 | import ua.polodarb.gmsflags.data.remote.setConfig 12 | 13 | private const val BASE_URL = "https://api.github.com" 14 | const val LOG_TAG = "GithubApiService" 15 | 16 | class GithubApiServiceImpl( 17 | engine: HttpClientEngine 18 | ): GithubApiService { 19 | private val client = HttpClient(engine) { 20 | this.setConfig(LOG_TAG) 21 | defaultRequest { 22 | url(BASE_URL) 23 | } 24 | } 25 | 26 | override suspend fun getLatestRelease(): Resource { 27 | return try { 28 | Resource.Success(client.get { 29 | url("repos/polodarb/GMS-Flags/releases/latest") 30 | }.body()) 31 | } catch (e: Exception) { 32 | Resource.Error(e) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/remote/github/dto/ReleaseInfo.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.remote.github.dto 2 | 3 | import kotlinx.serialization.SerialName 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ReleaseInfo( 8 | @SerialName("tag_name") val tagName: String 9 | ) 10 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/remote/googleUpdates/GoogleAppUpdatesService.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.remote.googleUpdates 2 | 3 | import tw.ktrssreader.kotlin.model.channel.RssStandardChannel 4 | 5 | interface GoogleAppUpdatesService { 6 | 7 | suspend fun getLatestRelease(): RssStandardChannel 8 | 9 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/remote/googleUpdates/GoogleAppUpdatesServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.remote.googleUpdates 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.call.body 5 | import io.ktor.client.engine.HttpClientEngine 6 | import io.ktor.client.plugins.defaultRequest 7 | import io.ktor.client.request.get 8 | import io.ktor.client.request.url 9 | import io.ktor.client.statement.bodyAsText 10 | import tw.ktrssreader.kotlin.model.channel.RssStandardChannel 11 | import tw.ktrssreader.kotlin.parser.RssStandardParser 12 | import ua.polodarb.gmsflags.data.remote.Resource 13 | import ua.polodarb.gmsflags.data.remote.setConfig 14 | 15 | private const val BASE_URL = "https://www.apkmirror.com/apk/google-inc/feed/" 16 | private const val LOG_TAG = "GoogleUpdatesApiService" 17 | 18 | class GoogleAppUpdatesServiceImpl( 19 | engine: HttpClientEngine 20 | ): GoogleAppUpdatesService { 21 | 22 | private val client = HttpClient(engine) { 23 | this.setConfig(LOG_TAG) 24 | defaultRequest { 25 | url(BASE_URL) 26 | } 27 | } 28 | 29 | override suspend fun getLatestRelease(): RssStandardChannel { 30 | val response = client.get(BASE_URL).bodyAsText() 31 | return RssStandardParser().parse(response) 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/remote/googleUpdates/dto/RssModels.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.remote.googleUpdates.dto 2 | 3 | data class RssMainModel( 4 | val articles: List
5 | ) 6 | 7 | data class Article( 8 | val title: String, 9 | val link: String, 10 | val pubDate: String, 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/repo/RoomDBRepository.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.repo 2 | 3 | import kotlinx.coroutines.flow.flow 4 | import ua.polodarb.gmsflags.data.databases.local.dao.FlagsDAO 5 | import ua.polodarb.gmsflags.data.databases.local.dao.PackagesDAO 6 | import ua.polodarb.gmsflags.data.databases.local.enities.SavedFlags 7 | import ua.polodarb.gmsflags.data.databases.local.enities.SavedPackages 8 | 9 | class RoomDBRepository( 10 | private val savedPackagesDao: PackagesDAO, 11 | private val savedFlagsDao: FlagsDAO 12 | ) { 13 | 14 | suspend fun getSavedPackages() = flow { 15 | savedPackagesDao.getSavedPackages().collect { 16 | emit(it) 17 | } 18 | } 19 | 20 | suspend fun deleteSavedPackage(pkgName: String) { 21 | savedPackagesDao.deleteSavedPackage(pkgName) 22 | } 23 | 24 | suspend fun savePackage(pkgName: String) { 25 | savedPackagesDao.savePackage(SavedPackages(pkgName)) 26 | } 27 | 28 | suspend fun getSavedFlags() = flow { 29 | savedFlagsDao.getSavedFlags().collect { 30 | emit(it) 31 | } 32 | } 33 | 34 | suspend fun deleteSavedFlag(flagName: String, pkgName: String) { 35 | savedFlagsDao.deleteSavedFlag(flagName, pkgName) 36 | } 37 | 38 | suspend fun saveFlag(flagName: String, pkgName: String, flagType: String) { 39 | savedFlagsDao.saveFlag(SavedFlags(pkgName, flagName, flagType)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/repo/SettingsRepository.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.repo 2 | 3 | import android.content.Context 4 | import ua.polodarb.gmsflags.GMSApplication 5 | import ua.polodarb.gmsflags.data.databases.local.dao.FlagsDAO 6 | import ua.polodarb.gmsflags.data.databases.local.dao.PackagesDAO 7 | 8 | class SettingsRepository( 9 | context: Context, 10 | private val flagsDao: FlagsDAO, 11 | private val packagesDAO: PackagesDAO 12 | ) { 13 | private val gmsApplication = context as GMSApplication 14 | 15 | fun deleteAllOverriddenFlagsFromGMS() { 16 | gmsApplication.getRootDatabase().deleteAllOverriddenFlagsFromGMS() 17 | } 18 | 19 | fun deleteAllOverriddenFlagsFromPlayStore() { 20 | gmsApplication.getRootDatabase().deleteAllOverriddenFlagsFromPlayStore() 21 | } 22 | 23 | fun deleteAllSavedFlags() { 24 | flagsDao.deleteAllSavedFlags() 25 | } 26 | 27 | fun deleteAllSavedPackages() { 28 | packagesDAO.deleteAllSavedPackages() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/repo/interactors/GmsDBInteractor.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.repo.interactors 2 | 3 | import android.content.Context 4 | import com.topjohnwu.superuser.Shell 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.invoke 7 | import kotlinx.coroutines.flow.first 8 | import ua.polodarb.gmsflags.data.repo.GmsDBRepository 9 | 10 | class GmsDBInteractor( 11 | private val context: Context, 12 | private val repository: GmsDBRepository 13 | ) { 14 | 15 | suspend fun overrideFlag( 16 | packageName: String, 17 | name: String, 18 | flagType: Int = 0, 19 | intVal: String? = null, 20 | boolVal: String? = null, 21 | floatVal: String? = null, 22 | stringVal: String? = null, 23 | extensionVal: String? = null, 24 | committed: Int = 0, 25 | clearData: Boolean = true, 26 | usersList: List 27 | ) = Dispatchers.IO { 28 | repository.deleteRowByFlagName(packageName, name) 29 | repository.overrideFlag( 30 | packageName = packageName, 31 | user = "", 32 | name = name, 33 | flagType = flagType, 34 | intVal = intVal, 35 | boolVal = boolVal, 36 | floatVal = floatVal, 37 | stringVal = stringVal, 38 | extensionVal = extensionVal, 39 | committed = committed 40 | ) 41 | for (i in usersList) { 42 | repository.overrideFlag( 43 | packageName = packageName, 44 | user = i, 45 | name = name, 46 | flagType = flagType, 47 | intVal = intVal, 48 | boolVal = boolVal, 49 | floatVal = floatVal, 50 | stringVal = stringVal, 51 | extensionVal = extensionVal, 52 | committed = committed 53 | ) 54 | } 55 | if (clearData) clearPhenotypeCache(packageName) 56 | } 57 | 58 | suspend fun clearPhenotypeCache(pkgName: String) { 59 | val androidPkgName = repository.androidPackage(pkgName).first() 60 | Shell.cmd("am force-stop $androidPkgName").exec() 61 | Shell.cmd("rm -rf /data/data/$androidPkgName/files/phenotype").exec() 62 | if (pkgName.contains("finsky") || pkgName.contains("vending")) { 63 | Shell.cmd("rm -rf /data/data/com.android.vending/files/experiment*").exec() 64 | Shell.cmd("am force-stop com.android.vending").exec() 65 | } 66 | if (pkgName.contains("com.google.android.apps.photos")) { 67 | Shell.cmd("rm -rf /data/data/com.google.android.apps.photos/shared_prefs/phenotype*") 68 | .exec() 69 | Shell.cmd("rm -rf /data/data/com.google.android.apps.photos/shared_prefs/com.google.android.apps.photos.phenotype.xml") 70 | .exec() 71 | Shell.cmd("am force-stop com.google.android.apps.photos").exec() 72 | } 73 | repeat(3) { 74 | Shell.cmd("am start -a android.intent.action.MAIN -n $androidPkgName &").exec() 75 | Shell.cmd("am force-stop $androidPkgName").exec() 76 | } 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/repo/mappers/GoogleUpdatesMapper.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.repo.mappers 2 | 3 | import tw.ktrssreader.kotlin.model.channel.RssStandardChannel 4 | import tw.ktrssreader.kotlin.model.channel.RssStandardChannelData 5 | import tw.ktrssreader.kotlin.model.item.RssStandardItem 6 | import ua.polodarb.gmsflags.data.remote.googleUpdates.dto.Article 7 | import ua.polodarb.gmsflags.data.remote.googleUpdates.dto.RssMainModel 8 | import java.text.SimpleDateFormat 9 | import java.util.Locale 10 | 11 | class GoogleUpdatesMapper { 12 | 13 | fun map(response: RssStandardChannel): NewRssModel { 14 | return NewRssModel( 15 | articles = mapArticle(response.items.orEmpty()) 16 | ) 17 | } 18 | 19 | private fun mapArticle(response: List): List { 20 | return response.mapNotNull { article -> 21 | val regex = Regex("""(.+?)\s(\d+\.\d+\.\d+)""") 22 | val matchResult = regex.find(article.title.orEmpty()) 23 | 24 | matchResult?.let { 25 | val appName = it.groupValues[1] 26 | val appVersion = it.groupValues[2] 27 | 28 | NewRssArticle( 29 | title = appName, 30 | version = appVersion, 31 | date = convertDateString(article.pubDate.orEmpty()), 32 | link = article.link.orEmpty() 33 | ) 34 | } 35 | }.filter { 36 | !(it.title.contains("Wear OS", ignoreCase = true) || 37 | it.title.contains("Android TV", ignoreCase = true) || 38 | it.title.contains("Trichrome", ignoreCase = true)) 39 | 40 | 41 | } 42 | } 43 | 44 | } 45 | 46 | data class NewRssModel( 47 | val articles: List 48 | ) 49 | 50 | data class NewRssArticle( 51 | val title: String, 52 | val version: String, 53 | val date: String, 54 | val link: String, 55 | ) 56 | 57 | fun convertDateString(inputDateString: String): String { 58 | val inputFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US) 59 | val outputFormat = SimpleDateFormat("dd.MM - HH:mm", Locale.getDefault()) 60 | 61 | return try { 62 | val date = inputFormat.parse(inputDateString) 63 | outputFormat.format(date ?: "Invalid date format") 64 | } catch (e: Exception) { 65 | e.printStackTrace() 66 | "Invalid date format" 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/data/repo/mappers/MergeAllTypesFlags.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.data.repo.mappers 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import kotlinx.coroutines.flow.flow 6 | import ua.polodarb.gmsflags.GMSApplication 7 | import ua.polodarb.gmsflags.ui.screens.UiStates 8 | 9 | class MergeFlagsMapper( 10 | context: Context 11 | ) { 12 | 13 | private val gmsApplication = (context as GMSApplication) 14 | 15 | fun getMergedOverriddenFlagsByPackage(pkg: String): MergedAllTypesOverriddenFlags { 16 | 17 | val boolFlags = 18 | gmsApplication.getRootDatabase().getOverriddenBoolFlagsByPackage(pkg) 19 | val intFlags = gmsApplication.getRootDatabase().getOverriddenIntFlagsByPackage(pkg) 20 | val floatFlags = 21 | gmsApplication.getRootDatabase().getOverriddenFloatFlagsByPackage(pkg) 22 | val stringFlags = 23 | gmsApplication.getRootDatabase().getOverriddenStringFlagsByPackage(pkg) 24 | 25 | return (MergedAllTypesOverriddenFlags( 26 | boolFlag = boolFlags, 27 | intFlag = intFlags, 28 | floatFlag = floatFlags, 29 | stringFlag = stringFlags 30 | )) 31 | 32 | } 33 | 34 | fun getMergedAllFlags() = flow> { 35 | 36 | gmsApplication.databaseInitializationStateFlow.collect { isInitialized -> 37 | if (isInitialized.isInitialized) { 38 | val boolFlags: Map = 39 | gmsApplication.getRootDatabase().allBoolFlags 40 | val intFlags: Map = gmsApplication.getRootDatabase().allIntFlags 41 | val floatFlags: Map = 42 | gmsApplication.getRootDatabase().allFloatFlags 43 | val stringFlags: Map = 44 | gmsApplication.getRootDatabase().allStringFlags 45 | 46 | val mergedBoolFlags = boolFlags.map { (pkgName, flagName) -> 47 | FlagDetails(pkgName, flagName, "bool") 48 | } 49 | 50 | Log.d("initAllFlags1", "mergedBoolFlags: ${boolFlags}") 51 | 52 | val mergedIntFlags = intFlags.map { (pkgName, flagName) -> 53 | FlagDetails(pkgName, flagName, "int") 54 | } 55 | 56 | val mergedFloatFlags = floatFlags.map { (pkgName, flagName) -> 57 | FlagDetails(pkgName, flagName, "float") 58 | } 59 | 60 | val mergedStringFlags = stringFlags.map { (pkgName, flagName) -> 61 | FlagDetails(pkgName, flagName, "string") 62 | } 63 | 64 | emit( 65 | UiStates.Success( 66 | MergedAllTypesFlags( 67 | boolFlag = mergedBoolFlags, 68 | intFlag = mergedIntFlags, 69 | floatFlag = mergedFloatFlags, 70 | stringFlag = mergedStringFlags 71 | ) 72 | ) 73 | ) 74 | } 75 | } 76 | } 77 | 78 | 79 | } 80 | 81 | data class MergedAllTypesOverriddenFlags( 82 | val boolFlag: Map, 83 | val intFlag: Map, 84 | val floatFlag: Map, 85 | val stringFlag: Map, 86 | ) 87 | 88 | data class MergedAllTypesFlags( 89 | val boolFlag: List, 90 | val intFlag: List, 91 | val floatFlag: List, 92 | val stringFlag: List 93 | ) { 94 | fun isNotEmpty() = boolFlag.isNotEmpty() && intFlag.isNotEmpty() && floatFlag.isNotEmpty() && stringFlag.isNotEmpty() 95 | } 96 | 97 | data class FlagDetails( 98 | val pkgName: String, 99 | val flagName: String, 100 | val type: String 101 | ) 102 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.di 2 | 3 | import org.koin.android.ext.koin.androidApplication 4 | import org.koin.android.ext.koin.androidContext 5 | import org.koin.dsl.module 6 | import ua.polodarb.gmsflags.GMSApplication 7 | import ua.polodarb.gmsflags.data.prefs.shared.PreferencesManager 8 | 9 | val appModule = module { 10 | single { androidApplication().applicationContext as GMSApplication } 11 | single { PreferencesManager(androidContext()) } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.di 2 | 3 | import androidx.room.Room 4 | import org.koin.android.ext.koin.androidApplication 5 | import org.koin.dsl.module 6 | import ua.polodarb.gmsflags.data.databases.local.AppDatabase 7 | 8 | private object LocalConstants { 9 | const val DATABASE_NAME = "gms_flags_database" 10 | } 11 | 12 | val databaseModule = module { 13 | single { 14 | Room.databaseBuilder( 15 | androidApplication().applicationContext, 16 | AppDatabase::class.java, 17 | LocalConstants.DATABASE_NAME 18 | ).build() 19 | } 20 | 21 | single { get().packagesDao() } 22 | 23 | single { get().flagsDao() } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/di/InteractorsModule.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.di 2 | 3 | import org.koin.dsl.module 4 | import ua.polodarb.gmsflags.data.repo.interactors.GmsDBInteractor 5 | 6 | val interactorsModule = module { 7 | 8 | single { 9 | GmsDBInteractor( 10 | context = get(), 11 | repository = get() 12 | ) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/di/RemoteModule.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.di 2 | 3 | import io.ktor.client.engine.android.Android 4 | import org.koin.dsl.module 5 | import ua.polodarb.gmsflags.data.remote.flags.FlagsApiServiceImpl 6 | import ua.polodarb.gmsflags.data.remote.github.GithubApiServiceImpl 7 | import ua.polodarb.gmsflags.data.remote.googleUpdates.GoogleAppUpdatesService 8 | import ua.polodarb.gmsflags.data.remote.googleUpdates.GoogleAppUpdatesServiceImpl 9 | 10 | val remoteModule = module { 11 | single { GithubApiServiceImpl(engine = Android.create()) } 12 | single { FlagsApiServiceImpl(engine = Android.create()) } 13 | single { GoogleAppUpdatesServiceImpl(engine = Android.create()) } 14 | single { GoogleAppUpdatesServiceImpl(engine = Android.create()) } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.di 2 | 3 | import org.koin.dsl.module 4 | import ua.polodarb.gmsflags.data.repo.AppsListRepository 5 | import ua.polodarb.gmsflags.data.repo.GmsDBRepository 6 | import ua.polodarb.gmsflags.data.repo.RoomDBRepository 7 | import ua.polodarb.gmsflags.data.repo.SettingsRepository 8 | import ua.polodarb.gmsflags.data.repo.mappers.GoogleUpdatesMapper 9 | import ua.polodarb.gmsflags.data.repo.mappers.MergeFlagsMapper 10 | 11 | val repositoryModule = module { 12 | 13 | single { 14 | GmsDBRepository( 15 | context = get() 16 | ) 17 | } 18 | 19 | single { 20 | AppsListRepository( 21 | context = get() 22 | ) 23 | } 24 | 25 | single { 26 | RoomDBRepository( 27 | savedPackagesDao = get(), 28 | savedFlagsDao = get() 29 | ) 30 | } 31 | 32 | single { 33 | SettingsRepository( 34 | context = get(), 35 | flagsDao = get(), 36 | packagesDAO = get() 37 | ) 38 | } 39 | 40 | single { 41 | MergeFlagsMapper( 42 | context = get() 43 | ) 44 | } 45 | 46 | single { 47 | GoogleUpdatesMapper() 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/di/ViewModelsModule.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.di 2 | 3 | import org.koin.androidx.viewmodel.dsl.viewModel 4 | import org.koin.dsl.module 5 | import ua.polodarb.gmsflags.data.remote.flags.FlagsApiServiceImpl 6 | import ua.polodarb.gmsflags.ui.screens.search.SearchScreenViewModel 7 | import ua.polodarb.gmsflags.ui.screens.flagChange.FlagChangeScreenViewModel 8 | import ua.polodarb.gmsflags.ui.screens.flagChange.extScreens.AddMultipleFlagsViewModel 9 | import ua.polodarb.gmsflags.ui.screens.packages.PackagesScreenViewModel 10 | import ua.polodarb.gmsflags.ui.screens.saved.SavedScreenViewModel 11 | import ua.polodarb.gmsflags.ui.screens.settings.SettingsViewModel 12 | import ua.polodarb.gmsflags.ui.screens.suggestions.SuggestionScreenViewModel 13 | import ua.polodarb.gmsflags.ui.screens.updates.UpdatesScreenViewModel 14 | 15 | val viewModelsModule = module { 16 | 17 | viewModel { 18 | PackagesScreenViewModel( 19 | gmsRepository = get(), 20 | roomRepository = get() 21 | ) 22 | } 23 | 24 | viewModel { 25 | FlagChangeScreenViewModel( 26 | pkgName = get(), 27 | repository = get(), 28 | roomRepository = get(), 29 | gmsDBInteractor = get() 30 | ) 31 | } 32 | 33 | viewModel { 34 | SearchScreenViewModel( 35 | repository = get(), 36 | gmsRepository = get(), 37 | roomRepository = get(), 38 | mergeFlagsMapper = get(), 39 | gmsDBInteractor = get() 40 | ) 41 | } 42 | 43 | viewModel { 44 | UpdatesScreenViewModel( 45 | googleAppUpdatesService = get(), 46 | googleUpdatesMapper = get(), 47 | sharedPrefs = get() 48 | ) 49 | } 50 | 51 | viewModel { 52 | SuggestionScreenViewModel( 53 | application = get(), 54 | repository = get(), 55 | appsRepository = get(), 56 | flagsApiService = get(), 57 | mapper = get(), 58 | interactor = get() 59 | ) 60 | } 61 | 62 | viewModel { 63 | SavedScreenViewModel( 64 | roomRepository = get() 65 | ) 66 | } 67 | 68 | viewModel { 69 | SettingsViewModel( 70 | settingsRepository = get() 71 | ) 72 | } 73 | 74 | viewModel { 75 | AddMultipleFlagsViewModel( 76 | pkgName = get(), 77 | repository = get(), 78 | gmsDBInteractor = get() 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/di/WorkersModule.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.di 2 | 3 | import org.koin.android.ext.koin.androidContext 4 | import org.koin.androidx.workmanager.dsl.worker 5 | import org.koin.dsl.module 6 | import ua.polodarb.gmsflags.data.workers.GoogleUpdatesCheckWorker 7 | 8 | val workerModule = module { 9 | 10 | worker { 11 | GoogleUpdatesCheckWorker( 12 | context = androidContext(), 13 | workerParameters = get(), 14 | googleAppUpdatesService = get(), 15 | googleUpdatesMapper = get(), 16 | sharedPrefs = get() 17 | ) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/animations/ScreensAnimation.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.animations 2 | 3 | import androidx.compose.animation.ExperimentalAnimationApi 4 | import androidx.compose.animation.core.CubicBezierEasing 5 | import androidx.compose.animation.core.tween 6 | import androidx.compose.animation.fadeIn 7 | import androidx.compose.animation.fadeOut 8 | import androidx.compose.animation.slideInHorizontally 9 | import androidx.compose.animation.slideOutHorizontally 10 | 11 | @ExperimentalAnimationApi 12 | fun enterAnim( 13 | toLeft: Boolean 14 | ) = slideInHorizontally( 15 | initialOffsetX = { (if (toLeft) 1 else -1) * (it * 0.075).toInt() }, 16 | animationSpec = tween( 17 | durationMillis = 350, 18 | delayMillis = 150, 19 | easing = CubicBezierEasing(0.0f, 0.0f, 0.3f, 1.0f) 20 | ) 21 | ) + fadeIn( 22 | animationSpec = tween( 23 | durationMillis = 250, 24 | delayMillis = 250, 25 | easing = CubicBezierEasing(0.0f, 0.0f, 0.3f, 1.0f) 26 | ), 27 | initialAlpha = 0.0f 28 | ) 29 | 30 | @ExperimentalAnimationApi 31 | fun exitAnim( 32 | toLeft: Boolean 33 | ) = slideOutHorizontally( 34 | targetOffsetX = { (if (!toLeft) 1 else -1) * (it * 0.075).toInt() }, 35 | animationSpec = tween( 36 | durationMillis = 350, 37 | easing = CubicBezierEasing(0.4f, 0.0f, 0.3f, 1.0f) 38 | ) 39 | ) + fadeOut( 40 | animationSpec = tween( 41 | durationMillis = 250, 42 | easing = CubicBezierEasing(0.4f, 0.0f, 0.3f, 1.0f) 43 | ), 44 | targetAlpha = 0.0f 45 | ) 46 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/UpdateDialog.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.material3.AlertDialog 6 | import androidx.compose.material3.Button 7 | import androidx.compose.material3.Icon 8 | import androidx.compose.material3.OutlinedButton 9 | import androidx.compose.material3.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.runtime.LaunchedEffect 12 | import androidx.compose.runtime.getValue 13 | import androidx.compose.runtime.mutableStateOf 14 | import androidx.compose.runtime.produceState 15 | import androidx.compose.runtime.remember 16 | import androidx.compose.runtime.saveable.rememberSaveable 17 | import androidx.compose.runtime.setValue 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.platform.LocalUriHandler 20 | import androidx.compose.ui.res.painterResource 21 | import androidx.compose.ui.res.stringResource 22 | import androidx.compose.ui.text.font.FontWeight 23 | import kotlinx.coroutines.CoroutineScope 24 | import kotlinx.coroutines.Dispatchers 25 | import kotlinx.coroutines.launch 26 | import ua.polodarb.gmsflags.BuildConfig 27 | import ua.polodarb.gmsflags.R 28 | import ua.polodarb.gmsflags.utils.Extensions.toFormattedInt 29 | import ua.polodarb.gmsflags.data.remote.Resource 30 | import ua.polodarb.gmsflags.data.remote.github.GithubApiService 31 | 32 | @Composable 33 | fun UpdateDialog( 34 | githubApiService: GithubApiService, 35 | isFirstStart: Boolean 36 | ) { 37 | val uriHandler = LocalUriHandler.current 38 | 39 | var showDialog by rememberSaveable { mutableStateOf(false) } 40 | val release = produceState( 41 | initialValue = BuildConfig.VERSION_NAME, 42 | producer = { 43 | CoroutineScope(Dispatchers.IO).launch { 44 | val res = githubApiService.getLatestRelease() 45 | if (res is Resource.Success) { 46 | this@produceState.value = res.data?.tagName!! 47 | } 48 | } 49 | } 50 | ).value 51 | 52 | var appUpdateState by remember { mutableStateOf(false) } 53 | appUpdateState = BuildConfig.VERSION_NAME.toFormattedInt() < release.toFormattedInt() 54 | 55 | if (!isFirstStart) { 56 | LaunchedEffect(appUpdateState) { 57 | if (appUpdateState) { 58 | showDialog = true 59 | } 60 | } 61 | } 62 | 63 | if (showDialog) { 64 | AlertDialog( 65 | onDismissRequest = { showDialog = false }, 66 | confirmButton = { 67 | Row { 68 | OutlinedButton(onClick = { showDialog = false }) { 69 | Text(text = stringResource(R.string.update_dialog_close)) 70 | } 71 | Spacer(modifier = Modifier.weight(1f)) 72 | Button( 73 | onClick = { 74 | uriHandler.openUri("https://github.com/polodarb/GMS-Flags/releases/latest") 75 | showDialog = false 76 | }) { 77 | Text(text = stringResource(R.string.update_dialog_confirm)) 78 | } 79 | } 80 | }, 81 | icon = { 82 | Icon( 83 | painter = painterResource(id = R.drawable.ic_update_app), 84 | contentDescription = null 85 | ) 86 | }, 87 | text = { 88 | Text(text = stringResource(R.string.update_dialog_info)) 89 | }, 90 | title = { 91 | Text( 92 | text = stringResource(R.string.update_dialog_title, release), 93 | fontWeight = FontWeight.Medium 94 | ) 95 | }, 96 | ) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/chips/filter/GFlagFilterChip.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.chips.filter 2 | 3 | import androidx.compose.foundation.layout.fillMaxWidth 4 | import androidx.compose.material3.ExperimentalMaterial3Api 5 | import androidx.compose.material3.FilterChip 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.SelectableChipColors 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.res.stringResource 13 | import androidx.compose.ui.text.style.TextAlign 14 | import androidx.compose.ui.text.style.TextOverflow 15 | import ua.polodarb.gmsflags.R 16 | 17 | @OptIn(ExperimentalMaterial3Api::class) 18 | @Composable 19 | fun GFlagFilterChip( 20 | selected: Boolean, 21 | pagerCurrentState: Int, 22 | chipOnClick: () -> Unit, 23 | chipTitle: String, 24 | modifier: Modifier 25 | ) { 26 | FilterChip( 27 | selected = selected, 28 | onClick = chipOnClick, 29 | colors = SelectableChipColors( 30 | containerColor = Color.Transparent, 31 | labelColor = MaterialTheme.colorScheme.onSurfaceVariant, 32 | leadingIconColor = Color.Transparent, 33 | trailingIconColor = Color.Transparent, 34 | disabledContainerColor = Color.Transparent, 35 | disabledLabelColor = MaterialTheme.colorScheme.onSecondaryContainer.copy( 36 | alpha = 0.3f 37 | ), 38 | disabledLeadingIconColor = Color.Transparent, 39 | disabledTrailingIconColor = Color.Transparent, 40 | selectedContainerColor = MaterialTheme.colorScheme.secondary, 41 | disabledSelectedContainerColor = MaterialTheme.colorScheme.secondary.copy( 42 | alpha = 0.2f 43 | ), 44 | selectedLabelColor = MaterialTheme.colorScheme.onSecondary, 45 | selectedLeadingIconColor = Color.Transparent, 46 | selectedTrailingIconColor = Color.Transparent 47 | ), 48 | label = { 49 | Text( 50 | text = chipTitle, 51 | modifier = Modifier.fillMaxWidth(), 52 | textAlign = TextAlign.Center, 53 | maxLines = 1, 54 | overflow = TextOverflow.Ellipsis 55 | ) 56 | }, 57 | leadingIcon = null, 58 | modifier = modifier, 59 | enabled = when (pagerCurrentState) { 60 | 0 -> true 61 | else -> { 62 | chipTitle == stringResource(R.string.filter_chip_all) || chipTitle == stringResource(R.string.filter_chip_changed) 63 | } 64 | } 65 | ) 66 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/chips/filter/GFlagFilterChipRow.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.chips.filter 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.surfaceColorAtElevation 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.graphics.lerp 13 | import androidx.compose.ui.unit.dp 14 | import kotlinx.collections.immutable.PersistentList 15 | 16 | @Composable 17 | fun GFlagFilterChipRow( 18 | list: PersistentList, 19 | selectedChips: Int, 20 | pagerCurrentState: Int, 21 | colorFraction: Float? = null, 22 | chipOnClick: (index: Int) -> Unit 23 | ) { 24 | Row( 25 | modifier = Modifier 26 | .background( 27 | if (colorFraction != null) { 28 | lerp( 29 | MaterialTheme.colorScheme.surfaceColorAtElevation(0.dp), 30 | MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp), 31 | colorFraction 32 | ) 33 | } else { 34 | MaterialTheme.colorScheme.surface 35 | } 36 | ) 37 | .fillMaxWidth() 38 | .padding(vertical = 12.dp, horizontal = 6.dp) 39 | .height(36.dp) 40 | ) { 41 | list.forEachIndexed { index, title -> 42 | GFlagFilterChip( 43 | selected = selectedChips == index, 44 | pagerCurrentState = pagerCurrentState, 45 | chipOnClick = { 46 | chipOnClick(index) 47 | }, 48 | chipTitle = title, 49 | modifier = Modifier 50 | .weight(if (index != 0) 1f else 0.8f) 51 | .padding(horizontal = 8.dp) 52 | ) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/chips/types/GFlagTypesChip.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.chips.types 2 | 3 | import androidx.compose.foundation.layout.fillMaxWidth 4 | import androidx.compose.material3.ExperimentalMaterial3Api 5 | import androidx.compose.material3.FilterChip 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.SelectableChipColors 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.res.stringResource 13 | import androidx.compose.ui.text.style.TextAlign 14 | import androidx.compose.ui.text.style.TextOverflow 15 | import ua.polodarb.gmsflags.R 16 | 17 | @OptIn(ExperimentalMaterial3Api::class) 18 | @Composable 19 | fun GFlagTypesChip( 20 | selected: Boolean, 21 | chipOnClick: () -> Unit, 22 | chipTitle: String, 23 | modifier: Modifier 24 | ) { 25 | FilterChip( 26 | selected = selected, 27 | onClick = chipOnClick, 28 | colors = SelectableChipColors( 29 | containerColor = Color.Transparent, 30 | labelColor = MaterialTheme.colorScheme.onSurfaceVariant, 31 | leadingIconColor = Color.Transparent, 32 | trailingIconColor = Color.Transparent, 33 | disabledContainerColor = Color.Transparent, 34 | disabledLabelColor = MaterialTheme.colorScheme.onSecondaryContainer.copy( 35 | alpha = 0.3f 36 | ), 37 | disabledLeadingIconColor = Color.Transparent, 38 | disabledTrailingIconColor = Color.Transparent, 39 | selectedContainerColor = MaterialTheme.colorScheme.secondary, 40 | disabledSelectedContainerColor = MaterialTheme.colorScheme.secondary.copy( 41 | alpha = 0.2f 42 | ), 43 | selectedLabelColor = MaterialTheme.colorScheme.onSecondary, 44 | selectedLeadingIconColor = Color.Transparent, 45 | selectedTrailingIconColor = Color.Transparent 46 | ), 47 | label = { 48 | Text( 49 | text = chipTitle, 50 | modifier = Modifier.fillMaxWidth(), 51 | textAlign = TextAlign.Center, 52 | maxLines = 1, 53 | overflow = TextOverflow.Ellipsis 54 | ) 55 | }, 56 | leadingIcon = null, 57 | modifier = modifier 58 | ) 59 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/chips/types/GFlagTypesChipsRow.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.chips.types 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.surfaceColorAtElevation 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.graphics.lerp 13 | import androidx.compose.ui.unit.dp 14 | import kotlinx.collections.immutable.PersistentList 15 | import kotlinx.collections.immutable.persistentListOf 16 | 17 | @Composable 18 | fun GFlagTypesChipRow( 19 | list: PersistentList = persistentListOf("Bool", "Int", "Float", "String"), 20 | selectedChips: Int, 21 | colorFraction: Float? = null, 22 | chipOnClick: (index: Int) -> Unit 23 | ) { 24 | Row( 25 | modifier = Modifier 26 | .background( 27 | if (colorFraction != null) { 28 | lerp( 29 | MaterialTheme.colorScheme.surfaceColorAtElevation(0.dp), 30 | MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp), 31 | colorFraction 32 | ) 33 | } else { 34 | MaterialTheme.colorScheme.surface 35 | } 36 | ) 37 | .fillMaxWidth() 38 | .padding(vertical = 12.dp, horizontal = 6.dp) 39 | .height(36.dp) 40 | ) { 41 | list.forEachIndexed { index, title -> 42 | GFlagTypesChip( 43 | selected = selectedChips == index, 44 | chipOnClick = { 45 | chipOnClick(index) 46 | }, 47 | chipTitle = title, 48 | modifier = Modifier 49 | .weight(1f) 50 | .padding(horizontal = 8.dp) 51 | ) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/dropDown/FlagChangeDropDown.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.dropDown 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.foundation.shape.RoundedCornerShape 6 | import androidx.compose.material.icons.Icons 7 | import androidx.compose.material.icons.outlined.Add 8 | import androidx.compose.material3.DropdownMenu 9 | import androidx.compose.material3.DropdownMenuItem 10 | import androidx.compose.material3.HorizontalDivider 11 | import androidx.compose.material3.Icon 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.Text 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.res.painterResource 17 | import androidx.compose.ui.res.stringResource 18 | import androidx.compose.ui.unit.dp 19 | import ua.polodarb.gmsflags.R 20 | 21 | @Composable 22 | fun FlagChangeDropDown( 23 | expanded: Boolean, 24 | onDismissRequest: () -> Unit, 25 | onAddFlag: () -> Unit, 26 | onAddMultipleFlags: () -> Unit, 27 | onDeleteOverriddenFlags: () -> Unit, 28 | onOpenAppDetailsSettings: () -> Unit, 29 | modifier: Modifier = Modifier 30 | ) { 31 | Box( 32 | modifier = modifier 33 | .padding(top = 44.dp) 34 | ) { 35 | MaterialTheme( 36 | shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(16.dp)) 37 | ) { 38 | DropdownMenu( 39 | expanded = expanded, 40 | onDismissRequest = onDismissRequest 41 | ) 42 | { 43 | DropdownMenuItem( 44 | text = { Text(text = stringResource(id = R.string.component_add_flag)) }, 45 | onClick = onAddFlag, 46 | leadingIcon = { 47 | Icon( 48 | Icons.Outlined.Add, 49 | contentDescription = null 50 | ) 51 | }, 52 | enabled = true 53 | ) 54 | DropdownMenuItem( 55 | text = { Text(text = stringResource(R.string.add_a_multiple_flags)) }, 56 | onClick = onAddMultipleFlags, 57 | leadingIcon = { 58 | Icon( 59 | painterResource(id = R.drawable.ic_add_flag_list), 60 | contentDescription = null 61 | ) 62 | }, 63 | enabled = true 64 | ) 65 | DropdownMenuItem( 66 | text = { Text(text = stringResource(R.string.open_app_details_settings)) }, 67 | onClick = onOpenAppDetailsSettings, 68 | leadingIcon = { 69 | Icon( 70 | painterResource(id = R.drawable.ic_open_app_settings), 71 | contentDescription = null 72 | ) 73 | }, 74 | enabled = true 75 | ) 76 | HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp)) 77 | DropdownMenuItem( 78 | text = { Text(text = stringResource(id = R.string.component_reset_flags)) }, 79 | onClick = onDeleteOverriddenFlags, 80 | leadingIcon = { 81 | Icon( 82 | painterResource(id = R.drawable.ic_reset_flags), 83 | contentDescription = null 84 | ) 85 | }, 86 | enabled = true 87 | ) 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/dropDown/FlagSelectDropDown.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.dropDown 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.offset 5 | import androidx.compose.foundation.shape.RoundedCornerShape 6 | import androidx.compose.material3.DropdownMenu 7 | import androidx.compose.material3.DropdownMenuItem 8 | import androidx.compose.material3.HorizontalDivider 9 | import androidx.compose.material3.Icon 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.material3.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.res.painterResource 15 | import androidx.compose.ui.res.stringResource 16 | import androidx.compose.ui.unit.dp 17 | import ua.polodarb.gmsflags.R 18 | 19 | @Composable 20 | fun FlagSelectDropDown( 21 | expanded: Boolean, 22 | onDismissRequest: () -> Unit, 23 | onEnableSelected: () -> Unit, 24 | onDisableSelected: () -> Unit, 25 | onSelectAllItems: () -> Unit, 26 | modifier: Modifier = Modifier 27 | ) { 28 | Box( 29 | modifier = modifier.offset(x = (-12).dp) 30 | ) { 31 | MaterialTheme( 32 | shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(16.dp)) 33 | ) { 34 | DropdownMenu( 35 | expanded = expanded, 36 | onDismissRequest = onDismissRequest 37 | ) 38 | { 39 | DropdownMenuItem( 40 | text = { Text(text = stringResource(R.string.drop_down_enable_selected)) }, 41 | onClick = onEnableSelected, 42 | leadingIcon = { 43 | Icon( 44 | painterResource(id = R.drawable.ic_enable_selected), 45 | contentDescription = null 46 | ) 47 | }, 48 | enabled = true 49 | ) 50 | DropdownMenuItem( 51 | text = { Text(text = stringResource(R.string.drop_down_disable_selected)) }, 52 | onClick = onDisableSelected, 53 | leadingIcon = { 54 | Icon( 55 | painterResource(id = R.drawable.ic_disable_selected), 56 | contentDescription = null 57 | ) 58 | }, 59 | enabled = true 60 | ) 61 | HorizontalDivider() 62 | DropdownMenuItem( 63 | text = { Text(text = stringResource(R.string.drop_down_select_all)) }, 64 | onClick = onSelectAllItems, 65 | leadingIcon = { 66 | Icon( 67 | painterResource(id = R.drawable.ic_select_all), 68 | contentDescription = null 69 | ) 70 | }, 71 | enabled = true 72 | ) 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/ErrorLoadScreen.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.inserts 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.material3.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.res.stringResource 10 | import ua.polodarb.gmsflags.R 11 | 12 | @Composable 13 | fun ErrorLoadScreen() { 14 | Box( 15 | modifier = Modifier.fillMaxSize(), 16 | contentAlignment = Alignment.Center 17 | ) { 18 | Text(text = stringResource(id = R.string.component_error_load)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/LoadingProgressBar.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.inserts 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.size 6 | import androidx.compose.material3.CircularProgressIndicator 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.StrokeCap 12 | import androidx.compose.ui.unit.dp 13 | 14 | @Composable 15 | fun LoadingProgressBar() { 16 | Box( 17 | modifier = Modifier.fillMaxSize(), 18 | contentAlignment = Alignment.Center 19 | ) { 20 | CircularProgressIndicator( 21 | color = MaterialTheme.colorScheme.primary, 22 | strokeWidth = 4.dp, 23 | strokeCap = StrokeCap.Round, 24 | modifier = Modifier.size(48.dp) 25 | ) 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/NotFoundContent.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.inserts 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxHeight 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.res.stringResource 13 | import androidx.compose.ui.text.font.FontWeight 14 | import androidx.compose.ui.text.style.TextAlign 15 | import ua.polodarb.gmsflags.R 16 | 17 | @Composable 18 | fun NotFoundContent( 19 | type: NoFlagsOrPackages = NoFlagsOrPackages.FLAGS, 20 | customText: String? = null 21 | ) { 22 | val text = "¯\\_(ツ)_/¯\n\n" + (customText ?: when (type) { 23 | NoFlagsOrPackages.FLAGS -> stringResource(id = R.string.component_no_flags) 24 | NoFlagsOrPackages.APPS -> stringResource(id = R.string.component_no_apps) 25 | NoFlagsOrPackages.PACKAGES -> stringResource(R.string.component_no_packages) 26 | }) 27 | 28 | Box( 29 | modifier = Modifier 30 | .fillMaxWidth() 31 | .fillMaxHeight(1f) 32 | .background(MaterialTheme.colorScheme.surface), 33 | contentAlignment = Alignment.Center 34 | ) { 35 | Text( 36 | text = text, 37 | color = MaterialTheme.colorScheme.onSurfaceVariant, 38 | style = MaterialTheme.typography.headlineMedium, 39 | textAlign = TextAlign.Center, 40 | fontWeight = FontWeight.Medium 41 | ) 42 | } 43 | } 44 | 45 | enum class NoFlagsOrPackages { 46 | APPS, PACKAGES, FLAGS 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/NotImplementedScreen.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.inserts 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.material3.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.res.stringResource 10 | import androidx.compose.ui.unit.sp 11 | import ua.polodarb.gmsflags.R 12 | 13 | @Composable 14 | fun NotImplementedScreen() { 15 | Box( 16 | modifier = Modifier.fillMaxSize(), 17 | contentAlignment = Alignment.Center 18 | ) { 19 | Text( 20 | text = stringResource(id = R.string.component_not_implemented), 21 | fontSize = 22.sp 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/searchBar/GFlagsSearchBar.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.searchBar 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.fadeIn 5 | import androidx.compose.animation.fadeOut 6 | import androidx.compose.foundation.background 7 | import androidx.compose.foundation.layout.Arrangement 8 | import androidx.compose.foundation.layout.Row 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.foundation.layout.height 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.material.icons.Icons 13 | import androidx.compose.material.icons.filled.Clear 14 | import androidx.compose.material3.DockedSearchBar 15 | import androidx.compose.material3.ExperimentalMaterial3Api 16 | import androidx.compose.material3.Icon 17 | import androidx.compose.material3.IconButton 18 | import androidx.compose.material3.MaterialTheme 19 | import androidx.compose.material3.SearchBarDefaults 20 | import androidx.compose.material3.Text 21 | import androidx.compose.material3.surfaceColorAtElevation 22 | import androidx.compose.runtime.Composable 23 | import androidx.compose.ui.Alignment 24 | import androidx.compose.ui.Modifier 25 | import androidx.compose.ui.focus.FocusRequester 26 | import androidx.compose.ui.focus.focusRequester 27 | import androidx.compose.ui.graphics.lerp 28 | import androidx.compose.ui.unit.dp 29 | 30 | @OptIn(ExperimentalMaterial3Api::class) 31 | @Composable 32 | fun GFlagsSearchBar( 33 | query: String, 34 | onQueryChange: (String) -> Unit, 35 | iconVisibility: Boolean, 36 | iconOnClick: () -> Unit, 37 | placeHolderText: String, 38 | colorFraction: Float? = null, 39 | keyboardFocus: FocusRequester 40 | ) { 41 | Row( 42 | modifier = Modifier 43 | .background( 44 | if (colorFraction != null) { 45 | lerp( 46 | MaterialTheme.colorScheme.surfaceColorAtElevation(0.dp), 47 | MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp), 48 | colorFraction 49 | ) 50 | } else { 51 | MaterialTheme.colorScheme.surface 52 | } 53 | ) 54 | .fillMaxWidth() 55 | .padding(vertical = 12.dp, horizontal = 16.dp) 56 | .height(64.dp), 57 | horizontalArrangement = Arrangement.Center, 58 | verticalAlignment = Alignment.CenterVertically 59 | ) { 60 | DockedSearchBar( 61 | query = query, 62 | onQueryChange = onQueryChange, 63 | onSearch = {}, 64 | placeholder = { 65 | Text(text = placeHolderText) 66 | }, 67 | colors = if (colorFraction != null) { 68 | SearchBarDefaults.colors( 69 | containerColor = lerp( 70 | MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp), 71 | MaterialTheme.colorScheme.surfaceColorAtElevation(0.dp), 72 | colorFraction - 0.05f 73 | ) 74 | ) 75 | } else { 76 | SearchBarDefaults.colors() 77 | }, 78 | trailingIcon = { 79 | AnimatedVisibility( 80 | visible = iconVisibility, 81 | enter = fadeIn(), 82 | exit = fadeOut() 83 | ) { 84 | IconButton(onClick = iconOnClick) { 85 | Icon( 86 | imageVector = Icons.Default.Clear, 87 | contentDescription = null 88 | ) 89 | } 90 | } 91 | }, 92 | active = false, 93 | onActiveChange = { }, 94 | modifier = Modifier 95 | .fillMaxWidth() 96 | .focusRequester(keyboardFocus) 97 | ) { } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/tabs/CustomTabIndicator.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.tabs 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.layout.wrapContentSize 9 | import androidx.compose.foundation.shape.RoundedCornerShape 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.draw.clip 14 | import androidx.compose.ui.graphics.Color 15 | import androidx.compose.ui.unit.dp 16 | 17 | @Composable 18 | fun CustomTabIndicator(color: Color, modifier: Modifier = Modifier) { 19 | Box( 20 | modifier 21 | .wrapContentSize(Alignment.BottomCenter) 22 | .padding(horizontal = 12.dp) 23 | .fillMaxWidth() 24 | .height(3.dp) 25 | .clip(RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp)) 26 | .background(color) 27 | ) 28 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/tabs/CustomTabIndicatorAnimation.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.tabs 2 | 3 | import androidx.compose.animation.core.animateDp 4 | import androidx.compose.animation.core.spring 5 | import androidx.compose.animation.core.updateTransition 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.offset 8 | import androidx.compose.foundation.layout.width 9 | import androidx.compose.foundation.layout.wrapContentSize 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.material3.TabPosition 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.getValue 14 | import androidx.compose.ui.Alignment 15 | import androidx.compose.ui.Modifier 16 | import kotlinx.collections.immutable.PersistentList 17 | 18 | 19 | @Composable 20 | fun CustomTabIndicatorAnimation( 21 | tabPositions: PersistentList, 22 | selectedTabIndex: Int 23 | ) { 24 | val transition = updateTransition(selectedTabIndex, label = "") 25 | val indicatorStart by transition.animateDp( 26 | transitionSpec = { 27 | if (initialState < targetState) { 28 | spring(dampingRatio = 1f, stiffness = 500f) 29 | } else { 30 | spring(dampingRatio = 1f, stiffness = 1500f) 31 | } 32 | }, label = "" 33 | ) { 34 | tabPositions[it].left 35 | } 36 | 37 | val indicatorEnd by transition.animateDp( 38 | transitionSpec = { 39 | if (initialState < targetState) { 40 | spring(dampingRatio = 1f, stiffness = 1500f) 41 | } else { 42 | spring(dampingRatio = 1f, stiffness = 500f) 43 | } 44 | }, label = "" 45 | ) { 46 | tabPositions[it].right 47 | } 48 | 49 | CustomTabIndicator( 50 | color = MaterialTheme.colorScheme.primary, 51 | modifier = Modifier 52 | .fillMaxSize() 53 | .wrapContentSize(align = Alignment.BottomStart) 54 | .offset(x = indicatorStart) 55 | .width(indicatorEnd - indicatorStart) 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/tabs/GFlagsTab.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.tabs 2 | 3 | import androidx.compose.foundation.layout.height 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.Tab 7 | import androidx.compose.material3.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.draw.clip 11 | import androidx.compose.ui.text.style.TextOverflow 12 | import androidx.compose.ui.unit.dp 13 | 14 | @Composable 15 | fun GFlagsTab( 16 | selected: Boolean, 17 | tabState: Int, 18 | index: Int, 19 | tabTitle: String, 20 | enabled: Boolean = true, 21 | onClick: () -> Unit, 22 | ) { 23 | Tab( 24 | selected = selected, 25 | onClick = onClick, 26 | text = { 27 | Text( 28 | text = tabTitle, 29 | maxLines = 1, 30 | overflow = TextOverflow.Ellipsis, 31 | color = if (tabState == index) { 32 | MaterialTheme.colorScheme.primary 33 | } else { 34 | MaterialTheme.colorScheme.onSurfaceVariant 35 | } 36 | ) 37 | }, 38 | 39 | modifier = Modifier 40 | .padding(horizontal = 4.dp, vertical = 12.dp) 41 | .height(40.dp) 42 | .clip(MaterialTheme.shapes.extraLarge), 43 | enabled = enabled 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/components/tabs/GFlagsTabRow.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.components.tabs 2 | 3 | import androidx.compose.animation.core.FastOutLinearInEasing 4 | import androidx.compose.material3.ExperimentalMaterial3Api 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.TabPosition 7 | import androidx.compose.material3.TabRow 8 | import androidx.compose.material3.TopAppBarState 9 | import androidx.compose.material3.surfaceColorAtElevation 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.graphics.lerp 12 | import androidx.compose.ui.unit.dp 13 | import kotlinx.collections.immutable.PersistentList 14 | import kotlinx.collections.immutable.toPersistentList 15 | 16 | @OptIn(ExperimentalMaterial3Api::class) 17 | @Composable 18 | fun GFlagsTabRow( 19 | list: PersistentList, 20 | tabState: Int, 21 | topBarState: TopAppBarState, 22 | enabled: Boolean = true, 23 | onClick: (Int) -> Unit 24 | ) { 25 | 26 | val indicator = @Composable { tabPositions: List -> 27 | CustomTabIndicatorAnimation( 28 | tabPositions = tabPositions.toPersistentList(), 29 | selectedTabIndex = tabState 30 | ) 31 | } 32 | 33 | TabRow( 34 | selectedTabIndex = tabState, 35 | indicator = indicator, 36 | containerColor = lerp( 37 | MaterialTheme.colorScheme.surfaceColorAtElevation(0.dp), 38 | MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp), 39 | FastOutLinearInEasing.transform(topBarState.collapsedFraction) 40 | ) 41 | ) { 42 | list.forEachIndexed { index, title -> 43 | GFlagsTab( 44 | selected = tabState == index, 45 | tabState = tabState, 46 | index = index, 47 | tabTitle = title, 48 | enabled = enabled, 49 | onClick = { 50 | onClick(index) 51 | } 52 | ) 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/navigation/NavExtensions.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.navigation 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.DisposableEffect 5 | import androidx.compose.runtime.Stable 6 | import androidx.compose.runtime.State 7 | import androidx.compose.runtime.mutableStateOf 8 | import androidx.compose.runtime.remember 9 | import androidx.navigation.NavController 10 | import androidx.navigation.NavDestination.Companion.hierarchy 11 | 12 | 13 | @Stable 14 | @Composable 15 | fun NavController.currentScreenAsState(): State { 16 | 17 | val selectedItem = remember { mutableStateOf(NavBarItem.Suggestions) } 18 | 19 | DisposableEffect(this) { 20 | val listener = NavController.OnDestinationChangedListener { _, destination, _ -> 21 | when { 22 | destination.hierarchy.any { it.route == NavBarItem.Suggestions.screenRoute } -> { 23 | selectedItem.value = NavBarItem.Suggestions 24 | } 25 | 26 | destination.hierarchy.any { it.route == NavBarItem.Apps.screenRoute } -> { 27 | selectedItem.value = NavBarItem.Apps 28 | } 29 | 30 | destination.hierarchy.any { it.route == NavBarItem.Saved.screenRoute } -> { 31 | selectedItem.value = NavBarItem.Saved 32 | } 33 | 34 | destination.hierarchy.any { it.route == NavBarItem.Updates.screenRoute } -> { 35 | selectedItem.value = NavBarItem.Updates 36 | } 37 | } 38 | } 39 | addOnDestinationChangedListener(listener) 40 | 41 | onDispose { 42 | removeOnDestinationChangedListener(listener) 43 | } 44 | } 45 | 46 | return selectedItem 47 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/navigation/NavigationBarUI.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.navigation 2 | 3 | import androidx.compose.material3.Icon 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.NavigationBar 6 | import androidx.compose.material3.NavigationBarItem 7 | import androidx.compose.material3.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.getValue 10 | import androidx.compose.ui.res.painterResource 11 | import androidx.compose.ui.res.stringResource 12 | import androidx.compose.ui.text.style.TextOverflow 13 | import androidx.navigation.NavHostController 14 | 15 | @Composable 16 | fun BottomBarUI( 17 | navController: NavHostController 18 | ) { 19 | val currentSelectedItem by navController.currentScreenAsState() 20 | 21 | NavigationBar { 22 | navBarItems.forEach { item -> 23 | NavigationBarItem( 24 | icon = { 25 | Icon( 26 | painter = painterResource( 27 | if (currentSelectedItem == item || item.iconInactive == null) 28 | item.iconActive 29 | else 30 | item.iconInactive 31 | ), 32 | tint = if (currentSelectedItem == item || item.iconInactive == null) 33 | MaterialTheme.colorScheme.onSecondaryContainer 34 | else 35 | MaterialTheme.colorScheme.onSurfaceVariant, 36 | contentDescription = stringResource(id = item.title) 37 | ) 38 | }, 39 | label = { Text(text = stringResource(id = item.title), maxLines = 1, overflow = TextOverflow.Ellipsis) }, 40 | selected = currentSelectedItem == item, 41 | onClick = { 42 | navController.navigate(item.screenRoute) { 43 | navController.graph.startDestinationRoute?.let { route -> 44 | popUpTo(route) { 45 | saveState = true 46 | } 47 | } 48 | launchSingleTop = true 49 | restoreState = true 50 | } 51 | } 52 | ) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/RootScreen.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens 2 | 3 | import androidx.compose.foundation.layout.fillMaxSize 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material3.Scaffold 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.navigation.NavController 9 | import androidx.navigation.NavHostController 10 | import androidx.navigation.compose.rememberNavController 11 | import ua.polodarb.gmsflags.ui.navigation.BottomBarNavigation 12 | import ua.polodarb.gmsflags.ui.navigation.BottomBarUI 13 | 14 | @Composable 15 | fun RootScreen( 16 | isFirstStart: Boolean, 17 | parentNavController: NavController, 18 | childNavController: NavHostController = rememberNavController() 19 | ) { 20 | Scaffold( 21 | bottomBar = { BottomBarUI(navController = childNavController) } 22 | ) { paddingValues -> 23 | BottomBarNavigation( 24 | isFirstStart = isFirstStart, 25 | parentNavController = parentNavController, 26 | navController = childNavController, 27 | modifier = Modifier 28 | .fillMaxSize() 29 | .padding(bottom = paddingValues.calculateBottomPadding()) 30 | ) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/UiStates.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens 2 | 3 | sealed interface UiStates { 4 | class Loading : UiStates 5 | 6 | data class Success( 7 | val data: T 8 | ) : UiStates 9 | 10 | data class Error( 11 | val throwable: Throwable? = null 12 | ) : UiStates 13 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/flagChange/dialogs/FlagChangeDialog.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.flagChange.dialogs 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.height 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.foundation.layout.width 11 | import androidx.compose.material3.AlertDialog 12 | import androidx.compose.material3.Button 13 | import androidx.compose.material3.MaterialTheme 14 | import androidx.compose.material3.OutlinedTextField 15 | import androidx.compose.material3.Text 16 | import androidx.compose.material3.TextButton 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.res.stringResource 20 | import androidx.compose.ui.text.font.FontWeight 21 | import androidx.compose.ui.unit.dp 22 | import androidx.compose.ui.unit.sp 23 | import ua.polodarb.gmsflags.R 24 | 25 | @Composable 26 | fun FlagChangeDialog( 27 | showDialog: Boolean, 28 | flagName: String, 29 | flagValue: String, 30 | onQueryChange: (String) -> Unit, 31 | flagType: String, 32 | onConfirm: () -> Unit, 33 | onDismiss: () -> Unit, 34 | onDefault: () -> Unit 35 | ) { 36 | 37 | if (showDialog) { 38 | AlertDialog( 39 | onDismissRequest = onDismiss, 40 | title = { Text(text = stringResource(R.string.flag_change_dialog_other_type_title)) }, 41 | text = { 42 | Column { 43 | Text( 44 | text = stringResource(R.string.flag_change_dialog_other_type_name, flagName), 45 | fontSize = 16.sp, 46 | fontWeight = FontWeight.Medium 47 | ) 48 | Spacer(modifier = Modifier.height(4.dp)) 49 | Text( 50 | text = stringResource(R.string.flag_change_dialog_other_type_type, flagType), 51 | fontSize = 14.sp, 52 | fontWeight = FontWeight.Normal 53 | ) 54 | OutlinedTextField( 55 | value = flagValue, 56 | onValueChange = { 57 | onQueryChange(it) 58 | }, 59 | modifier = Modifier.padding(top = 16.dp), 60 | shape = MaterialTheme.shapes.medium 61 | 62 | ) 63 | } 64 | }, 65 | confirmButton = { 66 | Row( 67 | modifier = Modifier.fillMaxWidth() 68 | ) { 69 | TextButton(onClick = onDefault) { 70 | Text(text = stringResource(R.string.flag_change_dialog_other_action_default)) 71 | } 72 | Spacer(modifier = Modifier.weight(1f)) 73 | Row( 74 | horizontalArrangement = Arrangement.End 75 | ) { 76 | TextButton(onClick = onDismiss) { 77 | Text(text = stringResource(id = R.string.close)) 78 | } 79 | Spacer(modifier = Modifier.width(8.dp)) 80 | Button(onClick = onConfirm) { 81 | Text(text = stringResource(R.string.flag_change_dialog_other_action_save)) 82 | } 83 | } 84 | } 85 | } 86 | ) 87 | } 88 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/flagChange/dialogs/ProgressDialog.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.flagChange.dialogs 2 | 3 | import androidx.compose.foundation.layout.fillMaxWidth 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material3.AlertDialog 6 | import androidx.compose.material3.LinearProgressIndicator 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.res.stringResource 12 | import androidx.compose.ui.text.style.TextAlign 13 | import androidx.compose.ui.unit.dp 14 | import ua.polodarb.gmsflags.R 15 | 16 | @Composable 17 | fun ProgressDialog( 18 | showDialog: Boolean 19 | ) { 20 | 21 | if (showDialog) { 22 | AlertDialog( 23 | onDismissRequest = { /*TODO*/ }, 24 | confirmButton = {}, 25 | dismissButton = {}, 26 | title = { Text(text = stringResource(R.string.flag_change_dialog_progress_title), textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth()) }, 27 | text = { 28 | LinearProgressIndicator( 29 | modifier = Modifier 30 | .fillMaxWidth() 31 | .padding(16.dp), 32 | color = MaterialTheme.colorScheme.surfaceVariant, 33 | trackColor = MaterialTheme.colorScheme.secondary, 34 | ) 35 | } 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/flagChange/dialogs/ReportFlagsDialog.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.flagChange.dialogs 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.material3.AlertDialog 10 | import androidx.compose.material3.Button 11 | import androidx.compose.material3.Icon 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.OutlinedTextField 14 | import androidx.compose.material3.Text 15 | import androidx.compose.material3.TextButton 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.res.painterResource 19 | import androidx.compose.ui.res.stringResource 20 | import androidx.compose.ui.text.style.TextAlign 21 | import androidx.compose.ui.unit.dp 22 | import ua.polodarb.gmsflags.R 23 | 24 | @Composable 25 | fun ReportFlagsDialog( 26 | showDialog: Boolean, 27 | flagDesc: String, 28 | onFlagDescChange: (String) -> Unit, 29 | onSend: () -> Unit, 30 | onDismiss: () -> Unit 31 | ) { 32 | 33 | if (showDialog) { 34 | AlertDialog( 35 | onDismissRequest = onDismiss, 36 | icon = { 37 | Icon( 38 | painter = painterResource(id = R.drawable.ic_report), 39 | contentDescription = null, 40 | modifier = Modifier.size(36.dp) 41 | ) 42 | }, 43 | dismissButton = {}, 44 | title = { 45 | Text( 46 | text = stringResource(R.string.flag_change_dialog_report_title), 47 | textAlign = TextAlign.Center, 48 | modifier = Modifier.fillMaxWidth() 49 | ) 50 | }, 51 | text = { 52 | Column { 53 | Text(text = stringResource(R.string.flag_change_dialog_report_text)) 54 | OutlinedTextField( 55 | value = flagDesc, 56 | onValueChange = onFlagDescChange, 57 | placeholder = { 58 | Text( 59 | text = stringResource(R.string.flag_change_dialog_report_placeholder), 60 | ) 61 | }, 62 | modifier = Modifier.padding(top = 16.dp), 63 | shape = MaterialTheme.shapes.medium 64 | 65 | ) 66 | } 67 | }, 68 | confirmButton = { 69 | Row( 70 | modifier = Modifier.fillMaxWidth() 71 | ) { 72 | TextButton(onClick = onDismiss) { 73 | Text(text = stringResource(id = R.string.close)) 74 | } 75 | 76 | Spacer(modifier = Modifier.weight(1f)) 77 | Button(onClick = onSend) { 78 | Text(text = stringResource(R.string.flag_change_dialog_report_action_send)) 79 | } 80 | } 81 | } 82 | ) 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/flagChange/dialogs/SuggestFlagsDialog.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.flagChange.dialogs 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.material3.AlertDialog 10 | import androidx.compose.material3.Button 11 | import androidx.compose.material3.Icon 12 | import androidx.compose.material3.MaterialTheme 13 | import androidx.compose.material3.OutlinedTextField 14 | import androidx.compose.material3.Text 15 | import androidx.compose.material3.TextButton 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.res.painterResource 19 | import androidx.compose.ui.res.stringResource 20 | import androidx.compose.ui.text.style.TextAlign 21 | import androidx.compose.ui.unit.dp 22 | import ua.polodarb.gmsflags.R 23 | 24 | @Composable 25 | fun SuggestFlagsDialog( 26 | showDialog: Boolean, 27 | flagDesc: String, 28 | onFlagDescChange: (String) -> Unit, 29 | senderName: String, 30 | onSenderNameChanged: (String) -> Unit, 31 | onSend: () -> Unit, 32 | onDismiss: () -> Unit 33 | ) { 34 | 35 | if (showDialog) { 36 | AlertDialog( 37 | onDismissRequest = onDismiss, 38 | icon = { 39 | Icon( 40 | painter = painterResource(id = R.drawable.ic_navbar_suggestions_active), 41 | contentDescription = null, 42 | modifier = Modifier.size(36.dp) 43 | ) 44 | }, 45 | dismissButton = {}, 46 | title = { 47 | Text( 48 | text = stringResource(R.string.flag_change_dialog_suggest_title), 49 | textAlign = TextAlign.Center, 50 | modifier = Modifier.fillMaxWidth() 51 | ) 52 | }, 53 | text = { 54 | Column { 55 | Text(text = stringResource(R.string.flag_change_dialog_suggest_text)) 56 | OutlinedTextField( 57 | value = flagDesc, 58 | onValueChange = onFlagDescChange, 59 | placeholder = { 60 | Text( 61 | text = stringResource(R.string.flag_change_dialog_suggest_placeholder_description), 62 | ) 63 | }, 64 | modifier = Modifier.padding(top = 16.dp), 65 | shape = MaterialTheme.shapes.medium 66 | 67 | ) 68 | OutlinedTextField( 69 | value = senderName, 70 | onValueChange = onSenderNameChanged, 71 | placeholder = { 72 | Text( 73 | text = stringResource(R.string.flag_change_dialog_suggest_name), 74 | ) 75 | }, 76 | modifier = Modifier.padding(top = 16.dp), 77 | shape = MaterialTheme.shapes.medium 78 | 79 | ) 80 | } 81 | }, 82 | confirmButton = { 83 | Row( 84 | modifier = Modifier.fillMaxWidth() 85 | ) { 86 | TextButton(onClick = onDismiss) { 87 | Text(text = stringResource(id = R.string.close)) 88 | } 89 | 90 | Spacer(modifier = Modifier.weight(1f)) 91 | Button(onClick = onSend) { 92 | Text(text = stringResource(R.string.flag_change_dialog_suggest_action_send)) 93 | } 94 | } 95 | } 96 | ) 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/history/HistoryScreen.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.history 2 | 3 | import android.widget.Toast 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material.icons.Icons 7 | import androidx.compose.material.icons.filled.Search 8 | import androidx.compose.material.icons.outlined.Settings 9 | import androidx.compose.material3.ExperimentalMaterial3Api 10 | import androidx.compose.material3.Icon 11 | import androidx.compose.material3.IconButton 12 | import androidx.compose.material3.LargeTopAppBar 13 | import androidx.compose.material3.Scaffold 14 | import androidx.compose.material3.Text 15 | import androidx.compose.material3.TopAppBarDefaults 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.hapticfeedback.HapticFeedbackType 19 | import androidx.compose.ui.input.nestedscroll.nestedScroll 20 | import androidx.compose.ui.platform.LocalContext 21 | import androidx.compose.ui.platform.LocalHapticFeedback 22 | import androidx.compose.ui.res.painterResource 23 | import androidx.compose.ui.text.style.TextOverflow 24 | import ua.polodarb.gmsflags.R 25 | import ua.polodarb.gmsflags.ui.components.inserts.NotImplementedScreen 26 | 27 | @OptIn(ExperimentalMaterial3Api::class) 28 | @Composable 29 | fun HistoryScreen( 30 | onSettingsClick: () -> Unit, 31 | onPackagesClick: () -> Unit 32 | ) { 33 | val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() 34 | val context = LocalContext.current 35 | val haptic = LocalHapticFeedback.current 36 | 37 | Scaffold( 38 | modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), 39 | topBar = { 40 | LargeTopAppBar( 41 | title = { 42 | Text( 43 | "History", 44 | maxLines = 1, 45 | overflow = TextOverflow.Ellipsis 46 | ) 47 | }, 48 | actions = { 49 | IconButton(onClick = { 50 | haptic.performHapticFeedback(HapticFeedbackType.LongPress) 51 | Toast.makeText(context, "Search", Toast.LENGTH_SHORT).show() 52 | }) { 53 | Icon( 54 | imageVector = Icons.Filled.Search, 55 | contentDescription = "Localized description" 56 | ) 57 | } 58 | IconButton(onClick = { 59 | haptic.performHapticFeedback(HapticFeedbackType.LongPress) 60 | onPackagesClick() 61 | }) { 62 | Icon( 63 | painterResource(id = R.drawable.ic_packages), 64 | contentDescription = "Localized description" 65 | ) 66 | } 67 | IconButton(onClick = { 68 | haptic.performHapticFeedback(HapticFeedbackType.LongPress) 69 | onSettingsClick() 70 | }) { 71 | Icon( 72 | imageVector = Icons.Outlined.Settings, 73 | contentDescription = "Settings" 74 | ) 75 | } 76 | }, 77 | scrollBehavior = scrollBehavior 78 | ) 79 | } 80 | ) { paddingValues -> 81 | Column(modifier = Modifier.padding(paddingValues)) { 82 | NotImplementedScreen() 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/loadFile/LoadFileScreen.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.loadFile 2 | 3 | import android.net.Uri 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material3.ExperimentalMaterial3Api 8 | import androidx.compose.material3.Scaffold 9 | import androidx.compose.material3.Surface 10 | import androidx.compose.material3.Text 11 | import androidx.compose.material3.TopAppBar 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import java.io.File 16 | 17 | @OptIn(ExperimentalMaterial3Api::class) 18 | @Composable 19 | fun LoadFileScreen( 20 | fileUri: Uri? 21 | ) { 22 | Scaffold( 23 | topBar = { 24 | TopAppBar( 25 | title = { 26 | Text( 27 | text = "Load flags from file", 28 | ) 29 | } 30 | ) 31 | } 32 | ) { 33 | Box( 34 | modifier = Modifier.fillMaxSize().padding(top = it.calculateTopPadding()), 35 | contentAlignment = Alignment.Center 36 | ) { 37 | Text(text = Uri.parse(fileUri.toString()).toString()) 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/loadFile/LoadFileScreenViewModel.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.loadFile 2 | 3 | import androidx.lifecycle.ViewModel 4 | 5 | class LoadFileScreenViewModel: ViewModel() { 6 | 7 | 8 | 9 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/packages/PackagesScreenViewModel.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.packages 2 | 3 | import androidx.compose.runtime.mutableStateOf 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.flow.MutableStateFlow 8 | import kotlinx.coroutines.flow.StateFlow 9 | import kotlinx.coroutines.flow.asStateFlow 10 | import kotlinx.coroutines.launch 11 | import kotlinx.coroutines.withContext 12 | import ua.polodarb.gmsflags.data.repo.GmsDBRepository 13 | import ua.polodarb.gmsflags.data.repo.RoomDBRepository 14 | import ua.polodarb.gmsflags.ui.screens.UiStates 15 | 16 | typealias PackagesScreenUiStates = UiStates> 17 | 18 | class PackagesScreenViewModel( 19 | private val gmsRepository: GmsDBRepository, 20 | private val roomRepository: RoomDBRepository, 21 | ) : ViewModel() { 22 | 23 | private val _state = MutableStateFlow(UiStates.Loading()) 24 | val state: StateFlow = _state.asStateFlow() 25 | 26 | private val _stateSavedPackages = 27 | MutableStateFlow>(emptyList()) 28 | val stateSavedPackages: StateFlow> = _stateSavedPackages.asStateFlow() 29 | 30 | // Search 31 | var searchQuery = mutableStateOf("") 32 | private val listFiltered: MutableMap = mutableMapOf() 33 | 34 | init { 35 | initGmsPackagesList() 36 | getAllSavedPackages() 37 | } 38 | 39 | private fun initGmsPackagesList() { 40 | viewModelScope.launch { 41 | withContext(Dispatchers.IO) { 42 | gmsRepository.getGmsPackages().collect { uiState -> 43 | when (uiState) { 44 | is UiStates.Success -> { 45 | listFiltered.putAll(uiState.data) 46 | getGmsPackagesList() 47 | } 48 | 49 | is UiStates.Loading -> { 50 | _state.value = UiStates.Loading() 51 | } 52 | 53 | is UiStates.Error -> { 54 | _state.value = UiStates.Error() 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | fun getGmsPackagesList() { 63 | if (listFiltered.isNotEmpty()) { 64 | _state.value = UiStates.Success( 65 | listFiltered.filter { 66 | it.key.contains(searchQuery.value, ignoreCase = true) 67 | }.toSortedMap() 68 | ) 69 | } 70 | } 71 | 72 | private fun getAllSavedPackages() { 73 | viewModelScope.launch { 74 | withContext(Dispatchers.IO) { 75 | roomRepository.getSavedPackages().collect { 76 | _stateSavedPackages.value = it 77 | } 78 | } 79 | } 80 | } 81 | 82 | fun savePackage(pkgName: String) { 83 | viewModelScope.launch { 84 | withContext(Dispatchers.IO) { 85 | roomRepository.savePackage(pkgName) 86 | } 87 | } 88 | } 89 | 90 | fun deleteSavedPackage(pkgName: String) { 91 | viewModelScope.launch { 92 | withContext(Dispatchers.IO) { 93 | roomRepository.deleteSavedPackage(pkgName) 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/saved/SavedPackagesScreen.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.saved 2 | 3 | import androidx.compose.foundation.ExperimentalFoundationApi 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.foundation.layout.fillMaxWidth 9 | import androidx.compose.foundation.layout.padding 10 | import androidx.compose.foundation.lazy.LazyColumn 11 | import androidx.compose.foundation.lazy.itemsIndexed 12 | import androidx.compose.material3.HorizontalDivider 13 | import androidx.compose.material3.Icon 14 | import androidx.compose.material3.IconToggleButton 15 | import androidx.compose.material3.Text 16 | import androidx.compose.runtime.Composable 17 | import androidx.compose.ui.Alignment 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.res.painterResource 20 | import androidx.compose.ui.unit.dp 21 | import androidx.compose.ui.unit.sp 22 | import ua.polodarb.gmsflags.R 23 | import ua.polodarb.gmsflags.ui.components.inserts.NoFlagsOrPackages 24 | import ua.polodarb.gmsflags.ui.components.inserts.NotFoundContent 25 | 26 | @OptIn(ExperimentalFoundationApi::class) 27 | @Composable 28 | fun SavedPackagesScreen( 29 | savedPackagesList: List, 30 | viewModel: SavedScreenViewModel, 31 | onPackageClick: (packageName: String) -> Unit 32 | ) { 33 | if (savedPackagesList.isEmpty()) { 34 | NotFoundContent(NoFlagsOrPackages.PACKAGES) 35 | } else { 36 | LazyColumn( 37 | modifier = Modifier.fillMaxSize() 38 | ) { 39 | itemsIndexed(savedPackagesList) { index, item -> 40 | SavedPackagesLazyItem( 41 | packageName = item, 42 | checked = savedPackagesList.contains(item), 43 | onCheckedChange = { 44 | if (!it) viewModel.deleteSavedPackage(item) 45 | }, 46 | lastItem = savedPackagesList.size - 1 == index, 47 | modifier = Modifier 48 | .animateItemPlacement() 49 | .clickable { 50 | onPackageClick(item) 51 | } 52 | ) 53 | } 54 | } 55 | } 56 | } 57 | 58 | @Composable 59 | fun SavedPackagesLazyItem( 60 | modifier: Modifier = Modifier, 61 | packageName: String, 62 | checked: Boolean, 63 | onCheckedChange: (Boolean) -> Unit, 64 | lastItem: Boolean, 65 | ) { 66 | Column(modifier = modifier) { 67 | Row( 68 | modifier = Modifier 69 | .fillMaxWidth() 70 | .padding(vertical = 8.dp), verticalAlignment = Alignment.CenterVertically 71 | ) { 72 | IconToggleButton(checked = checked, onCheckedChange = onCheckedChange) { 73 | if (checked) { 74 | Icon( 75 | painterResource(id = R.drawable.ic_save_active), 76 | contentDescription = null 77 | ) 78 | } else { 79 | Icon( 80 | painterResource(id = R.drawable.ic_save_inactive), 81 | contentDescription = null 82 | ) 83 | } 84 | } 85 | Column(Modifier.weight(0.9f)) { 86 | Text(text = packageName, fontSize = 15.sp) 87 | } 88 | Icon( 89 | modifier = Modifier 90 | .padding(16.dp), 91 | painter = painterResource(id = R.drawable.ic_next), 92 | contentDescription = null 93 | ) 94 | } 95 | } 96 | if (!lastItem) HorizontalDivider(Modifier.padding(horizontal = 16.dp)) 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/saved/SavedScreenViewModel.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.saved 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.flow.StateFlow 8 | import kotlinx.coroutines.flow.asStateFlow 9 | import kotlinx.coroutines.launch 10 | import kotlinx.coroutines.withContext 11 | import ua.polodarb.gmsflags.data.databases.local.enities.SavedFlags 12 | import ua.polodarb.gmsflags.data.repo.RoomDBRepository 13 | 14 | class SavedScreenViewModel( 15 | val roomRepository: RoomDBRepository 16 | ): ViewModel() { 17 | 18 | private val _stateSavedPackages = 19 | MutableStateFlow>(emptyList()) 20 | val stateSavedPackages: StateFlow> = _stateSavedPackages.asStateFlow() 21 | 22 | private val _stateSavedFlags = 23 | MutableStateFlow>(emptyList()) 24 | val stateSavedFlags: StateFlow> = _stateSavedFlags.asStateFlow() 25 | 26 | init { 27 | getAllSavedPackages() 28 | getAllSavedFlags() 29 | } 30 | 31 | // Packages 32 | private fun getAllSavedPackages() { 33 | viewModelScope.launch { 34 | roomRepository.getSavedPackages().collect { 35 | _stateSavedPackages.value = it 36 | } 37 | } 38 | } 39 | 40 | fun deleteSavedPackage(pkgName: String) { 41 | viewModelScope.launch { 42 | roomRepository.deleteSavedPackage(pkgName) 43 | } 44 | } 45 | 46 | // Flags 47 | private fun getAllSavedFlags() { 48 | viewModelScope.launch { 49 | roomRepository.getSavedFlags().collect { 50 | _stateSavedFlags.value = it 51 | } 52 | } 53 | } 54 | 55 | fun deleteSavedFlag(flagName: String, pkgName: String) { 56 | viewModelScope.launch { 57 | roomRepository.deleteSavedFlag(flagName, pkgName) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/search/dialog/AddPackageDialog.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.search.dialog 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material3.AlertDialog 8 | import androidx.compose.material3.Button 9 | import androidx.compose.material3.MaterialTheme 10 | import androidx.compose.material3.OutlinedTextField 11 | import androidx.compose.material3.Text 12 | import androidx.compose.material3.TextButton 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.getValue 15 | import androidx.compose.runtime.mutableStateOf 16 | import androidx.compose.runtime.saveable.rememberSaveable 17 | import androidx.compose.runtime.setValue 18 | import androidx.compose.ui.Modifier 19 | import androidx.compose.ui.res.stringResource 20 | import androidx.compose.ui.text.style.TextAlign 21 | import androidx.compose.ui.unit.dp 22 | import ua.polodarb.gmsflags.R 23 | 24 | @Composable 25 | fun AddPackageDialog( 26 | showDialog: Boolean, 27 | pkgName: String, 28 | onPkgNameChange: (String) -> Unit, 29 | onAdd: () -> Unit, 30 | onDismiss: () -> Unit 31 | ) { 32 | 33 | var isError by rememberSaveable { mutableStateOf(false) } 34 | 35 | if (showDialog) { 36 | AlertDialog( 37 | onDismissRequest = { 38 | isError = false 39 | onDismiss() 40 | }, 41 | dismissButton = {}, 42 | title = { 43 | Text( 44 | text = "Add package", 45 | textAlign = TextAlign.Start, 46 | modifier = Modifier.fillMaxWidth() 47 | ) 48 | }, 49 | text = { 50 | OutlinedTextField( 51 | value = pkgName, 52 | onValueChange = { 53 | isError = false 54 | onPkgNameChange(it) 55 | }, 56 | placeholder = { 57 | if (isError) { 58 | Text( 59 | modifier = Modifier.fillMaxWidth(), 60 | text = "The field cannot be empty", 61 | color = MaterialTheme.colorScheme.error 62 | ) 63 | } else { 64 | Text( 65 | modifier = Modifier.fillMaxWidth(), 66 | text = "Enter package name", 67 | color = MaterialTheme.colorScheme.onSurfaceVariant 68 | ) 69 | } 70 | }, 71 | shape = MaterialTheme.shapes.medium, 72 | isError = isError, 73 | modifier = Modifier.padding(top = 8.dp) 74 | ) 75 | }, 76 | confirmButton = { 77 | Row( 78 | modifier = Modifier.fillMaxWidth() 79 | ) { 80 | TextButton(onClick = onDismiss) { 81 | Text(text = stringResource(id = R.string.close)) 82 | } 83 | Spacer(modifier = Modifier.weight(1f)) 84 | Button(onClick = { 85 | if (pkgName.isEmpty()) { 86 | isError = true 87 | } else { 88 | isError = false 89 | onAdd() 90 | } 91 | }) { 92 | Text(text = "Add") 93 | } 94 | } 95 | } 96 | ) 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/search/dialog/SortAppsDialog.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.search.dialog 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.selection.selectable 10 | import androidx.compose.foundation.selection.selectableGroup 11 | import androidx.compose.material3.AlertDialog 12 | import androidx.compose.material3.Button 13 | import androidx.compose.material3.MaterialTheme 14 | import androidx.compose.material3.RadioButton 15 | import androidx.compose.material3.Text 16 | import androidx.compose.material3.TextButton 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.runtime.mutableStateOf 19 | import androidx.compose.runtime.saveable.rememberSaveable 20 | import androidx.compose.ui.Alignment 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.res.stringResource 23 | import androidx.compose.ui.semantics.Role 24 | import androidx.compose.ui.text.style.TextAlign 25 | import androidx.compose.ui.unit.dp 26 | import ua.polodarb.gmsflags.R 27 | 28 | @Composable 29 | fun SortAppsDialog( 30 | sortTypes: List, 31 | showDialog: Boolean, 32 | onSelect: (sortType: String) -> Unit, 33 | onDismiss: () -> Unit 34 | ) { 35 | 36 | val (selectedOption, onOptionSelected) = rememberSaveable { mutableStateOf(sortTypes[0]) } 37 | 38 | if (showDialog) { 39 | AlertDialog( 40 | onDismissRequest = onDismiss, 41 | dismissButton = {}, 42 | title = { 43 | Text( 44 | text = "Sort options", 45 | textAlign = TextAlign.Start, 46 | modifier = Modifier.fillMaxWidth() 47 | ) 48 | }, 49 | text = { 50 | Column(Modifier.selectableGroup()) { 51 | sortTypes.forEach { text -> 52 | Row( 53 | Modifier 54 | .fillMaxWidth() 55 | .height(56.dp) 56 | .selectable( 57 | selected = (text == selectedOption), 58 | onClick = { onOptionSelected(text) }, 59 | role = Role.RadioButton 60 | ) 61 | .padding(horizontal = 16.dp), 62 | verticalAlignment = Alignment.CenterVertically 63 | ) { 64 | RadioButton( 65 | selected = (text == selectedOption), 66 | onClick = null 67 | ) 68 | Text( 69 | text = text, 70 | style = MaterialTheme.typography.bodyLarge, 71 | modifier = Modifier.padding(start = 16.dp) 72 | ) 73 | } 74 | } 75 | } 76 | }, 77 | confirmButton = { 78 | Row( 79 | modifier = Modifier.fillMaxWidth() 80 | ) { 81 | TextButton(onClick = onDismiss) { 82 | Text(text = stringResource(id = R.string.close)) 83 | } 84 | Spacer(modifier = Modifier.weight(1f)) 85 | Button(onClick = { 86 | onSelect(selectedOption) 87 | }) { 88 | Text(text = "Select") 89 | } 90 | } 91 | } 92 | ) 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/settings/SettingsViewModel.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.settings 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.topjohnwu.superuser.Shell 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.launch 8 | import ua.polodarb.gmsflags.data.repo.SettingsRepository 9 | 10 | private object LocalConstants { 11 | const val PLAY_STORE_PKG = "com.android.vending" 12 | const val STOP_PLAY_STORE_CMD = "am force-stop $PLAY_STORE_PKG" 13 | const val CLEAN_PLAY_STORE_CMD = "rm -rf /data/data/$PLAY_STORE_PKG/files/experiment*" 14 | const val PHOTOS_PKG = "com.google.android.apps.photos" 15 | const val STOP_PHOTOS_CMD = "am force-stop $PHOTOS_PKG" 16 | const val CLEAN_PHOTOS_CMD = "rm -rf /data/data/$PHOTOS_PKG/shared_prefs/*phenotype*" 17 | } 18 | 19 | class SettingsViewModel( 20 | val settingsRepository: SettingsRepository 21 | ) : ViewModel() { 22 | fun deleteAllOverriddenFlagsFromGMS() { 23 | settingsRepository.deleteAllOverriddenFlagsFromGMS() 24 | clearCache() 25 | } 26 | 27 | fun deleteAllOverriddenFlagsFromPlayStore() { 28 | settingsRepository.deleteAllOverriddenFlagsFromPlayStore() 29 | clearCache() 30 | } 31 | 32 | fun deleteAllOverriddenFlags() { 33 | settingsRepository.deleteAllOverriddenFlagsFromGMS() 34 | settingsRepository.deleteAllOverriddenFlagsFromPlayStore() 35 | clearCache() 36 | } 37 | 38 | private fun clearCache() { 39 | viewModelScope.launch(Dispatchers.IO) { 40 | Shell.cmd(LocalConstants.STOP_PLAY_STORE_CMD).exec() 41 | Shell.cmd(LocalConstants.CLEAN_PLAY_STORE_CMD).exec() 42 | Shell.cmd(LocalConstants.STOP_PHOTOS_CMD).exec() 43 | Shell.cmd(LocalConstants.CLEAN_PHOTOS_CMD).exec() 44 | } 45 | } 46 | 47 | fun deleteAllSavedFlags() { 48 | viewModelScope.launch(Dispatchers.IO) { 49 | settingsRepository.deleteAllSavedFlags() 50 | } 51 | } 52 | 53 | fun deleteAllSavedPackages() { 54 | viewModelScope.launch(Dispatchers.IO) { 55 | settingsRepository.deleteAllSavedPackages() 56 | } 57 | } 58 | 59 | fun deleteAllSavedFlagsAndPackages() { 60 | viewModelScope.launch(Dispatchers.IO) { 61 | settingsRepository.deleteAllSavedFlags() 62 | settingsRepository.deleteAllSavedPackages() 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/settings/common/ConfirmationDialog.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.settings.common 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.foundation.Image 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.material3.AlertDialog 10 | import androidx.compose.material3.Button 11 | import androidx.compose.material3.MaterialTheme 12 | import androidx.compose.material3.OutlinedButton 13 | import androidx.compose.material3.Text 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.graphics.ColorFilter 17 | import androidx.compose.ui.res.painterResource 18 | import androidx.compose.ui.res.stringResource 19 | import androidx.compose.ui.text.style.TextAlign 20 | import androidx.compose.ui.unit.dp 21 | import ua.polodarb.gmsflags.R 22 | 23 | @Composable 24 | fun ConfirmationDialog( 25 | showDialog: Boolean, 26 | onDismiss: () -> Unit, 27 | onConfirmClick: () -> Unit, 28 | @StringRes text: Int 29 | ) { 30 | if (showDialog) { 31 | AlertDialog( 32 | onDismissRequest = onDismiss, 33 | icon = { 34 | Image( 35 | painter = painterResource(id = R.drawable.ic_force_stop), 36 | contentDescription = null, 37 | modifier = Modifier.size(36.dp), 38 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface) 39 | ) 40 | }, 41 | title = { 42 | Text(text = stringResource(id = R.string.settings_confirmation_dialog_title)) 43 | }, 44 | text = { 45 | Text( 46 | text = stringResource(id = text), 47 | textAlign = TextAlign.Center, 48 | modifier = Modifier.fillMaxWidth() 49 | ) 50 | }, 51 | confirmButton = { 52 | Row( 53 | modifier = Modifier.fillMaxWidth(), 54 | horizontalArrangement = Arrangement.SpaceBetween 55 | ) { 56 | OutlinedButton( 57 | onClick = onDismiss 58 | ) { 59 | Text(text = stringResource(id = R.string.settings_confirmation_dialog_close)) 60 | } 61 | Button( 62 | onClick = onConfirmClick 63 | ) { 64 | Text(text = stringResource(id = R.string.settings_confirmation_dialog_confirm)) 65 | } 66 | } 67 | }, 68 | ) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/settings/common/HeaderWithIcon.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.settings.common 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.annotation.StringRes 5 | import androidx.compose.foundation.Image 6 | import androidx.compose.foundation.background 7 | import androidx.compose.foundation.layout.Box 8 | import androidx.compose.foundation.layout.Column 9 | import androidx.compose.foundation.layout.fillMaxWidth 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.foundation.layout.size 12 | import androidx.compose.foundation.shape.RoundedCornerShape 13 | import androidx.compose.material.icons.Icons 14 | import androidx.compose.material.icons.outlined.Info 15 | import androidx.compose.material3.Icon 16 | import androidx.compose.material3.MaterialTheme 17 | import androidx.compose.material3.Text 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.ui.Alignment 20 | import androidx.compose.ui.Modifier 21 | import androidx.compose.ui.draw.clip 22 | import androidx.compose.ui.graphics.ColorFilter 23 | import androidx.compose.ui.res.painterResource 24 | import androidx.compose.ui.res.stringResource 25 | import androidx.compose.ui.text.font.FontWeight 26 | import androidx.compose.ui.unit.dp 27 | 28 | @Composable 29 | fun HeaderWithIcon( 30 | @DrawableRes icon: Int, 31 | @StringRes description: Int 32 | ) { 33 | Column { 34 | Box( 35 | modifier = Modifier 36 | .fillMaxWidth() 37 | .padding(24.dp) 38 | .clip(RoundedCornerShape(24.dp)) 39 | .background(MaterialTheme.colorScheme.surfaceContainer), 40 | contentAlignment = Alignment.Center 41 | ) { 42 | Icon( 43 | painter = painterResource(id = icon), 44 | contentDescription = null, 45 | tint = MaterialTheme.colorScheme.onSurfaceVariant, 46 | modifier = Modifier 47 | .padding(36.dp) 48 | .size(112.dp) 49 | ) 50 | } 51 | Image( 52 | imageVector = Icons.Outlined.Info, 53 | contentDescription = null, 54 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant), 55 | modifier = Modifier.padding(start = 24.dp, bottom = 12.dp) 56 | ) 57 | Text( 58 | text = stringResource(id = description), 59 | style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.Medium, 60 | modifier = Modifier.padding(horizontal = 24.dp), 61 | color = MaterialTheme.colorScheme.onSurfaceVariant 62 | ) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestions/SuggestedFlag.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.suggestions 2 | 3 | import ua.polodarb.gmsflags.data.remote.flags.dto.Primary 4 | import ua.polodarb.gmsflags.data.remote.flags.dto.Secondary 5 | 6 | data class SuggestedFlag( 7 | val primary: List, 8 | val secondary: List 9 | ) 10 | 11 | data class PrimarySuggestedFlag( 12 | val flag: Primary, 13 | val enabled: Boolean = false 14 | ) 15 | 16 | data class SecondarySuggestedFlag( 17 | val flag: Secondary, 18 | val enabled: Boolean = false 19 | ) 20 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestions/dialog/ResetFlagToDefaultDialog.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.suggestions.dialog 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.Arrangement 5 | import androidx.compose.foundation.layout.Row 6 | import androidx.compose.foundation.layout.fillMaxWidth 7 | import androidx.compose.foundation.layout.size 8 | import androidx.compose.material3.AlertDialog 9 | import androidx.compose.material3.Button 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.material3.OutlinedButton 12 | import androidx.compose.material3.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.graphics.ColorFilter 16 | import androidx.compose.ui.res.painterResource 17 | import androidx.compose.ui.res.stringResource 18 | import androidx.compose.ui.text.style.TextAlign 19 | import androidx.compose.ui.unit.dp 20 | import ua.polodarb.gmsflags.R 21 | 22 | @Composable 23 | fun ResetFlagToDefaultDialog( 24 | showDialog: Boolean, 25 | onDismiss: () -> Unit, 26 | onConfirmClick: () -> Unit 27 | ) { 28 | if (showDialog) { 29 | AlertDialog( 30 | onDismissRequest = onDismiss, 31 | icon = { 32 | Image( 33 | painter = painterResource(id = R.drawable.ic_reset_flags), 34 | contentDescription = null, 35 | modifier = Modifier.size(36.dp), 36 | colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface) 37 | ) 38 | }, 39 | title = { 40 | Text(text = stringResource(R.string.suggestions_dialog_title), 41 | textAlign = TextAlign.Center) 42 | }, 43 | text = { 44 | Text( 45 | text = stringResource(R.string.suggestions_dialog_text), 46 | textAlign = TextAlign.Start, 47 | modifier = Modifier.fillMaxWidth() 48 | ) 49 | }, 50 | confirmButton = { 51 | Row( 52 | modifier = Modifier.fillMaxWidth(), 53 | horizontalArrangement = Arrangement.SpaceBetween 54 | ) { 55 | OutlinedButton( 56 | onClick = onDismiss 57 | ) { 58 | Text(stringResource(id = R.string.close)) 59 | } 60 | Button( 61 | onClick = onConfirmClick 62 | ) { 63 | Text(stringResource(id = R.string.settings_confirmation_dialog_confirm)) 64 | } 65 | } 66 | }, 67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/screens/updates/UpdatesScreenViewModel.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.screens.updates 2 | 3 | import android.util.Log 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import kotlinx.coroutines.Dispatchers 7 | import kotlinx.coroutines.flow.MutableStateFlow 8 | import kotlinx.coroutines.flow.SharingStarted 9 | import kotlinx.coroutines.flow.StateFlow 10 | import kotlinx.coroutines.flow.stateIn 11 | import kotlinx.coroutines.flow.update 12 | import kotlinx.coroutines.launch 13 | import kotlinx.coroutines.withContext 14 | import ua.polodarb.gmsflags.data.prefs.shared.PreferenceConstants 15 | import ua.polodarb.gmsflags.data.prefs.shared.PreferencesManager 16 | import ua.polodarb.gmsflags.data.remote.googleUpdates.GoogleAppUpdatesService 17 | import ua.polodarb.gmsflags.data.repo.mappers.GoogleUpdatesMapper 18 | import ua.polodarb.gmsflags.data.repo.mappers.NewRssArticle 19 | import ua.polodarb.gmsflags.ui.screens.UiStates 20 | 21 | class UpdatesScreenViewModel( 22 | private val googleAppUpdatesService: GoogleAppUpdatesService, 23 | private val googleUpdatesMapper: GoogleUpdatesMapper, 24 | private val sharedPrefs: PreferencesManager 25 | ) : ViewModel() { 26 | 27 | private val _uiState = MutableStateFlow>>(UiStates.Loading()) 28 | val uiState: StateFlow>> = _uiState.stateIn( 29 | viewModelScope, 30 | SharingStarted.WhileSubscribed(5000), 31 | UiStates.Loading() 32 | ) 33 | 34 | init { 35 | loadArticles() 36 | } 37 | 38 | fun loadArticles() { 39 | _uiState.update { UiStates.Loading() } 40 | viewModelScope.launch { 41 | try { 42 | val result = withContext(Dispatchers.IO) { 43 | val response = googleAppUpdatesService.getLatestRelease() 44 | googleUpdatesMapper.map(response) 45 | } 46 | _uiState.update { 47 | UiStates.Success(result.articles) 48 | } 49 | sharedPrefs.saveData( 50 | PreferenceConstants.GOOGLE_LAST_UPDATE, 51 | "${result.articles.first().title}/${result.articles.first().version}" 52 | ) 53 | } catch (err: Throwable) { 54 | _uiState.update { UiStates.Error(err) } 55 | } 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val md_theme_light_primary = Color(0xFF6750A4) 6 | val md_theme_light_onPrimary = Color(0xFFFFFFFF) 7 | val md_theme_light_primaryContainer = Color(0xFFEADDFF) 8 | val md_theme_light_onPrimaryContainer = Color(0xFF21005D) 9 | val md_theme_light_secondary = Color(0xFF625B71) 10 | val md_theme_light_onSecondary = Color(0xFFFFFFFF) 11 | val md_theme_light_secondaryContainer = Color(0xFFE8DEF8) 12 | val md_theme_light_onSecondaryContainer = Color(0xFF1D192B) 13 | val md_theme_light_tertiary = Color(0xFF7D5260) 14 | val md_theme_light_onTertiary = Color(0xFFFFFFFF) 15 | val md_theme_light_tertiaryContainer = Color(0xFFFFD8E4) 16 | val md_theme_light_onTertiaryContainer = Color(0xFF31111D) 17 | val md_theme_light_error = Color(0xFFB3261E) 18 | val md_theme_light_onError = Color(0xFFFFFFFF) 19 | val md_theme_light_errorContainer = Color(0xFFF9DEDC) 20 | val md_theme_light_onErrorContainer = Color(0xFF410E0B) 21 | val md_theme_light_outline = Color(0xFF79747E) 22 | val md_theme_light_background = Color(0xFFFFFBFE) 23 | val md_theme_light_onBackground = Color(0xFF1C1B1F) 24 | val md_theme_light_surface = Color(0xFFFFFBFE) 25 | val md_theme_light_onSurface = Color(0xFF1C1B1F) 26 | val md_theme_light_surfaceVariant = Color(0xFFE7E0EC) 27 | val md_theme_light_onSurfaceVariant = Color(0xFF49454F) 28 | val md_theme_light_inverseSurface = Color(0xFF313033) 29 | val md_theme_light_inverseOnSurface = Color(0xFFF4EFF4) 30 | val md_theme_light_inversePrimary = Color(0xFFD0BCFF) 31 | val md_theme_light_shadow = Color(0xFF000000) 32 | val md_theme_light_surfaceTint = Color(0xFF6750A4) 33 | val md_theme_light_outlineVariant = Color(0xFFCAC4D0) 34 | val md_theme_light_scrim = Color(0xFF000000) 35 | 36 | val md_theme_dark_primary = Color(0xFFD0BCFF) 37 | val md_theme_dark_onPrimary = Color(0xFF381E72) 38 | val md_theme_dark_primaryContainer = Color(0xFF4F378B) 39 | val md_theme_dark_onPrimaryContainer = Color(0xFFEADDFF) 40 | val md_theme_dark_secondary = Color(0xFFCCC2DC) 41 | val md_theme_dark_onSecondary = Color(0xFF332D41) 42 | val md_theme_dark_secondaryContainer = Color(0xFF4A4458) 43 | val md_theme_dark_onSecondaryContainer = Color(0xFFE8DEF8) 44 | val md_theme_dark_tertiary = Color(0xFFEFB8C8) 45 | val md_theme_dark_onTertiary = Color(0xFF492532) 46 | val md_theme_dark_tertiaryContainer = Color(0xFF633B48) 47 | val md_theme_dark_onTertiaryContainer = Color(0xFFFFD8E4) 48 | val md_theme_dark_error = Color(0xFFF2B8B5) 49 | val md_theme_dark_onError = Color(0xFF601410) 50 | val md_theme_dark_errorContainer = Color(0xFF8C1D18) 51 | val md_theme_dark_onErrorContainer = Color(0xFFF9DEDC) 52 | val md_theme_dark_outline = Color(0xFF938F99) 53 | val md_theme_dark_background = Color(0xFF1C1B1F) 54 | val md_theme_dark_onBackground = Color(0xFFE6E1E5) 55 | val md_theme_dark_surface = Color(0xFF1C1B1F) 56 | val md_theme_dark_onSurface = Color(0xFFE6E1E5) 57 | val md_theme_dark_surfaceVariant = Color(0xFF49454F) 58 | val md_theme_dark_onSurfaceVariant = Color(0xFFCAC4D0) 59 | val md_theme_dark_inverseSurface = Color(0xFFE6E1E5) 60 | val md_theme_dark_inverseOnSurface = Color(0xFF313033) 61 | val md_theme_dark_inversePrimary = Color(0xFF6750A4) 62 | val md_theme_dark_shadow = Color(0xFF000000) 63 | val md_theme_dark_surfaceTint = Color(0xFFD0BCFF) 64 | val md_theme_dark_outlineVariant = Color(0xFF49454F) 65 | val md_theme_dark_scrim = Color(0xFF000000) 66 | 67 | 68 | val seed = Color(0xFF6750A4) 69 | -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/java/ua/polodarb/gmsflags/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags.utils 2 | 3 | import android.annotation.SuppressLint 4 | 5 | @SuppressLint("SdCardPath") 6 | object Constants { 7 | const val DB_PATH_GMS = "/data/data/com.google.android.gms/databases/phenotype.db" 8 | const val DB_PATH_VENDING = "/data/data/com.android.vending/databases/phenotype.db" 9 | const val GMS_DATABASE_CRASH_MSG = "RootDatabase is not initialized yet." 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_activate_all.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_flag_list.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_disable_selected.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_enable_selected.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_filter.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_flag_reset_new.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_force_stop.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_dark.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_light.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_navbar_apps.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_navbar_history.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_navbar_packages.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_navbar_suggestions_active.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_navbar_suggestions_inactive.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_next.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_notify_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_open_app_settings.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_packages.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_question.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh_flags_list.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_report.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_report_fill.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_reset.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_reset_flags.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_reset_saved.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_save_active.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_save_inactive.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_select_all.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sort.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_suggestions.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_telegram.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_update_app.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_updates_active.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_updates_inactive.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/star_background.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /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.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night-v31/splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/values-v31/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | @color/material_dynamic_primary40 3 | @color/material_dynamic_primary100 4 | @color/material_dynamic_primary90 5 | @color/material_dynamic_primary10 6 | @color/material_dynamic_secondary40 7 | @color/material_dynamic_secondary100 8 | @color/material_dynamic_secondary90 9 | @color/material_dynamic_secondary10 10 | @color/material_dynamic_tertiary40 11 | @color/material_dynamic_tertiary100 12 | @color/material_dynamic_tertiary90 13 | @color/material_dynamic_tertiary10 14 | @color/material_dynamic_neutral_variant50 15 | @color/material_dynamic_neutral99 16 | @color/material_dynamic_neutral10 17 | @color/material_dynamic_neutral99 18 | @color/material_dynamic_neutral10 19 | @color/material_dynamic_neutral_variant90 20 | @color/material_dynamic_neutral_variant30 21 | @color/material_dynamic_neutral20 22 | @color/material_dynamic_neutral95 23 | @color/material_dynamic_primary80 24 | @color/material_dynamic_primary80 25 | @color/material_dynamic_primary20 26 | @color/material_dynamic_primary30 27 | @color/material_dynamic_primary90 28 | @color/material_dynamic_secondary80 29 | @color/material_dynamic_secondary20 30 | @color/material_dynamic_secondary30 31 | @color/material_dynamic_secondary90 32 | @color/material_dynamic_tertiary80 33 | @color/material_dynamic_tertiary20 34 | @color/material_dynamic_tertiary30 35 | @color/material_dynamic_tertiary90 36 | @color/material_dynamic_neutral_variant60 37 | @color/material_dynamic_neutral10 38 | @color/material_dynamic_neutral90 39 | @color/material_dynamic_neutral10 40 | @color/material_dynamic_neutral80 41 | @color/material_dynamic_neutral_variant30 42 | @color/material_dynamic_neutral_variant80 43 | @color/material_dynamic_neutral90 44 | @color/material_dynamic_neutral20 45 | @color/material_dynamic_primary40 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/values-v31/splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | #6750A4 3 | #6750A4 4 | #FFFFFF 5 | #EADDFF 6 | #21005D 7 | #625B71 8 | #FFFFFF 9 | #E8DEF8 10 | #1D192B 11 | #7D5260 12 | #FFFFFF 13 | #FFD8E4 14 | #31111D 15 | #B3261E 16 | #FFFFFF 17 | #F9DEDC 18 | #410E0B 19 | #79747E 20 | #FFFBFE 21 | #1C1B1F 22 | #FFFBFE 23 | #1C1B1F 24 | #E7E0EC 25 | #49454F 26 | #313033 27 | #F4EFF4 28 | #D0BCFF 29 | #000000 30 | #6750A4 31 | #CAC4D0 32 | #000000 33 | #D0BCFF 34 | #381E72 35 | #4F378B 36 | #EADDFF 37 | #CCC2DC 38 | #332D41 39 | #4A4458 40 | #E8DEF8 41 | #EFB8C8 42 | #492532 43 | #633B48 44 | #FFD8E4 45 | #F2B8B5 46 | #601410 47 | #8C1D18 48 | #F9DEDC 49 | #938F99 50 | #1C1B1F 51 | #E6E1E5 52 | #1C1B1F 53 | #E6E1E5 54 | #49454F 55 | #CAC4D0 56 | #E6E1E5 57 | #313033 58 | #6750A4 59 | #000000 60 | #D0BCFF 61 | #49454F 62 | #000000 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #F1F3F6 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/xml/filepaths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 15 | 18 | -------------------------------------------------------------------------------- /app/src/test/java/ua/polodarb/gmsflags/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package ua.polodarb.gmsflags 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | dependencies { 3 | classpath(libs.android.gradle) 4 | classpath(libs.firebase.crashlytics.gradle) 5 | } 6 | } 7 | 8 | plugins { 9 | alias(libs.plugins.android.application) apply false 10 | alias(libs.plugins.android.library) apply false 11 | alias(libs.plugins.kotlin.android) apply false 12 | alias(libs.plugins.kotlin.serialization) apply false 13 | alias(libs.plugins.ksp) apply false 14 | alias(libs.plugins.gms) apply false 15 | alias(libs.plugins.firebase.crashlytics) apply false 16 | alias(libs.plugins.firebase.perf) apply false 17 | alias(libs.plugins.detekt) apply false 18 | } 19 | -------------------------------------------------------------------------------- /config/detekt/detekt.yml: -------------------------------------------------------------------------------- 1 | naming: 2 | FunctionNaming: 3 | ignoreAnnotated: ['Composable'] 4 | TopLevelPropertyNaming: 5 | constantPattern: '[A-Z][A-Za-z0-9]*' 6 | 7 | complexity: 8 | LongParameterList: 9 | ignoreDefaultParameters: true 10 | 11 | style: 12 | MagicNumber: 13 | ignorePropertyDeclaration: true 14 | ignoreCompanionObjectPropertyDeclaration: true 15 | UnusedPrivateMember: 16 | ignoreAnnotated: ['Preview'] 17 | -------------------------------------------------------------------------------- /gf_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/gf_banner.png -------------------------------------------------------------------------------- /gf_root.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/gf_root.png -------------------------------------------------------------------------------- /gh_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/gh_download.png -------------------------------------------------------------------------------- /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=-Xmx2048m -Dfile.encoding=UTF-8 10 | # Gradle Configuration cache 11 | # https://www.gradle.org/docs/current/userguide/configuration_cache.html 12 | org.gradle.configuration-cache=true 13 | # When configured, Gradle will run in incubating parallel mode. 14 | # This option should only be used with decoupled projects. More details, visit 15 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 16 | # org.gradle.parallel=true 17 | # AndroidX package structure to make it clearer which packages are bundled with the 18 | # Android operating system, and which are packaged with your app's APK 19 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 20 | android.useAndroidX=true 21 | # Kotlin code style for this project: "official" or "obsolete": 22 | kotlin.code.style=official 23 | # Enables namespacing of each library's R class so that its R class includes only the 24 | # resources declared in the library itself and none from the library's dependencies, 25 | # thereby reducing the size of the R class for that library 26 | android.nonTransitiveRClass=true 27 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polodarb/GMS-Flags/c9a1cf4fb68ccaec784af881b64e83f24a631976/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Apr 06 19:31:15 EEST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { 14 | url = uri("https://jitpack.io") 15 | } 16 | } 17 | } 18 | rootProject.name = "GMS Flags" 19 | include(":app") 20 | --------------------------------------------------------------------------------