diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ff9ec7cd..e31f3ad7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("com.google.devtools.ksp") + id("org.jetbrains.kotlin.plugin.serialization") } android { @@ -12,19 +13,21 @@ android { applicationId = "ua.polodarb.gmsflags" minSdk = 29 targetSdk = 33 - versionCode = 3 - versionName = "1.0.2" + versionCode = 4 + versionName = "1.0.3" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } } - buildTypes { getByName("release") { isMinifyEnabled = true - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) } getByName("debug") { isMinifyEnabled = false @@ -51,6 +54,19 @@ android { dependencies { + // KTOR + val ktorVersion = "2.3.4" + implementation("io.ktor:ktor-client-core:$ktorVersion") + implementation("io.ktor:ktor-client-android:$ktorVersion") + implementation("io.ktor:ktor-client-serialization:$ktorVersion") + implementation("io.ktor:ktor-client-logging:$ktorVersion") + implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") + implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") + + // Kotlin JSON Serialization + val serializationVersion = "1.5.1" + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion") + // Coil implementation("io.coil-kt:coil-compose:2.4.0") @@ -87,4 +103,4 @@ dependencies { androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.5.1") debugImplementation("androidx.compose.ui:ui-tooling:1.5.1") debugImplementation("androidx.compose.ui:ui-test-manifest:1.5.1") -} \ No newline at end of file +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 406ab6b9..6c354738 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -19,6 +19,7 @@ # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile +-dontwarn org.slf4j.LoggerFactory -dontobfuscate -keepattributes LineNumberTable,SourceFile -renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 35c48dd2..3be3f792 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + @@ -23,6 +24,7 @@ android:theme="@style/AppTheme"/> diff --git a/app/src/main/java/ua/polodarb/gmsflags/GMSApplication.kt b/app/src/main/java/ua/polodarb/gmsflags/GMSApplication.kt index 784ce773..545727cc 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/GMSApplication.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/GMSApplication.kt @@ -5,14 +5,11 @@ import android.content.ComponentName import android.content.Intent import android.content.ServiceConnection import android.os.IBinder +import com.google.android.material.color.DynamicColors import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.ipc.RootService -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch import org.koin.android.BuildConfig import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger @@ -27,9 +24,13 @@ import ua.polodarb.gmsflags.ui.ExceptionHandler data class DatabaseInitializationState(val isInitialized: Boolean) class GMSApplication : Application() { + private companion object { + const val SHELL_TIMEOUT = 10L + } + private val shellConfig = Shell.Builder.create() .setFlags(Shell.FLAG_REDIRECT_STDERR or Shell.FLAG_MOUNT_MASTER) - .setTimeout(10) + .setTimeout(SHELL_TIMEOUT) private val _databaseInitializationStateFlow = MutableStateFlow(DatabaseInitializationState(false)) val databaseInitializationStateFlow: Flow = _databaseInitializationStateFlow @@ -46,6 +47,8 @@ class GMSApplication : Application() { ExceptionHandler.initialize(this, CrashActivity::class.java) + DynamicColors.applyToActivitiesIfAvailable(this) + startKoin { androidLogger(if (BuildConfig.DEBUG) Level.DEBUG else Level.NONE) androidContext(this@GMSApplication) @@ -83,9 +86,7 @@ class GMSApplication : Application() { } fun getRootDatabase(): IRootDatabase { - if (!isRootDatabaseInitialized) { - throw IllegalStateException("RootDatabase is not initialized yet.") - } + check (isRootDatabaseInitialized) { "RootDatabase is not initialized yet." } return rootDatabase } } diff --git a/app/src/main/java/ua/polodarb/gmsflags/core/Constants.kt b/app/src/main/java/ua/polodarb/gmsflags/core/Constants.kt index 83c042c4..93b27df7 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/core/Constants.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/core/Constants.kt @@ -2,7 +2,6 @@ package ua.polodarb.gmsflags.core object Constants { const val TAG = "RootDatabase" - const val DB_PATH = "/data/data/com.google.android.gms/databases/phenotype.db" - const val GET_GMS_PACKAGES = - "SELECT packageName, COUNT(DISTINCT name) FROM Flags group by packageName" + const val DB_PATH_GMS = "/data/data/com.google.android.gms/databases/phenotype.db" + const val DB_PATH_VENDING = "/data/data/com.android.vending/databases/phenotype.db" } \ No newline at end of file diff --git a/app/src/main/java/ua/polodarb/gmsflags/core/Extensions.kt b/app/src/main/java/ua/polodarb/gmsflags/core/Extensions.kt index db979726..6be80d44 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/core/Extensions.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/core/Extensions.kt @@ -17,6 +17,11 @@ import androidx.compose.ui.unit.dp object Extensions { + fun String.toFormattedInt(): Int { + val digits = this.filter { it.isDigit() } + return digits.toIntOrNull() ?: 0 + } + fun Modifier.customTabIndicatorOffset( currentTabPosition: TabPosition ): Modifier = composed( diff --git a/app/src/main/java/ua/polodarb/gmsflags/data/databases/gms/RootDatabase.kt b/app/src/main/java/ua/polodarb/gmsflags/data/databases/gms/RootDatabase.kt index 4770071e..179e81bb 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/data/databases/gms/RootDatabase.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/data/databases/gms/RootDatabase.kt @@ -11,18 +11,20 @@ import io.requery.android.database.sqlite.SQLiteDatabase import io.requery.android.database.sqlite.SQLiteDatabase.OPEN_READWRITE import io.requery.android.database.sqlite.SQLiteDatabase.openDatabase import ua.polodarb.gmsflags.IRootDatabase -import ua.polodarb.gmsflags.core.Constants.DB_PATH -import ua.polodarb.gmsflags.core.Constants.GET_GMS_PACKAGES +import ua.polodarb.gmsflags.core.Constants.DB_PATH_GMS +import ua.polodarb.gmsflags.core.Constants.DB_PATH_VENDING import ua.polodarb.gmsflags.core.Constants.TAG @SuppressLint("SdCardPath") class RootDatabase : RootService() { - private lateinit var db: SQLiteDatabase + private lateinit var gmsDB: SQLiteDatabase + private lateinit var vendingDB: SQLiteDatabase override fun onBind(intent: Intent): IBinder { try { - db = openDatabase(DB_PATH, null, OPEN_READWRITE) + gmsDB = openDatabase(DB_PATH_GMS, null, OPEN_READWRITE) + vendingDB = openDatabase(DB_PATH_VENDING, null, OPEN_READWRITE) } catch (e: SQLiteException) { // TODO: Handle exception Log.wtf(TAG, e.message) @@ -104,17 +106,19 @@ class RootDatabase : RootService() { } override fun onUnbind(intent: Intent): Boolean { - if (db.isOpen) db.close() + if (gmsDB.isOpen) gmsDB.close() + if (vendingDB.isOpen) vendingDB.close() return super.onUnbind(intent) } override fun onDestroy() { - if (db.isOpen) db.close() + if (gmsDB.isOpen) gmsDB.close() + if (vendingDB.isOpen) vendingDB.close() super.onDestroy() } fun getGooglePackages(): List { - val cursor = db.rawQuery( + val cursor = gmsDB.rawQuery( "SELECT DISTINCT P.androidPackageName\n" + "FROM Packages P\n" + "JOIN (\n" + @@ -133,7 +137,7 @@ class RootDatabase : RootService() { } fun getUsers(): MutableList { - val cursor = db.rawQuery( + val cursor = gmsDB.rawQuery( "SELECT DISTINCT user FROM Flags WHERE user IS NOT \"\";", null ) val list = mutableListOf() @@ -146,7 +150,7 @@ class RootDatabase : RootService() { } fun getListByPackages(pkgName: String): List { - val cursor = db.rawQuery( + val cursor = gmsDB.rawQuery( "SELECT DISTINCT packageName FROM Flags WHERE packageName LIKE '%$pkgName%';", null ) val list = mutableListOf() @@ -159,7 +163,7 @@ class RootDatabase : RootService() { } fun androidPackage(pkgName: String): String { - val cursor = db.rawQuery( + val cursor = gmsDB.rawQuery( "SELECT androidPackageName FROM Packages WHERE packageName = '$pkgName' LIMIT 1;", null ) val androidPackage = if (cursor.moveToFirst()) return cursor.getString(0) else "" // todo @@ -174,7 +178,11 @@ class RootDatabase : RootService() { val whereClause = "packageName = ? AND name = ?" val whereArgs = arrayOf(packageName, name) - db.delete("FlagOverrides", whereClause, whereArgs) + gmsDB.delete("FlagOverrides", whereClause, whereArgs) + + if (packageName.contains("finsky") || packageName.contains("vending")) { + vendingDB.delete("FlagOverrides", whereClause, whereArgs) + } } fun deleteOverriddenFlagByPackage( @@ -183,7 +191,11 @@ class RootDatabase : RootService() { val whereClause = "packageName = ?" val whereArgs = arrayOf(packageName) - db.delete("FlagOverrides", whereClause, whereArgs) + gmsDB.delete("FlagOverrides", whereClause, whereArgs) + + if (packageName.contains("finsky") || packageName.contains("vending")) { + vendingDB.delete("FlagOverrides", whereClause, whereArgs) + } } fun overrideFlag( @@ -211,12 +223,16 @@ class RootDatabase : RootService() { put("committed", committed) } - db.insertWithOnConflict("FlagOverrides", null, values, SQLiteDatabase.CONFLICT_REPLACE) + gmsDB.insertWithOnConflict("FlagOverrides", null, values, SQLiteDatabase.CONFLICT_REPLACE) + + if (packageName?.contains("finsky") == true || packageName?.contains("vending") == true) { + vendingDB.insertWithOnConflict("FlagOverrides", null, values, SQLiteDatabase.CONFLICT_REPLACE) + } } private fun getBoolFlags(pkgName: String): Map { - val cursor = db.rawQuery( + val cursor = gmsDB.rawQuery( "SELECT DISTINCT f.name, COALESCE(fo.boolVal, f.boolVal) " + "AS boolVal FROM Flags f LEFT JOIN " + "(SELECT name, boolVal FROM FlagOverrides) fo " + @@ -231,11 +247,26 @@ class RootDatabase : RootService() { list[cursor.getString(0)] = cursor.getString(1) } cursor.close() + + val cursorVending = vendingDB.rawQuery( + "SELECT DISTINCT f.name, COALESCE(fo.boolVal, f.boolVal) " + + "AS boolVal FROM Flags f LEFT JOIN " + + "(SELECT name, boolVal FROM FlagOverrides) fo " + + "ON f.name = fo.name " + + "WHERE f.packageName = '$pkgName' " + // pkgName + "AND f.boolVal IS NOT NULL " + + "ORDER BY f.name ASC;", + null + ) + while (cursorVending.moveToNext()) { + list[cursorVending.getString(0)] = cursorVending.getString(1) + } + cursorVending.close() return list.toMap() } private fun getIntFlags(pkgName: String): Map { - val cursor = db.rawQuery( + val cursor = gmsDB.rawQuery( "SELECT DISTINCT f.name, COALESCE(fo.intVal, f.intVal) " + "AS intVal FROM Flags f LEFT JOIN " + "(SELECT name, intVal FROM FlagOverrides) fo " + @@ -249,11 +280,25 @@ class RootDatabase : RootService() { list[cursor.getString(0)] = cursor.getString(1) } cursor.close() + + val cursorVending = vendingDB.rawQuery( + "SELECT DISTINCT f.name, COALESCE(fo.intVal, f.intVal) " + + "AS intVal FROM Flags f LEFT JOIN " + + "(SELECT name, intVal FROM FlagOverrides) fo " + + "ON f.name = fo.name " + + "WHERE f.packageName = '$pkgName' " + // pkgName + "AND f.intVal IS NOT NULL;", + null + ) + while (cursorVending.moveToNext()) { + list[cursorVending.getString(0)] = cursorVending.getString(1) + } + cursorVending.close() return list.toMap() } private fun getFloatFlags(pkgName: String): Map { - val cursor = db.rawQuery( + val cursor = gmsDB.rawQuery( "SELECT DISTINCT f.name, COALESCE(fo.floatVal, f.floatVal) " + "AS floatVal FROM Flags f LEFT JOIN " + "(SELECT name, floatVal FROM FlagOverrides) fo " + @@ -267,11 +312,25 @@ class RootDatabase : RootService() { list[cursor.getString(0)] = cursor.getString(1) } cursor.close() + + val cursorVending = vendingDB.rawQuery( + "SELECT DISTINCT f.name, COALESCE(fo.floatVal, f.floatVal) " + + "AS floatVal FROM Flags f LEFT JOIN " + + "(SELECT name, floatVal FROM FlagOverrides) fo " + + "ON f.name = fo.name " + + "WHERE f.packageName = '$pkgName' " + // pkgName + "AND f.floatVal IS NOT NULL;", + null + ) + while (cursorVending.moveToNext()) { + list[cursorVending.getString(0)] = cursorVending.getString(1) + } + cursorVending.close() return list.toMap() } private fun getStringFlags(pkgName: String): Map { - val cursor = db.rawQuery( + val cursor = gmsDB.rawQuery( "SELECT DISTINCT f.name, COALESCE(fo.stringVal, f.stringVal) " + "AS stringVal FROM Flags f LEFT JOIN " + "(SELECT name, stringVal FROM FlagOverrides) fo " + @@ -286,11 +345,26 @@ class RootDatabase : RootService() { list[cursor.getString(0)] = cursor.getString(1) } cursor.close() + + val cursorVending = vendingDB.rawQuery( + "SELECT DISTINCT f.name, COALESCE(fo.stringVal, f.stringVal) " + + "AS stringVal FROM Flags f LEFT JOIN " + + "(SELECT name, stringVal FROM FlagOverrides) fo " + + "ON f.name = fo.name " + + "WHERE f.packageName = '$pkgName' " + // pkgName + "AND f.stringVal IS NOT NULL " + + "AND f.stringVal <> '';", + null + ) + while (cursorVending.moveToNext()) { + list[cursorVending.getString(0)] = cursorVending.getString(1) + } + cursorVending.close() return list.toMap() } private fun getOverriddenBoolFlagsByPackage(pkgName: String?): Map { - val cursor = db.rawQuery( + val cursor = gmsDB.rawQuery( "SELECT DISTINCT name, boolVal FROM FlagOverrides WHERE packageName = '$pkgName';", null ) @@ -301,12 +375,23 @@ class RootDatabase : RootService() { } while (cursor.moveToNext()) } cursor.close() + + val cursorVending = vendingDB.rawQuery( + "SELECT DISTINCT name, boolVal FROM FlagOverrides WHERE packageName = '$pkgName';", + null + ) + if (cursorVending.moveToFirst()) { + do { + list[cursorVending.getString(0)] = cursorVending.getString(1) + } while (cursorVending.moveToNext()) + } + cursorVending.close() return list } - private fun getOverriddenIntFlagsByPackage(pkgName: String): Map { - val cursor = db.rawQuery( + private fun getOverriddenIntFlagsByPackage(pkgName: String): Map { // todo: not used + val cursor = gmsDB.rawQuery( "SELECT DISTINCT name, intVal FROM FlagOverrides WHERE packageName = \"$pkgName\";", null ) @@ -318,8 +403,8 @@ class RootDatabase : RootService() { return list.toMap() } - private fun getOverriddenFloatFlagsByPackage(pkgName: String): Map { - val cursor = db.rawQuery( + private fun getOverriddenFloatFlagsByPackage(pkgName: String): Map { // todo: not used + val cursor = gmsDB.rawQuery( "SELECT DISTINCT name, floatVal FROM FlagOverrides WHERE packageName = \"$pkgName\";", null ) @@ -331,8 +416,8 @@ class RootDatabase : RootService() { return list.toMap() } - private fun getOverriddenStringFlagsByPackage(pkgName: String): Map { - val cursor = db.rawQuery( + private fun getOverriddenStringFlagsByPackage(pkgName: String): Map { // todo: not used + val cursor = gmsDB.rawQuery( "SELECT DISTINCT name, stringVal FROM FlagOverrides WHERE packageName = \"$pkgName\";", null ) @@ -345,7 +430,7 @@ class RootDatabase : RootService() { } fun getAllOverriddenBoolFlags(): Map { - val cursor = db.rawQuery( + val cursor = gmsDB.rawQuery( "SELECT DISTINCT name, boolVal\n" + "FROM FlagOverrides\n" + "WHERE name IS NOT NULL AND boolVal IS NOT NULL;\n", @@ -356,16 +441,33 @@ class RootDatabase : RootService() { list[cursor.getString(0)] = cursor.getString(1) } cursor.close() + + val cursorVending = vendingDB.rawQuery( + "SELECT DISTINCT name, boolVal\n" + + "FROM FlagOverrides\n" + + "WHERE name IS NOT NULL AND boolVal IS NOT NULL;\n", + null + ) + while (cursorVending.moveToNext()) { + list[cursorVending.getString(0)] = cursorVending.getString(1) + } + cursorVending.close() return list.toMap() } private fun getGmsPackages(): Map { - val cursor = db.rawQuery(GET_GMS_PACKAGES, null) + val cursor = gmsDB.rawQuery("SELECT packageName, COUNT(DISTINCT name) FROM Flags group by packageName", null) val list = mutableMapOf() while (cursor.moveToNext()) { list[cursor.getString(0)] = cursor.getString(1) } cursor.close() + + val cursorVending = vendingDB.rawQuery("SELECT packageName, COUNT(DISTINCT name) FROM Flags group by packageName", null) + while (cursorVending.moveToNext()) { + list[cursorVending.getString(0)] = cursorVending.getString(1) + } + cursorVending.close() return list.toMap() } diff --git a/app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubApiRoutes.kt b/app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubApiRoutes.kt new file mode 100644 index 00000000..df3433e0 --- /dev/null +++ b/app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubApiRoutes.kt @@ -0,0 +1,6 @@ +package ua.polodarb.gmsflags.data.remote.github + +object GithubApiRoutes { + private const val BASE_URL = "https://api.github.com/repos/polodarb/GMS-Flags" + const val RELEASE_UPDATES = "$BASE_URL/releases/latest" +} \ No newline at end of file diff --git a/app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubApiService.kt b/app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubApiService.kt new file mode 100644 index 00000000..b51a83c9 --- /dev/null +++ b/app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubApiService.kt @@ -0,0 +1,44 @@ +package ua.polodarb.gmsflags.data.remote.github + +import io.ktor.client.HttpClient +import io.ktor.client.engine.android.Android +import io.ktor.client.plugins.HttpTimeout +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.serialization.kotlinx.json.json + +interface GithubApiService { + + suspend fun getLatestRelease(): GithubUpdateModel + + companion object { + fun create(): GithubApiService { + return GithubApiServiceImpl( + client = HttpClient(Android) { + // Logging + install(Logging) { + level = LogLevel.ALL + } + // JSON + install(ContentNegotiation) { + json(json) + } + // Timeout + install(HttpTimeout) { + requestTimeoutMillis = 15000L + connectTimeoutMillis = 15000L + socketTimeoutMillis = 15000L + } + } + ) + } + + private val json = kotlinx.serialization.json.Json { + ignoreUnknownKeys = true + isLenient = true + encodeDefaults = false + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubApiServiceImpl.kt b/app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubApiServiceImpl.kt new file mode 100644 index 00000000..cf407347 --- /dev/null +++ b/app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubApiServiceImpl.kt @@ -0,0 +1,37 @@ +package ua.polodarb.gmsflags.data.remote.github + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.plugins.ClientRequestException +import io.ktor.client.plugins.RedirectResponseException +import io.ktor.client.plugins.ServerResponseException +import io.ktor.client.request.get +import io.ktor.client.request.url +import io.ktor.client.statement.bodyAsText +import ua.polodarb.gmsflags.BuildConfig +import java.net.UnknownHostException + +class GithubApiServiceImpl( + private val client: HttpClient +): GithubApiService { + override suspend fun getLatestRelease(): GithubUpdateModel { + return try { + client.get { url(GithubApiRoutes.RELEASE_UPDATES) }.body() + } catch (ex: RedirectResponseException) { + // 3xx - responses + println("Error: ${ex.response.status.description}") + GithubUpdateModel(tagName = BuildConfig.VERSION_NAME) + } catch (ex: ClientRequestException) { + // 4xx - responses + println("Error: ${ex.response.status.description}") + GithubUpdateModel(tagName = BuildConfig.VERSION_NAME) + } catch (ex: ServerResponseException) { + // 5xx - response + println("Error: ${ex.response.status.description}") + GithubUpdateModel(tagName = BuildConfig.VERSION_NAME) + } catch (ex: UnknownHostException) { + println("Offline mode") + GithubUpdateModel(tagName = BuildConfig.VERSION_NAME) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubUpdateModel.kt b/app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubUpdateModel.kt new file mode 100644 index 00000000..31a1973b --- /dev/null +++ b/app/src/main/java/ua/polodarb/gmsflags/data/remote/github/GithubUpdateModel.kt @@ -0,0 +1,9 @@ +package ua.polodarb.gmsflags.data.remote.github + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GithubUpdateModel( + @SerialName("tag_name") val tagName: String +) \ No newline at end of file diff --git a/app/src/main/java/ua/polodarb/gmsflags/data/repo/AppsListRepository.kt b/app/src/main/java/ua/polodarb/gmsflags/data/repo/AppsListRepository.kt index 360e3807..7981e9cd 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/data/repo/AppsListRepository.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/data/repo/AppsListRepository.kt @@ -18,6 +18,8 @@ class AppsListRepository( val gmsPackages = (context as GMSApplication).getRootDatabase().googlePackages val pm = context.packageManager + val finskyPackages = gmsPackages.filter { it.contains("finsky") } + val appInfoList = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { pm.getInstalledApplications(PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA.toLong())) } else { @@ -25,8 +27,11 @@ class AppsListRepository( } val filteredAppInfoList = appInfoList.asSequence() - .filter { gmsPackages.contains(it.packageName) && it.packageName.contains("com.google") } -// .filterNot { it.packageName == "com.google.android.gm" || it.packageName.contains("gms") } + .filter { + gmsPackages.contains(it.packageName) + && it.packageName.contains("com.google") + || it.packageName.contains("com.android.vending") + } .map { AppInfo.create(pm, it) } .sortedBy { it.appName } .toList() @@ -44,7 +49,16 @@ class AppsListRepository( } else { false } - } + }.toMutableList() + + if (pkgName == "com.android.vending") list.addAll(0, + listOf( + "com.google.android.finsky.instantapps", + "com.google.android.finsky.regular", + "com.google.android.finsky.stable" + ) + ) + emit(DialogUiStates.Success(list)) } diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/CrashActivity.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/CrashActivity.kt index b9b14033..290edefb 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/CrashActivity.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/CrashActivity.kt @@ -57,7 +57,7 @@ class CrashActivity : ComponentActivity() { putExtra(Intent.EXTRA_TEXT, intent.getStringExtra(STACK_TRACE_KEY)) } startActivity(intent) - } catch (e: ActivityNotFoundException) { + } catch (_: ActivityNotFoundException) { Toast.makeText(this, getString(R.string.crash_report_failed), Toast.LENGTH_SHORT).show() } }, @@ -156,7 +156,7 @@ class ExceptionHandler( } context.startActivity(intent) exitProcess(0) - } catch (e: Exception) { + } catch (_: Exception) { handler.uncaughtException(thread, throwable) } } diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/components/UpdateDialog.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/components/UpdateDialog.kt new file mode 100644 index 00000000..f3633ffd --- /dev/null +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/components/UpdateDialog.kt @@ -0,0 +1,61 @@ +package ua.polodarb.gmsflags.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import ua.polodarb.gmsflags.R + +@Composable +fun UpdateDialog( + showDialog: Boolean, + appVersion: String, + onDismiss: () -> Unit, + onUpdateClick: () -> Unit, +) { + + val configuration = LocalConfiguration.current + val screenWidth = configuration.screenWidthDp.dp + + if (showDialog) { + AlertDialog( + onDismissRequest = onDismiss, + confirmButton = { + Row { + OutlinedButton(onClick = onDismiss) { + Text(text = stringResource(R.string.update_dialog_close)) + } + Spacer(modifier = Modifier.weight(1f)) + Button(onClick = onUpdateClick) { + Text(text = stringResource(R.string.update_dialog_confirm)) + } + } + }, + icon = { + Icon( + painter = painterResource(id = R.drawable.ic_update_app), + contentDescription = null + ) + }, + text = { + Text(text = stringResource(R.string.update_dialog_info)) + }, + title = { + Text(text = stringResource(R.string.update_dialog_title) + appVersion + "!", fontWeight = FontWeight.Medium) + }, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/components/dropDown/FlagChangeDropDown.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/components/dropDown/FlagChangeDropDown.kt index 427d99f4..c3b024b2 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/components/dropDown/FlagChangeDropDown.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/components/dropDown/FlagChangeDropDown.kt @@ -13,6 +13,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import ua.polodarb.gmsflags.R @@ -37,7 +38,7 @@ fun FlagChangeDropDown( ) { DropdownMenuItem( - text = { Text("Add flag") }, + text = { Text(text = stringResource(id = R.string.component_add_flag)) }, onClick = onAddFlag, leadingIcon = { Icon( @@ -48,7 +49,7 @@ fun FlagChangeDropDown( enabled = true ) DropdownMenuItem( - text = { Text("Reset all overridden flags") }, + text = { Text(text = stringResource(id = R.string.component_reset_flags)) }, onClick = onDeleteOverriddenFlags, leadingIcon = { Icon( @@ -61,4 +62,4 @@ fun FlagChangeDropDown( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/ErrorLoadScreen.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/ErrorLoadScreen.kt index dc8bdd45..4df9e847 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/ErrorLoadScreen.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/ErrorLoadScreen.kt @@ -6,6 +6,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import ua.polodarb.gmsflags.R @Composable fun ErrorLoadScreen() { @@ -13,6 +15,6 @@ fun ErrorLoadScreen() { modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { - Text(text = "Data loading error") + Text(text = stringResource(id = R.string.component_error_load)) } -} \ No newline at end of file +} diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/NoFlagsOrPackages.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/NoFlagsOrPackages.kt index bf0e279e..d30325b6 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/NoFlagsOrPackages.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/NoFlagsOrPackages.kt @@ -9,17 +9,18 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign +import ua.polodarb.gmsflags.R @Composable fun NoFlagsOrPackages( type: NoFlagsOrPackages = NoFlagsOrPackages.FLAGS ) { - - val text = when (type) { - NoFlagsOrPackages.FLAGS -> "¯\\_(ツ)_/¯\n\nFlags not found" - NoFlagsOrPackages.APPS -> "¯\\_(ツ)_/¯\n\nApps not found" + val text = "¯\\_(ツ)_/¯\n\n" + when (type) { + NoFlagsOrPackages.FLAGS -> stringResource(id = R.string.component_no_flags) + NoFlagsOrPackages.APPS -> stringResource(id = R.string.component_no_apps) } Box( @@ -41,4 +42,4 @@ fun NoFlagsOrPackages( enum class NoFlagsOrPackages { APPS, FLAGS -} \ No newline at end of file +} diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/NotImplementedScreen.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/NotImplementedScreen.kt index 66b7ec73..c6a21214 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/NotImplementedScreen.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/components/inserts/NotImplementedScreen.kt @@ -6,7 +6,9 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.sp +import ua.polodarb.gmsflags.R @Composable fun NotImplementedScreen() { @@ -14,6 +16,9 @@ fun NotImplementedScreen() { modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { - Text(text = "Not implemented", fontSize = 22.sp) + Text( + text = stringResource(id = R.string.component_not_implemented), + fontSize = 22.sp + ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/components/tabs/GFlagsTab.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/components/tabs/GFlagsTab.kt index 9773fdae..245e578e 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/components/tabs/GFlagsTab.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/components/tabs/GFlagsTab.kt @@ -34,7 +34,11 @@ fun GFlagsTab( text = tabTitle, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = if (tabState == index) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant + color = if (tabState == index) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + } ) }, modifier = Modifier @@ -42,4 +46,4 @@ fun GFlagsTab( .height(36.dp) .clip(MaterialTheme.shapes.extraLarge) ) -} \ No newline at end of file +} diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/navigation/AppNavigation.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/navigation/AppNavigation.kt index e0b1fe9f..0ed12048 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/navigation/AppNavigation.kt @@ -1,6 +1,8 @@ package ua.polodarb.gmsflags.ui.navigation import android.net.Uri +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.NavController @@ -13,39 +15,65 @@ import ua.polodarb.gmsflags.ui.screens.historyScreen.HistoryScreen import ua.polodarb.gmsflags.ui.screens.savedScreen.SavedScreen import ua.polodarb.gmsflags.ui.screens.suggestionsScreen.SuggestionsScreen -sealed class NavBarItem(var title: String, var icon: Int, var screenRoute: String) { +sealed class NavBarItem( + @StringRes val title: Int, + @DrawableRes val iconActive: Int, + @DrawableRes val iconInactive: Int?, + val screenRoute: String +) { + data object Suggestions : NavBarItem( + title = R.string.nav_bar_suggestions, + iconActive = R.drawable.ic_navbar_suggestions_active, + iconInactive = R.drawable.ic_navbar_suggestions_inactive, + screenRoute = "suggestions" + ) - object Suggestions : - NavBarItem("Suggestions", R.drawable.ic_navbar_suggestions_active, "suggestions") + data object Apps : NavBarItem( + title = R.string.nav_bar_apps, + iconActive = R.drawable.ic_navbar_apps, + iconInactive = null, + screenRoute = "apps" + ) - object Apps : NavBarItem("Apps", R.drawable.ic_navbar_apps, "apps") - object Saved : NavBarItem("Saved", R.drawable.ic_save_inactive, "saved") - object History : NavBarItem("History", R.drawable.ic_navbar_history, "history") + data object Saved : NavBarItem( + title = R.string.nav_bar_saved, + iconActive = R.drawable.ic_save_active, + iconInactive = R.drawable.ic_save_inactive, + screenRoute = "saved" + ) + data object History : NavBarItem( + title = R.string.nav_bar_history, + iconActive = R.drawable.ic_navbar_history, + iconInactive = null, + screenRoute = "history" + ) } +val navBarItems = listOf(NavBarItem.Suggestions, NavBarItem.Apps, NavBarItem.Saved, NavBarItem.History) + internal sealed class ScreensDestination(var screenRoute: String) { fun createStringRoute(rootRoute: String) = "${rootRoute}/$screenRoute" - object Root : ScreensDestination("root") - object FlagChange : ScreensDestination("{flagChange}") { + data object Root : ScreensDestination("root") + data object FlagChange : ScreensDestination("{flagChange}") { fun createRoute(flagChange: String): String { return "packages/$flagChange" } } - object Settings : ScreensDestination("settings") - object Packages : ScreensDestination("packages") - object Welcome : ScreensDestination("welcome") - object RootRequest : ScreensDestination("rootRequest") + data object Settings : ScreensDestination("settings") + data object Packages : ScreensDestination("packages") + data object Welcome : ScreensDestination("welcome") + data object RootRequest : ScreensDestination("rootRequest") } @Composable internal fun BottomBarNavigation( // Navigation realization for BottomBar + modifier: Modifier = Modifier, parentNavController: NavController, - navController: NavHostController, - modifier: Modifier = Modifier + navController: NavHostController ) { NavHost( navController = navController, @@ -98,4 +126,4 @@ internal fun BottomBarNavigation( // Navigation realization for BottomBar ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/navigation/NavigationBarUI.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/navigation/NavigationBarUI.kt index 3659a573..6713be44 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/navigation/NavigationBarUI.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/navigation/NavigationBarUI.kt @@ -6,116 +6,44 @@ import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.navigation.NavHostController -import androidx.navigation.compose.currentBackStackEntryAsState -import ua.polodarb.gmsflags.R @Composable -fun BottomBarUI( // UI realization for BottomBar - modifier: Modifier = Modifier, +fun BottomBarUI( navController: NavHostController ) { - NavigationBar { - - val navBackStackEntry by navController.currentBackStackEntryAsState() - val currentSelectedItem by navController.currentScreenAsState() + val currentSelectedItem by navController.currentScreenAsState() - NavigationBarItem( - icon = { - Icon( - painter = painterResource( - if (currentSelectedItem == NavBarItem.Suggestions) - R.drawable.ic_navbar_suggestions_active - else - R.drawable.ic_navbar_suggestions_inactive - ), - contentDescription = "Suggestions" - ) - }, - label = { Text(text = NavBarItem.Suggestions.title) }, - selected = currentSelectedItem == NavBarItem.Suggestions, - onClick = { - navController.navigate(NavBarItem.Suggestions.screenRoute) { - navController.graph.startDestinationRoute?.let { route -> - popUpTo(route) { - saveState = true - } - } - launchSingleTop = true - restoreState = true - } - } - ) - NavigationBarItem( - icon = { - Icon( - painter = painterResource(NavBarItem.Apps.icon), - contentDescription = "Apps" - ) - }, - label = { Text(text = NavBarItem.Apps.title) }, - selected = currentSelectedItem == NavBarItem.Apps, - onClick = { - navController.navigate(NavBarItem.Apps.screenRoute) { - navController.graph.startDestinationRoute?.let { route -> - popUpTo(route) { - saveState = true + NavigationBar { + navBarItems.forEach { item -> + NavigationBarItem( + icon = { + Icon( + painter = painterResource( + if (currentSelectedItem == item || item.iconInactive == null) + item.iconActive + else + item.iconInactive + ), + contentDescription = stringResource(id = item.title) + ) + }, + label = { Text(text = stringResource(id = item.title)) }, + selected = currentSelectedItem == item, + onClick = { + navController.navigate(item.screenRoute) { + navController.graph.startDestinationRoute?.let { route -> + popUpTo(route) { + saveState = true + } } + launchSingleTop = true + restoreState = true } - launchSingleTop = true - restoreState = true } - }, - ) - NavigationBarItem( - icon = { - Icon( - painter = painterResource( - if (currentSelectedItem == NavBarItem.Saved) - R.drawable.ic_save_active - else - R.drawable.ic_save_inactive - ), - contentDescription = "Saved" - ) - }, - label = { Text(text = NavBarItem.Saved.title) }, - selected = currentSelectedItem == NavBarItem.Saved, - onClick = { - navController.navigate(NavBarItem.Saved.screenRoute) { - navController.graph.startDestinationRoute?.let { route -> - popUpTo(route) { - saveState = true - } - } - launchSingleTop = true - restoreState = true - } - } - ) - NavigationBarItem( - icon = { - Icon( - painter = painterResource(NavBarItem.History.icon), - contentDescription = "History" - ) - }, - label = { Text(text = NavBarItem.History.title) }, - selected = currentSelectedItem == NavBarItem.History, - onClick = { - navController.navigate(NavBarItem.History.screenRoute) { - navController.graph.startDestinationRoute?.let { route -> - popUpTo(route) { - saveState = true - } - } - launchSingleTop = true - restoreState = true - } - } - ) + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/navigation/RootAppNavigation.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/navigation/RootAppNavigation.kt index 1eb44196..38665264 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/navigation/RootAppNavigation.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/navigation/RootAppNavigation.kt @@ -4,13 +4,20 @@ import android.content.Context import android.net.Uri import android.widget.Toast import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.platform.LocalUriHandler import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.NavHost @@ -23,10 +30,14 @@ import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.koin.compose.koinInject +import ua.polodarb.gmsflags.BuildConfig import ua.polodarb.gmsflags.GMSApplication +import ua.polodarb.gmsflags.core.Extensions.toFormattedInt +import ua.polodarb.gmsflags.data.remote.github.GithubApiService import ua.polodarb.gmsflags.ui.MainActivity import ua.polodarb.gmsflags.ui.animations.enterAnim import ua.polodarb.gmsflags.ui.animations.exitAnim +import ua.polodarb.gmsflags.ui.components.UpdateDialog import ua.polodarb.gmsflags.ui.screens.RootScreen import ua.polodarb.gmsflags.ui.screens.firstStartScreens.RootRequestScreen import ua.polodarb.gmsflags.ui.screens.firstStartScreens.WelcomeScreen @@ -51,6 +62,41 @@ internal fun RootAppNavigation( mutableStateOf(false) } + var showDialog by rememberSaveable { + mutableStateOf(false) + } + + // Github latest release request + val apiService by lazy { GithubApiService.create() } + val uriHandler = LocalUriHandler.current + + val products = produceState( + initialValue = BuildConfig.VERSION_NAME, + producer = { + value = apiService.getLatestRelease().tagName + } + ) + + LaunchedEffect(Unit) { + if (BuildConfig.VERSION_NAME.toFormattedInt() < products.value.toFormattedInt()) { + showDialog = true + } + } + + if (showDialog) { + UpdateDialog( + showDialog = showDialog, + appVersion = products.value, + onDismiss = { + showDialog = false + }, + onUpdateClick = { + uriHandler.openUri("https://github.com/polodarb/GMS-Flags/releases/latest") + showDialog = false + } + ) + } + NavHost( navController = navController, startDestination = if (isFirstStart) ScreensDestination.Welcome.screenRoute else ScreensDestination.Root.screenRoute, @@ -72,8 +118,7 @@ internal fun RootAppNavigation( onStart = { navController.navigate(ScreensDestination.RootRequest.screenRoute) }, - onPolicyClick = {}, // todo - onTermsClick = {} // todo + openLink = { } ) } composable( diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/appsScreen/AppsScreen.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/appsScreen/AppsScreen.kt index 3ece5bd8..1cad5152 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/appsScreen/AppsScreen.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/appsScreen/AppsScreen.kt @@ -2,7 +2,6 @@ package ua.polodarb.gmsflags.ui.screens.appsScreen import android.graphics.drawable.Drawable import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -43,7 +42,6 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight @@ -61,7 +59,7 @@ import ua.polodarb.gmsflags.ui.components.searchBar.GFlagsSearchBar import ua.polodarb.gmsflags.ui.screens.appsScreen.dialog.AppsScreenDialog import ua.polodarb.gmsflags.ui.screens.appsScreen.dialog.DialogUiStates -@OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun AppsScreen( onSettingsClick: () -> Unit, @@ -77,7 +75,6 @@ fun AppsScreen( val showDialog = rememberSaveable { mutableStateOf(false) } val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - val context = LocalContext.current val haptic = LocalHapticFeedback.current // Keyboard @@ -122,7 +119,7 @@ fun AppsScreen( ) { Icon( imageVector = Icons.Filled.Search, - contentDescription = "Localized description" + contentDescription = null ) } IconButton(onClick = { @@ -131,7 +128,7 @@ fun AppsScreen( }) { Icon( painterResource(id = R.drawable.ic_packages), - contentDescription = "Localized description" + contentDescription = null ) } IconButton(onClick = { @@ -198,13 +195,16 @@ fun AppsScreen( is DialogUiStates.Success -> { val dialogPackagesList = - (dialogDataState.value as DialogUiStates.Success).data + (dialogDataState.value as DialogUiStates.Success).data.toMutableList() AppsScreenDialog( showDialog.value, - onDismiss = { showDialog.value = false }, + onDismiss = { + showDialog.value = false + dialogPackagesList.clear() + }, pkgName = dialogPackageText.value, - list = dialogPackagesList, + list = dialogPackagesList.toList(), onPackageClick = { onPackageItemClick(it) showDialog.value = false @@ -262,4 +262,4 @@ fun AppListItem( ) } }) -} \ No newline at end of file +} diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/appsScreen/AppsScreenViewModel.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/appsScreen/AppsScreenViewModel.kt index e44fe392..cc9b3a65 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/appsScreen/AppsScreenViewModel.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/appsScreen/AppsScreenViewModel.kt @@ -27,8 +27,7 @@ class AppsScreenViewModel( MutableStateFlow(DialogUiStates.Loading) val dialogDataState: StateFlow = _dialogDataState.asStateFlow() - private val _dialogPackage = - MutableStateFlow("") + private val _dialogPackage = MutableStateFlow("") val dialogPackage: StateFlow = _dialogPackage.asStateFlow() @@ -99,5 +98,4 @@ class AppsScreenViewModel( ) } } - -} \ No newline at end of file +} diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/appsScreen/dialog/AppsScreenDialog.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/appsScreen/dialog/AppsScreenDialog.kt index 78eeac49..85b5f3bd 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/appsScreen/dialog/AppsScreenDialog.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/appsScreen/dialog/AppsScreenDialog.kt @@ -1,5 +1,6 @@ package ua.polodarb.gmsflags.ui.screens.appsScreen.dialog +import android.util.Log import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -162,7 +163,8 @@ fun DialogListWithSeparator( it == pkgName || it == "$pkgName#$pkgName" || it.contains("device#$pkgName") || - it.contains("user#$pkgName") + it.contains("user#$pkgName") || + it.contains("finsky") } val filteredSecondaryList = packagesList @@ -170,7 +172,8 @@ fun DialogListWithSeparator( it == pkgName || it == "$pkgName#$pkgName" || it.contains("device#$pkgName") || - it.contains("user#$pkgName") + it.contains("user#$pkgName") || + it.contains("finsky") } LazyColumn( @@ -299,5 +302,6 @@ fun checkAppsListSeparation(allPackages: List, pkgName: String): Boolean return allPackages.contains(pkgName) || allPackages.contains("device#$pkgName") || allPackages.contains("user#$pkgName") || - allPackages.contains("$pkgName#$pkgName") + allPackages.contains("$pkgName#$pkgName") || + allPackages.any { it.contains("finsky") } } \ No newline at end of file diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/firstStartScreens/RootRequestScreen.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/firstStartScreens/RootRequestScreen.kt index 1935d8d9..839ee0aa 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/firstStartScreens/RootRequestScreen.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/firstStartScreens/RootRequestScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -37,27 +38,24 @@ fun RootRequestScreen( Column { Image( painter = painterResource(id = R.drawable.root_image), - contentDescription = "", + contentDescription = null, modifier = Modifier .align(Alignment.CenterHorizontally) .padding(top = 44.dp) ) Text( - text = "Root required", + text = stringResource(id = R.string.root_title), fontSize = 46.sp, fontWeight = FontWeight.W600, modifier = Modifier.padding(vertical = 16.dp, horizontal = 24.dp) ) Text( - text = """ - Unfortunately, the application - requires superuser rights to function. - """.trimIndent(), + text = stringResource(id = R.string.root_msg), fontSize = 20.sp, modifier = Modifier.padding(horizontal = 24.dp) ) Text( - text = "To request click the \"Request root\"", + text = stringResource(id = R.string.root_advice), fontSize = 20.sp, modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp) ) @@ -75,7 +73,7 @@ fun RootRequestScreen( .height(48.dp) ) { Text( - text = "Exit", + text = stringResource(id = R.string.root_exit), fontWeight = FontWeight.Medium, fontSize = 15.sp ) @@ -97,7 +95,7 @@ fun RootRequestScreen( ) } else { Text( - text = "Request root", + text = stringResource(id = R.string.root_request), fontWeight = FontWeight.Medium, fontSize = 15.sp ) @@ -106,4 +104,4 @@ fun RootRequestScreen( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/firstStartScreens/WelcomeScreen.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/firstStartScreens/WelcomeScreen.kt index 8d3dc867..8268645d 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/firstStartScreens/WelcomeScreen.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/firstStartScreens/WelcomeScreen.kt @@ -3,19 +3,19 @@ package ua.polodarb.gmsflags.ui.screens.firstStartScreens import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString @@ -30,126 +30,131 @@ import ua.polodarb.gmsflags.R @Composable fun WelcomeScreen( onStart: () -> Unit, - onPolicyClick: (String) -> Unit, - onTermsClick: (String) -> Unit + openLink: (String) -> Unit ) { - Surface( - modifier = Modifier.fillMaxSize() - ) { - Column { - Image( - painter = painterResource(id = R.drawable.welcome_image), - contentDescription = null, - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(top = 32.dp) - ) - Text( - text = "Welcome!", - fontSize = 46.sp, - fontWeight = FontWeight.W600, - modifier = Modifier.padding(vertical = 16.dp, horizontal = 24.dp) - ) - Text( - text = """ - Explore Google Apps for hidden - features, redesigns, abilities to turn - off regional restrictions, and more... - """.trimIndent(), - fontSize = 20.sp, - modifier = Modifier.padding(horizontal = 24.dp) - ) + val context = LocalContext.current + + Column { + Image( + painter = painterResource(id = R.drawable.welcome_image), + contentDescription = null, + modifier = Modifier + .align(Alignment.CenterHorizontally) + .padding(top = 32.dp) + ) + Text( + text = stringResource(id = R.string.welcome_title), + fontSize = 46.sp, + fontWeight = FontWeight.W600, + modifier = Modifier.padding(vertical = 16.dp, horizontal = 24.dp) + ) + Text( + text = stringResource(id = R.string.welcome_msg), + fontSize = 20.sp, + modifier = Modifier.padding(horizontal = 24.dp) + ) + Text( + text = stringResource(id = R.string.welcome_advice), + fontSize = 20.sp, + modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp) + ) + Spacer(modifier = Modifier.weight(1f)) + Button( + onClick = onStart, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .height(48.dp) + ) { Text( - text = "Become part of the community!", - fontSize = 20.sp, - modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp) + text = stringResource(id = R.string.welcome_start), + fontWeight = FontWeight.Medium, + fontSize = 15.sp ) - Spacer(modifier = Modifier.weight(1f)) - Button( - onClick = onStart, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp) - .height(48.dp) - ) { - Text( - text = "Start", - fontWeight = FontWeight.Medium, - fontSize = 15.sp + } + Disclaimer( + modifier = Modifier + .fillMaxWidth() + .padding(start = 24.dp, end = 24.dp, bottom = 36.dp, top = 24.dp), + disclaimer = context.getString(R.string.welcome_disclaimer), + links = listOf( + Pair( + context.getString(R.string.welcome_terms_chunk), + context.getString(R.string.welcome_terms_url) + ), + Pair( + context.getString(R.string.welcome_policy_chunk), + context.getString(R.string.welcome_policy_url) ) - } - val annotatedString = buildAnnotatedString { - withStyle( - style = SpanStyle( - fontWeight = FontWeight.Normal, - fontSize = 15.sp, - color = MaterialTheme.colorScheme.onBackground - ) - ) { - append("By continuing, you agree to our ") - } + ), + openLink = openLink + ) + } +} - pushStringAnnotation(tag = "policy", annotation = "https://google.com/policy") - withStyle( - style = SpanStyle( +@Composable +private fun Disclaimer( + modifier: Modifier = Modifier, + disclaimer: String, + links: List>, + openLink: (String) -> Unit +) { + val chunks = mutableListOf>() + var start = 0 + + links.forEach { link -> + val end = disclaimer.indexOf(link.first) + require (end != -1) { "Links mismatch!" } + chunks.add(Pair(disclaimer.substring(start, end), null)) + chunks.add(link) + start = end + link.first.length + } + if (start < disclaimer.length) { + chunks.add(Pair(disclaimer.substring(start, disclaimer.length), null)) + } + + val annotatedString = buildAnnotatedString { + chunks.forEach { chunk -> + if (chunk.second != null) + pushStringAnnotation(tag = chunk.first, annotation = chunk.second!!) + withStyle( + style = if (chunk.second != null) { + SpanStyle( fontWeight = FontWeight.SemiBold, fontSize = 15.sp, textDecoration = TextDecoration.Underline, color = MaterialTheme.colorScheme.primary ) - ) { - append("Privacy Policy") - } - pop() - - withStyle( - style = SpanStyle( + } else { + SpanStyle( fontWeight = FontWeight.Normal, fontSize = 15.sp, color = MaterialTheme.colorScheme.onBackground ) - ) { - append(" and ") } - - pushStringAnnotation(tag = "terms", annotation = "https://google.com/terms") - withStyle( - style = SpanStyle( - fontWeight = FontWeight.SemiBold, - fontSize = 15.sp, - textDecoration = TextDecoration.Underline, - color = MaterialTheme.colorScheme.primary - ) - ) { - append("Terms of Service") - } - pop() + ) { + append(chunk.first) } + if (chunk.second != null) pop() + } + } - ClickableText( - text = annotatedString, style = TextStyle( - textAlign = TextAlign.Center - ), onClick = { offset -> - annotatedString.getStringAnnotations( - tag = "policy", - start = offset, - end = offset - ).firstOrNull()?.let { - onPolicyClick(it.item) - } - + ClickableText( + text = annotatedString, + style = TextStyle(textAlign = TextAlign.Center), + onClick = { offset -> + chunks.forEach { chunk -> + if (chunk.second != null) { annotatedString.getStringAnnotations( - tag = "terms", + tag = chunk.first, start = offset, - end = offset + end = offset, ).firstOrNull()?.let { - onTermsClick(it.item) + openLink(chunk.second!!) } - }, - modifier = Modifier - .fillMaxWidth() - .padding(start = 24.dp, end = 24.dp, bottom = 36.dp, top = 24.dp) - ) - } - } -} \ No newline at end of file + } + } + }, + modifier = modifier + ) +} diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/flagChangeScreen/FlagChangeScreen.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/flagChangeScreen/FlagChangeScreen.kt index 1d6ab6d7..055062a8 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/flagChangeScreen/FlagChangeScreen.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/flagChangeScreen/FlagChangeScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed @@ -292,7 +293,7 @@ fun FlagChangeScreen( topBarState = topBarState, onClick = { index -> coroutineScope.launch { - pagerState.animateScrollToPage(index) + pagerState.scrollToPage(index) } if (index != 0) filterIconState = false tabFilterState = index == 0 @@ -526,7 +527,7 @@ fun BooleanFlagsScreen( if (listBool.isEmpty()) NoFlagsOrPackages() - Box(modifier = Modifier.fillMaxSize()) { + Box(modifier = Modifier.fillMaxSize().imePadding()) { if (listBool.isNotEmpty()) { LazyColumn { itemsIndexed(listBool.keys.toList()) { index, flagName -> @@ -639,7 +640,7 @@ fun OtherTypesFlagsScreen( if (listInt.isEmpty()) NoFlagsOrPackages() - Box(modifier = Modifier.fillMaxSize()) { + Box(modifier = Modifier.fillMaxSize().imePadding()) { if (listInt.isNotEmpty()) { LazyColumn { itemsIndexed(listInt.toList()) { index, item -> diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/flagChangeScreen/FlagChangeScreenViewModel.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/flagChangeScreen/FlagChangeScreenViewModel.kt index 32bcec26..1daea1b0 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/flagChangeScreen/FlagChangeScreenViewModel.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/flagChangeScreen/FlagChangeScreenViewModel.kt @@ -305,6 +305,10 @@ class FlagChangeScreenViewModel( CoroutineScope(Dispatchers.IO).launch { Shell.cmd("am force-stop $androidPkgName").exec() Shell.cmd("rm -rf /data/data/$androidPkgName/files/phenotype").exec() + if (pkgName == "com.android.vending") { + Shell.cmd("rm -rf /data/data/com.android.vending/files/experiment*").exec() + Shell.cmd("am force-stop com.android.vending").exec() + } repeat(3) { Shell.cmd("am start -a android.intent.action.MAIN -n $androidPkgName &").exec() Shell.cmd("am force-stop $androidPkgName").exec() diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/packagesScreen/PackagesScreen.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/packagesScreen/PackagesScreen.kt index 2c1b1316..d36b2a13 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/packagesScreen/PackagesScreen.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/packagesScreen/PackagesScreen.kt @@ -42,9 +42,9 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import org.koin.androidx.compose.koinViewModel @@ -69,7 +69,6 @@ fun PackagesScreen( } val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - val context = LocalContext.current val haptic = LocalHapticFeedback.current // Keyboard @@ -112,7 +111,7 @@ fun PackagesScreen( LargeTopAppBar( title = { Text( - "Packages", + stringResource(id = R.string.packages_title), maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -131,7 +130,7 @@ fun PackagesScreen( ) { Icon( imageVector = Icons.Filled.Search, - contentDescription = "Localized description" + contentDescription = null ) } }, @@ -139,7 +138,7 @@ fun PackagesScreen( IconButton(onClick = onBackPressed) { Icon( imageVector = Icons.Filled.ArrowBack, - contentDescription = "Localized description" + contentDescription = null ) } }, @@ -156,7 +155,7 @@ fun PackagesScreen( searchQuery = "" haptic.performHapticFeedback(HapticFeedbackType.LongPress) }, - placeHolderText = "Search a package name", + placeHolderText = stringResource(id = R.string.packages_search_advice), keyboardFocus = focusRequester ) } @@ -176,14 +175,8 @@ fun PackagesScreen( onFlagClick = onFlagClick ) } - - is ScreenUiStates.Loading -> { - LoadingProgressBar() - } - - is ScreenUiStates.Error -> { - ErrorLoadScreen() - } + is ScreenUiStates.Loading -> LoadingProgressBar() + is ScreenUiStates.Error -> ErrorLoadScreen() } } } @@ -216,10 +209,10 @@ private fun SuccessListItems( @Composable fun LazyItem( + modifier: Modifier = Modifier, packageName: String, packagesCount: Int, lastItem: Boolean = false, - modifier: Modifier = Modifier ) { var checkedState by rememberSaveable { mutableStateOf(false) @@ -239,12 +232,12 @@ fun LazyItem( @Composable fun LazyItem( + modifier: Modifier = Modifier, packageName: String, packagesCount: Int, checked: Boolean, onCheckedChange: (Boolean) -> Unit, lastItem: Boolean, - modifier: Modifier = Modifier ) { Column { Row( @@ -256,12 +249,12 @@ fun LazyItem( if (checked) { Icon( painterResource(id = R.drawable.ic_save_active), - contentDescription = "Localized description" + contentDescription = null ) } else { Icon( painterResource(id = R.drawable.ic_save_inactive), - contentDescription = "Localized description" + contentDescription = null ) } } @@ -282,4 +275,4 @@ fun LazyItem( } } if (!lastItem) HorizontalDivider(Modifier.padding(horizontal = 16.dp)) -} \ No newline at end of file +} diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestionsScreen/SuggestedFlagsList.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestionsScreen/SuggestedFlagsList.kt index fae78438..96468000 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestionsScreen/SuggestedFlagsList.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestionsScreen/SuggestedFlagsList.kt @@ -1,33 +1,47 @@ package ua.polodarb.gmsflags.ui.screens.suggestionsScreen data class SuggestedFlag( - var flagName: String, - var flagSender: String, - var phenotypeFlagName: String, - var phenotypePackageName: String, - var flagValue: Boolean + val flagName: String, + val flagSender: String, + val phenotypeFlagName: List, + val phenotypePackageName: String, + val flagValue: Boolean ) object SuggestedFlagsList { val suggestedFlagsList = mutableListOf( + SuggestedFlag( + "Proofreading mode in GBoard", + "GApps Flags & Leaks", + listOf("writing_helper", "writing_helper_enable_free_chat", "writing_helper_chip_shown_as_candidate"), + "com.google.android.inputmethod.latin#com.google.android.inputmethod.latin", + false + ), + SuggestedFlag( + "\"Undo\" button in GBoard", + "GApps Flags & Leaks", + listOf("undo_access_point"), + "com.google.android.inputmethod.latin#com.google.android.inputmethod.latin", + false + ), SuggestedFlag( "Enable transparent statusBar in dialer", "Nail Sadykov", - "45372787", + listOf("45372787"), "com.google.android.dialer.directboot", false ), SuggestedFlag( "Enable docs scanner in Drive", "Nail Sadykov", - "MlkitScanningUiFeature__enable_mlkit_scanning_ui", + listOf("MlkitScanningUiFeature__enable_mlkit_scanning_ui"), "com.google.apps.drive.android#com.google.android.apps.docs", false ), SuggestedFlag( "Enable MD3 style in Android Auto", "Nail Sadykov", - "SystemUi__material_you_settings_enabled", + listOf("SystemUi__material_you_settings_enabled"), "com.google.android.projection.gearhead", false ) diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestionsScreen/SuggestionScreenViewModel.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestionsScreen/SuggestionScreenViewModel.kt index 06f1f9bd..4468d22b 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestionsScreen/SuggestionScreenViewModel.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestionsScreen/SuggestionScreenViewModel.kt @@ -31,21 +31,17 @@ class SuggestionScreenViewModel( getAllOverriddenBoolFlags() } - fun updateFlagValue(phenotypeFlagName: String, newValue: Boolean) { + fun updateFlagValue(newValue: Boolean, index: Int) { val currentState = _stateSuggestionsFlags.value if (currentState is SuggestionsScreenUiStates.Success) { val updatedData = currentState.data.toMutableList() - val flagToUpdateIndex = updatedData.indexOfFirst { it.phenotypeFlagName == phenotypeFlagName } - if (flagToUpdateIndex != -1) { - updatedData[flagToUpdateIndex] = updatedData[flagToUpdateIndex].copy(flagValue = newValue) + if (index != -1) { + updatedData[index] = updatedData[index].copy(flagValue = newValue) _stateSuggestionsFlags.value = currentState.copy(data = updatedData) } } } - - - fun initUsers() { usersList.clear() usersList.addAll(repository.getUsers()) @@ -74,7 +70,7 @@ class SuggestionScreenViewModel( fun updateFlagValues(suggestedFlags: List, flagValuesMap: Map): List { val list = suggestedFlags.map { suggestedFlag -> - val newFlagValue = flagValuesMap[suggestedFlag.phenotypeFlagName]?.toIntOrNull() == 1 + val newFlagValue = flagValuesMap[suggestedFlag.phenotypeFlagName[0]]?.toIntOrNull() == 1 SuggestedFlag( suggestedFlag.flagName, suggestedFlag.flagSender, @@ -91,16 +87,12 @@ class SuggestionScreenViewModel( CoroutineScope(Dispatchers.IO).launch { Shell.cmd("am force-stop $androidPkgName").exec() Shell.cmd("rm -rf /data/data/$androidPkgName/files/phenotype").exec() - repeat(3) { - Shell.cmd("am start -a android.intent.action.MAIN -n $androidPkgName &").exec() - Shell.cmd("am force-stop $androidPkgName").exec() - } } } fun overrideFlag( packageName: String, - name: String, + name: List, flagType: Int = 0, intVal: String? = null, boolVal: String? = null, @@ -110,24 +102,12 @@ class SuggestionScreenViewModel( committed: Int = 0 ) { initUsers() - repository.deleteRowByFlagName(packageName, name) - repository.overrideFlag( - packageName = packageName, - user = "", - name = name, - flagType = flagType, - intVal = intVal, - boolVal = boolVal, - floatVal = floatVal, - stringVal = stringVal, - extensionVal = extensionVal, - committed = committed - ) - for (i in usersList) { + name.forEach { + repository.deleteRowByFlagName(packageName, it) repository.overrideFlag( packageName = packageName, - user = i, - name = name, + user = "", + name = it, flagType = flagType, intVal = intVal, boolVal = boolVal, @@ -136,6 +116,20 @@ class SuggestionScreenViewModel( extensionVal = extensionVal, committed = committed ) + for (i in usersList) { + repository.overrideFlag( + packageName = packageName, + user = i, + name = it, + flagType = flagType, + intVal = intVal, + boolVal = boolVal, + floatVal = floatVal, + stringVal = stringVal, + extensionVal = extensionVal, + committed = committed + ) + } } clearPhenotypeCache(packageName) } diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestionsScreen/SuggestionsScreen.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestionsScreen/SuggestionsScreen.kt index 7cc8ead0..c757f190 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestionsScreen/SuggestionsScreen.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/screens/suggestionsScreen/SuggestionsScreen.kt @@ -141,16 +141,16 @@ fun SuggestionsScreen( contentPadding = it, state = listState ) { - itemsIndexed(data.toList()) { index, _ -> + itemsIndexed(data.toList()) { index, item -> SuggestedFlagItem( - flagName = data[index].flagName, - senderName = data[index].flagSender, - flagValue = data[index].flagValue, + flagName = item.flagName, + senderName = item.flagSender, + flagValue = item.flagValue, flagOnCheckedChange = { - viewModel.updateFlagValue(data[index].phenotypeFlagName, it) + viewModel.updateFlagValue(it, index) viewModel.overrideFlag( - packageName = data[index].phenotypePackageName, - name = data[index].phenotypeFlagName, + packageName = item.phenotypePackageName, + name = item.phenotypeFlagName, boolVal = if (it) "1" else "0" ) } diff --git a/app/src/main/java/ua/polodarb/gmsflags/ui/theme/Theme.kt b/app/src/main/java/ua/polodarb/gmsflags/ui/theme/Theme.kt index a6d874a6..a85ff7dc 100644 --- a/app/src/main/java/ua/polodarb/gmsflags/ui/theme/Theme.kt +++ b/app/src/main/java/ua/polodarb/gmsflags/ui/theme/Theme.kt @@ -16,8 +16,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.core.view.WindowCompat - -private val LightColors = lightColorScheme( +private val lightColors = lightColorScheme( primary = md_theme_light_primary, onPrimary = md_theme_light_onPrimary, primaryContainer = md_theme_light_primaryContainer, @@ -49,8 +48,7 @@ private val LightColors = lightColorScheme( scrim = md_theme_light_scrim, ) - -private val DarkColors = darkColorScheme( +private val darkColors = darkColorScheme( primary = md_theme_dark_primary, onPrimary = md_theme_dark_onPrimary, primaryContainer = md_theme_dark_primaryContainer, @@ -83,17 +81,14 @@ private val DarkColors = darkColorScheme( ) @Composable -fun GMSFlagsTheme( - useDarkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable() () -> Unit -) { +fun GMSFlagsTheme(content: @Composable () -> Unit) { val inDarkMode: Boolean = isSystemInDarkTheme() val colors = if (supportsDynamic()) { val context = LocalContext.current if (inDarkMode) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } else { - if (inDarkMode) DarkColors else LightColors + if (inDarkMode) darkColors else lightColors } val view = LocalView.current @@ -101,16 +96,14 @@ fun GMSFlagsTheme( SideEffect { val window = (view.context as Activity).window window.navigationBarColor = Color.Transparent.toArgb() - window.statusBarColor = Color.Transparent.toArgb() WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !inDarkMode } - MaterialTheme( colorScheme = colors, content = content ) } -fun supportsDynamic(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S \ No newline at end of file +fun supportsDynamic(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S diff --git a/app/src/main/res/drawable/ic_navbar_apps.xml b/app/src/main/res/drawable/ic_navbar_apps.xml index 5303ff39..ac66e573 100644 --- a/app/src/main/res/drawable/ic_navbar_apps.xml +++ b/app/src/main/res/drawable/ic_navbar_apps.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_navbar_suggestions_active.xml b/app/src/main/res/drawable/ic_navbar_suggestions_active.xml index 45931367..a503a552 100644 --- a/app/src/main/res/drawable/ic_navbar_suggestions_active.xml +++ b/app/src/main/res/drawable/ic_navbar_suggestions_active.xml @@ -1,6 +1,7 @@ @@ -8,6 +9,6 @@ android:pathData="M0.5,0h24v24h-24z"/> + android:fillColor="@android:color/white"/> diff --git a/app/src/main/res/drawable/ic_navbar_suggestions_inactive.xml b/app/src/main/res/drawable/ic_navbar_suggestions_inactive.xml index 52b3a392..0c06c216 100644 --- a/app/src/main/res/drawable/ic_navbar_suggestions_inactive.xml +++ b/app/src/main/res/drawable/ic_navbar_suggestions_inactive.xml @@ -1,6 +1,7 @@ @@ -8,7 +9,7 @@ android:pathData="M0.5,0h24v24h-24z"/> diff --git a/app/src/main/res/drawable/ic_save_active.xml b/app/src/main/res/drawable/ic_save_active.xml index 0c4469ae..78ef4991 100644 --- a/app/src/main/res/drawable/ic_save_active.xml +++ b/app/src/main/res/drawable/ic_save_active.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_save_inactive.xml b/app/src/main/res/drawable/ic_save_inactive.xml index fefa312a..bd9b1805 100644 --- a/app/src/main/res/drawable/ic_save_inactive.xml +++ b/app/src/main/res/drawable/ic_save_inactive.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_suggestions.xml b/app/src/main/res/drawable/ic_suggestions.xml index 54ff8ee2..7a2f8a64 100644 --- a/app/src/main/res/drawable/ic_suggestions.xml +++ b/app/src/main/res/drawable/ic_suggestions.xml @@ -1,5 +1,10 @@ - - + + diff --git a/app/src/main/res/drawable/ic_update_app.xml b/app/src/main/res/drawable/ic_update_app.xml new file mode 100644 index 00000000..83c923af --- /dev/null +++ b/app/src/main/res/drawable/ic_update_app.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5f69ee0f..e03924b7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,4 +7,36 @@ "GMS Flags has crashed!" Restart app Restart app + Welcome! + Explore Google Apps for hidden features, redesigns, abilities to turn off regional restrictions, and more… + Become part of the community! + Start! + By continuing, you agree to our Terms of Service and Privacy Policy + Terms of Service + https://google.com/terms + Privacy Policy + https://google.com/policy + Root required + Unfortunately, the application requires superuser rights to function. + To request click the "Request root" + Exit + Request root + Packages + Search a package name + + Suggestions + Apps + Saved + History + + Data loading error + Flags not found + Apps not found + Not implemented + Add flag + Reset all overridden flags + "Clicking the \"Update GMS Flags\" button will take you to GitHub with the latest release " + Update app to v + Close + Update GMS Flags \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 7e587584..5c2d1962 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,4 +4,5 @@ plugins { id("com.android.library") version "8.1.0" apply false id("org.jetbrains.kotlin.android") version "1.9.0" apply false id("com.google.devtools.ksp") version "1.9.0-1.0.12" apply false + id("org.jetbrains.kotlin.plugin.serialization") version "1.9.0" } \ No newline at end of file