├── .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 | 
3 | 
4 | 
5 | [](https://t.me/gmsflags)
6 |
7 | 
8 | 
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 | [
](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