From 5e2f84d0b5a7e98f79e67f3077d02c7aa2ccfd28 Mon Sep 17 00:00:00 2001 From: Lennoard Silva Date: Mon, 18 Aug 2025 20:05:18 -0300 Subject: [PATCH 01/23] Create codeql.yml --- .github/workflows/codeql.yml | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..f9e953b --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,46 @@ +name: "CodeQL Advanced" + +on: + push: + branches: [ "develop", "master" ] + pull_request: + branches: [ "develop", "master" ] + schedule: + - cron: '16 0 * * 2' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + security-events: write + packages: read + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: java-kotlin + build-mode: manual + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + queries: security-extended,security-and-quality + + - if: matrix.build-mode == 'manual' + shell: bash + run: | + ./gradlew assembleDebug testDebug + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" From 1ccb8bb25a70380535e0970c31e0af9fe511807a Mon Sep 17 00:00:00 2001 From: Lennoard Date: Sun, 10 Aug 2025 23:24:52 -0300 Subject: [PATCH 02/23] refactor: updated build scripts + version catalog --- app/build.gradle.kts | 55 ++++++++----- .../ui/params/edit/EditKernelParamActivity.kt | 5 -- build.gradle.kts | 38 ++------- buildSrc/src/main/kotlin/AndroidX.kt | 1 - buildSrc/src/main/kotlin/AppConfig.kt | 8 +- common/design/build.gradle.kts | 34 ++++---- common/utils/build.gradle.kts | 13 +-- data/build.gradle.kts | 39 ++++----- .../sysctlgui/data/utils/RootUtils.kt | 9 +-- domain/build.gradle.kts | 34 ++++++-- gradle/libs.versions.toml | 81 +++++++++++++++++++ gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 29 ++++++- 13 files changed, 232 insertions(+), 116 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4aa5fe0..b202615 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,11 +1,12 @@ -import org.jetbrains.kotlin.config.KotlinCompilerVersion import java.util.Properties plugins { - id("com.android.application") - kotlin("android") + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.ksp) + alias(libs.plugins.jetbrains.kotlin.serialization) kotlin("kapt") - id("com.google.devtools.ksp") id("kotlin-parcelize") } @@ -93,28 +94,38 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlin { - jvmToolchain(17) - } - - composeOptions { - kotlinCompilerExtensionVersion = Compose.kotlinCompilerExtensionVersion + kotlinOptions { + jvmTarget = "17" } } dependencies { - implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) - implementation(kotlin("stdlib-jdk8", KotlinCompilerVersion.VERSION)) + implementation(project(":common:design")) + implementation(project(":common:utils")) + implementation(project(":domain")) + implementation(project(":data")) + + implementation(libs.kotlin.stdlib) + implementation(libs.kotlinx.coroutines.android) + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.material) + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.window) + + // Lifecycle + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.runtime.compose) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.lifecycle.viewmodel.navigation3) + implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.lifecycle.viewmodel.savedstate) + //ksp(libs.androidx.lifecycle.compiler) - implementation(project(Modules.domain)) - implementation(project(Modules.data)) - implementation(project(Modules.utils)) - implementation(project(Modules.design)) - - implementation(AndroidX.activity) implementation(AndroidX.splashScreen) implementation(AndroidX.lifecycleLiveData) - implementation(AndroidX.lifecycleRuntimeCompose) implementation(AndroidX.navigationFragment) implementation(AndroidX.navigationUi) implementation(AndroidX.preference) @@ -125,8 +136,10 @@ dependencies { implementation(Google.gson) - implementation(Dependencies.koinAndroid) - implementation(Dependencies.libSuCore) + implementation(libs.koin) + implementation(libs.koin.compose) + implementation(libs.bundles.libsu) + implementation(Dependencies.libSuIo) implementation(Dependencies.liveEvent) implementation(Dependencies.tapTargetView) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditKernelParamActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditKernelParamActivity.kt index 3719b12..459e4e1 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditKernelParamActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditKernelParamActivity.kt @@ -37,11 +37,6 @@ class EditKernelParamActivity : ComponentActivity() { handleIntent(intent) } - override fun onNewIntent(intent: Intent?) { - super.onNewIntent(intent) - handleIntent(intent ?: return) - } - private fun handleIntent(intent: Intent) { val param = intent.getParcelableExtra(EXTRA_PARAM) as? KernelParam if (param != null) { diff --git a/build.gradle.kts b/build.gradle.kts index d11fcfb..09c0244 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,33 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.google.devtools.ksp") version "1.9.24-1.0.20" apply false -} - -buildscript { - repositories { - google() - mavenCentral() - } - - dependencies { - classpath("com.android.tools.build:gradle:8.5.0") - classpath(BuildPlugins.kotlin) - } -} - -allprojects { - repositories { - google() - mavenCentral() - maven { - url = uri("https://maven.google.com") - } - maven { - url = uri("https://jitpack.io") - } - } -} - -tasks.register("clean", Delete::class) { - delete(rootProject.buildDir) -} + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.jetbrains.kotlin.jvm) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.ksp) apply false +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/AndroidX.kt b/buildSrc/src/main/kotlin/AndroidX.kt index 6f22260..52d512d 100644 --- a/buildSrc/src/main/kotlin/AndroidX.kt +++ b/buildSrc/src/main/kotlin/AndroidX.kt @@ -8,7 +8,6 @@ object AndroidX { private const val lifecycleVersion = "2.6.1" const val lifecycleLiveData = "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" const val lifecycleViewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" - const val lifecycleRuntimeCompose = "androidx.lifecycle:lifecycle-runtime-compose:$lifecycleVersion" const val preference = "androidx.preference:preference-ktx:1.2.0" const val swipeRefreshLayout = "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" diff --git a/buildSrc/src/main/kotlin/AppConfig.kt b/buildSrc/src/main/kotlin/AppConfig.kt index a2448da..3e47b91 100644 --- a/buildSrc/src/main/kotlin/AppConfig.kt +++ b/buildSrc/src/main/kotlin/AppConfig.kt @@ -1,10 +1,10 @@ object AppConfig { - val devCycle = false + val devCycle = true const val appId = "com.androidvip.sysctlgui" - const val compileSdkVersion = 34 - const val minSdkVersion = 21 - const val targetSdkVersion = 34 + const val compileSdkVersion = 36 + const val minSdkVersion = 24 + const val targetSdkVersion = 36 const val testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" const val proguardConsumerRules = "consumer-rules.pro" diff --git a/common/design/build.gradle.kts b/common/design/build.gradle.kts index e2da347..d9ddb38 100644 --- a/common/design/build.gradle.kts +++ b/common/design/build.gradle.kts @@ -1,6 +1,7 @@ plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) } android { @@ -9,7 +10,6 @@ android { defaultConfig { minSdk = AppConfig.minSdkVersion - targetSdk = AppConfig.targetSdkVersion testInstrumentationRunner = AppConfig.testInstrumentationRunner consumerProguardFiles(AppConfig.proguardConsumerRules) @@ -38,27 +38,27 @@ android { kotlinOptions { jvmTarget = "17" } - - composeOptions { - kotlinCompilerExtensionVersion = Compose.kotlinCompilerExtensionVersion - } } dependencies { - val composeBom = platform(Compose.BoM) - api(composeBom) - androidTestImplementation(composeBom) + implementation(libs.androidx.core.ktx) + + api(platform(libs.androidx.compose.bom)) + api(libs.androidx.ui) + api(libs.androidx.ui.graphics) + api(libs.androidx.ui.tooling.preview) + api(libs.androidx.material3) + api(libs.androidx.material.icons.core) + api(libs.androidx.window) - api(AndroidX.activity) - api(AndroidX.appCompat) api(AndroidX.constraintLayout) - api(AndroidX.core) api(AndroidX.swipeRefreshLayout) - api(Compose.material3) api(Compose.material) - api(Compose.activity) - api(Compose.uiTooling) - debugApi(Compose.uiTooling) implementation(AndroidX.splashScreen) implementation(Google.material) + + androidTestApi(platform(libs.androidx.compose.bom)) + debugApi(libs.androidx.ui.tooling) + debugApi(libs.androidx.ui.test.manifest) } + diff --git a/common/utils/build.gradle.kts b/common/utils/build.gradle.kts index 6f0ac70..532ccf1 100644 --- a/common/utils/build.gradle.kts +++ b/common/utils/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("com.android.library") - id("org.jetbrains.kotlin.android") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) } android { @@ -9,7 +9,6 @@ android { defaultConfig { minSdk = AppConfig.minSdkVersion - targetSdk = AppConfig.targetSdkVersion testInstrumentationRunner = AppConfig.testInstrumentationRunner consumerProguardFiles(AppConfig.proguardConsumerRules) @@ -36,6 +35,10 @@ android { } dependencies { - implementation(AndroidX.lifecycleViewModel) - api(Dependencies.coroutinesCore) + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.lifecycle.viewmodel.savedstate) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) } diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 9e7f726..28f6e5e 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -1,7 +1,8 @@ plugins { - id("com.android.library") - kotlin("android") - id("com.google.devtools.ksp") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.jetbrains.kotlin.serialization) + alias(libs.plugins.ksp) } android { @@ -10,7 +11,7 @@ android { defaultConfig { minSdk = AppConfig.minSdkVersion - targetSdk = AppConfig.targetSdkVersion + javaCompileOptions { annotationProcessorOptions { arguments += mapOf( @@ -33,8 +34,8 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlin { - jvmToolchain(17) + kotlinOptions { + jvmTarget = "17" } sourceSets { @@ -43,20 +44,22 @@ android { } dependencies { - implementation(project(Modules.domain)) - implementation(project(Modules.utils)) + implementation(project(":common:utils")) + implementation(project(":domain")) - implementation(AndroidX.preference) - implementation(AndroidX.room) - implementation(AndroidX.roomRuntime) - ksp(AndroidX.roomCompiler) + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.preference) - implementation(Dependencies.libSuCore) - implementation(Google.gson) + // Room + implementation(libs.androidx.room.runtime) + implementation(libs.androidx.room.ktx) + ksp(libs.androidx.room.compiler) - implementation(Dependencies.koinAndroid) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.kotlinx.serialization.json) + implementation(libs.koin) + implementation(libs.bundles.libsu) + implementation(Google.gson) - testImplementation("junit:junit:4.+") - androidTestImplementation("androidx.test.ext:junit:1.1.3") - androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") + testImplementation(libs.junit) } diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/utils/RootUtils.kt b/data/src/main/java/com/androidvip/sysctlgui/data/utils/RootUtils.kt index 54c0620..901304a 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/utils/RootUtils.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/utils/RootUtils.kt @@ -9,10 +9,9 @@ import kotlinx.coroutines.withContext class RootUtils(private val dispatcher: CoroutineDispatcher = Dispatchers.Default) { suspend fun isBusyboxAvailable(): Boolean = withContext(dispatcher) { - val results: List = Shell.sh("which busybox").exec().out - return@withContext if (ShellUtils.isValidOutput(results)) { - results.first().isNotEmpty() - } else false + val results: List = Shell.cmd("which busybox").exec().out + return@withContext ShellUtils.isValidOutput(results) && results.firstOrNull() + ?.isNotEmpty() == true } suspend fun executeWithOutput( @@ -22,7 +21,7 @@ class RootUtils(private val dispatcher: CoroutineDispatcher = Dispatchers.Defaul ): String = withContext(dispatcher) { return@withContext runCatching { buildString { - val outputs = Shell.su(command).exec().out + val outputs = Shell.cmd(command).exec().out if (!ShellUtils.isValidOutput(outputs)) { append(defaultOutput) return@buildString diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 495951c..63160d8 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -1,14 +1,34 @@ plugins { - id("java-library") - id("kotlin") + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) } -java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 +android { + namespace = "${AppConfig.appId}.domain" + compileSdk = AppConfig.compileSdkVersion + + defaultConfig { + minSdk = AppConfig.minSdkVersion + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } } dependencies { - implementation(Dependencies.koinCore) -} + implementation(project(":common:utils")) + implementation(libs.androidx.core.ktx) + + implementation(libs.koin) + testImplementation(libs.junit) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..f41fe46 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,81 @@ +[versions] +agp = "8.12.0" +libsu = "6.0.0" +jsoup = "1.21.1" +kotlin = "2.2.0" +kotlinxCoroutinesAndroid = "1.10.2" +ksp = "2.2.0-2.0.2" +coreKtx = "1.16.0" +junit = "4.13.2" +junitVersion = "1.3.0" +espressoCore = "3.7.0" +ktor = "3.2.3" +lifecycle = "2.9.2" +activityCompose = "1.10.1" +composeBom = "2025.07.00" +appcompat = "1.7.1" +material = "1.8.3" +materialIconsCore = "1.7.8" +navigationCompose = "2.9.3" +preference = "1.2.1" +room = "2.7.2" +nav3Lifecycle = "1.0.0-alpha03" +material3 = "1.5.0-alpha01" +kotlinxSerializationCore = "1.9.0" +koin = "4.1.0" +window = "1.4.0" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-material = { module = "androidx.compose.material:material", version.ref = "material" } +androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "materialIconsCore" } +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } +androidx-preference = { module = "androidx.preference:preference", version.ref = "preference" } +androidx-window = { module = "androidx.window:window", version.ref = "window" } +libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" } +libsu-nio = { module = "com.github.topjohnwu.libsu:nio", version.ref = "libsu" } +jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } +androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" } +androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" } +androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" } +androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "nav3Lifecycle" } +androidx-lifecycle-viewmodel-savedstate = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-savedstate", version.ref = "lifecycle" } +androidx-lifecycle-compiler = { group = "androidx.lifecycle", name = "lifecycle-compiler", version.ref = "lifecycle" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } +androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } +androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" } +kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationCore" } +koin = { module = "io.insert-koin:koin-android", version.ref = "koin" } +koin-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" } +ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" } +ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } +ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } + +[bundles] +ktor-clients = ["ktor-client-core", "ktor-client-android", "ktor-client-logging"] +libsu = ["libsu-core", "libsu-nio"] + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"} +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6372a16..24dffe3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip diff --git a/settings.gradle.kts b/settings.gradle.kts index 1d3e2c5..8228a9a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,32 @@ +@file:Suppress("UnstableApiUsage") + +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { + url = uri("https://jitpack.io") + } + } +} + +rootProject.name = "SysctlGUI" include(":app") include(":data") include(":domain") -include(":common:utils") include(":common:design") +include(":common:utils") From 956751a5d3673ce56dd9745bc722a6cd23e1c29d Mon Sep 17 00:00:00 2001 From: Lennoard Date: Mon, 11 Aug 2025 20:48:13 -0300 Subject: [PATCH 03/23] refactor: [WIP] updated domain layer --- domain/consumer-rules.pro | 0 domain/proguard-rules.pro | 21 +++ .../sysctlgui/domain/StringProvider.kt | 12 ++ .../datasource/LocalDataSourceContract.kt | 10 -- .../datasource/RuntimeDataSourceContract.kt | 9 -- .../sysctlgui/domain/di/DomainModule.kt | 44 +++--- .../sysctlgui/domain/enums/CommitMode.kt | 27 ++++ .../sysctlgui/domain/enums/SettingItemType.kt | 27 ++++ .../domain/exceptions/ApplyValueException.kt | 18 +++ .../domain/exceptions/ExportExceptions.kt | 3 + .../domain/exceptions/ImportExceptions.kt | 8 +- .../sysctlgui/domain/models/AppSetting.kt | 31 ++++ .../domain/models/DomainKernelParam.kt | 69 --------- .../sysctlgui/domain/models/KernelParam.kt | 133 ++++++++++++++++++ .../domain/models/KernelParamContract.kt | 10 -- .../domain/models/ParamDocumentation.kt | 16 +++ .../sysctlgui/domain/repository/AppPrefs.kt | 12 +- .../repository/AppSettingsRepository.kt | 7 + .../repository/DocumentationRepository.kt | 18 +++ .../domain/repository/ParamsRepository.kt | 82 ++++++++--- .../domain/repository/PresetRepository.kt | 44 ++++++ .../domain/repository/UserRepository.kt | 47 +++++++ .../domain/usecase/AddUserParamUseCase.kt | 14 -- .../domain/usecase/AddUserParamsUseCase.kt | 16 ++- .../domain/usecase/ApplyParamUseCase.kt | 76 ++++++++++ .../domain/usecase/ApplyParamsUseCase.kt | 19 --- .../domain/usecase/BackupParamsUseCase.kt | 8 +- .../domain/usecase/ClearUserParamUseCase.kt | 4 +- .../domain/usecase/ExportParamsUseCase.kt | 18 ++- .../domain/usecase/GetAppSettingsUseCase.kt | 20 +++ .../domain/usecase/GetJsonParamsUseCase.kt | 7 - .../usecase/GetParamDocumentationUseCase.kt | 15 ++ .../usecase/GetParamsFromFilesUseCase.kt | 7 +- .../domain/usecase/GetRuntimeParamUseCase.kt | 25 ++++ .../domain/usecase/GetRuntimeParamsUseCase.kt | 16 ++- .../usecase/GetUserParamByNameUseCase.kt | 7 + .../domain/usecase/GetUserParamsUseCase.kt | 7 +- .../domain/usecase/ImportParamsUseCase.kt | 45 ------ .../usecase/IsTaskerInstalledUseCase.kt | 27 ++++ .../PerformDatabaseMigrationUseCase.kt | 9 -- .../domain/usecase/RemoveUserParamUseCase.kt | 10 +- .../domain/usecase/UpdateUserParamUseCase.kt | 14 -- .../domain/usecase/UpsertUserParamUseCase.kt | 26 ++++ 43 files changed, 758 insertions(+), 280 deletions(-) create mode 100644 domain/consumer-rules.pro create mode 100644 domain/proguard-rules.pro create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/StringProvider.kt delete mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/datasource/LocalDataSourceContract.kt delete mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/datasource/RuntimeDataSourceContract.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/enums/CommitMode.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/enums/SettingItemType.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/models/AppSetting.kt delete mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/models/DomainKernelParam.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/models/KernelParam.kt delete mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/models/KernelParamContract.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/models/ParamDocumentation.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/repository/AppSettingsRepository.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/repository/DocumentationRepository.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/repository/PresetRepository.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/repository/UserRepository.kt delete mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/AddUserParamUseCase.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ApplyParamUseCase.kt delete mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ApplyParamsUseCase.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetAppSettingsUseCase.kt delete mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetJsonParamsUseCase.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetParamDocumentationUseCase.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetRuntimeParamUseCase.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetUserParamByNameUseCase.kt delete mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ImportParamsUseCase.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/IsTaskerInstalledUseCase.kt delete mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/PerformDatabaseMigrationUseCase.kt delete mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/UpdateUserParamUseCase.kt create mode 100644 domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/UpsertUserParamUseCase.kt diff --git a/domain/consumer-rules.pro b/domain/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/domain/proguard-rules.pro b/domain/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/domain/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/StringProvider.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/StringProvider.kt new file mode 100644 index 0000000..b141cda --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/StringProvider.kt @@ -0,0 +1,12 @@ +package com.androidvip.sysctlgui.domain + +import androidx.annotation.StringRes + +/** + * Provides access to string resources. + * This interface allows for fetching localized strings, potentially with formatting arguments. + */ +interface StringProvider { + fun getString(@StringRes resId: Int): String + fun getString(@StringRes resId: Int, vararg formatArgs: Any): String +} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/datasource/LocalDataSourceContract.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/datasource/LocalDataSourceContract.kt deleted file mode 100644 index 7a47fe0..0000000 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/datasource/LocalDataSourceContract.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.androidvip.sysctlgui.domain.datasource - -interface LocalDataSourceContract { - suspend fun add(param: T, allowBlank: Boolean) - suspend fun addAll(params: List, allowBlank: Boolean) - suspend fun remove(param: T) - suspend fun edit(param: T, allowBlank: Boolean) - suspend fun clear() - suspend fun getData(): List -} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/datasource/RuntimeDataSourceContract.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/datasource/RuntimeDataSourceContract.kt deleted file mode 100644 index 40d9ede..0000000 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/datasource/RuntimeDataSourceContract.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.androidvip.sysctlgui.domain.datasource - -import java.io.File - -interface RuntimeDataSourceContract { - suspend fun edit(param: T, commitMode: String, useBusybox: Boolean, allowBlank: Boolean) - suspend fun getData(useBusybox: Boolean): List - suspend fun getParamsFromFiles(files: List): List -} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/di/DomainModule.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/di/DomainModule.kt index ca17635..dc7075a 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/di/DomainModule.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/di/DomainModule.kt @@ -1,34 +1,38 @@ package com.androidvip.sysctlgui.domain.di -import com.androidvip.sysctlgui.domain.usecase.AddUserParamUseCase import com.androidvip.sysctlgui.domain.usecase.AddUserParamsUseCase -import com.androidvip.sysctlgui.domain.usecase.ApplyParamsUseCase +import com.androidvip.sysctlgui.domain.usecase.ApplyParamUseCase import com.androidvip.sysctlgui.domain.usecase.BackupParamsUseCase import com.androidvip.sysctlgui.domain.usecase.ClearUserParamUseCase import com.androidvip.sysctlgui.domain.usecase.ExportParamsUseCase -import com.androidvip.sysctlgui.domain.usecase.GetJsonParamsUseCase +import com.androidvip.sysctlgui.domain.usecase.GetAppSettingsUseCase +import com.androidvip.sysctlgui.domain.usecase.GetParamDocumentationUseCase import com.androidvip.sysctlgui.domain.usecase.GetParamsFromFilesUseCase +import com.androidvip.sysctlgui.domain.usecase.GetRuntimeParamUseCase import com.androidvip.sysctlgui.domain.usecase.GetRuntimeParamsUseCase +import com.androidvip.sysctlgui.domain.usecase.GetUserParamByNameUseCase import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase -import com.androidvip.sysctlgui.domain.usecase.ImportParamsUseCase -import com.androidvip.sysctlgui.domain.usecase.PerformDatabaseMigrationUseCase +import com.androidvip.sysctlgui.domain.usecase.IsTaskerInstalledUseCase import com.androidvip.sysctlgui.domain.usecase.RemoveUserParamUseCase -import com.androidvip.sysctlgui.domain.usecase.UpdateUserParamUseCase +import com.androidvip.sysctlgui.domain.usecase.UpsertUserParamUseCase +import org.koin.android.ext.koin.androidContext +import org.koin.core.module.dsl.factoryOf import org.koin.dsl.module val domainModule = module { - factory { AddUserParamsUseCase(get(), get()) } - factory { AddUserParamUseCase(get(), get()) } - factory { ApplyParamsUseCase(get(), get()) } - factory { ClearUserParamUseCase(get()) } - factory { GetJsonParamsUseCase(get()) } - factory { GetParamsFromFilesUseCase(get()) } - factory { GetUserParamsUseCase(get()) } - factory { GetRuntimeParamsUseCase(get(), get()) } - factory { PerformDatabaseMigrationUseCase(get()) } - factory { RemoveUserParamUseCase(get()) } - factory { UpdateUserParamUseCase(get(), get()) } - factory { ImportParamsUseCase(get(), get(), get(), get()) } - factory { BackupParamsUseCase(get(), get()) } - factory { ExportParamsUseCase(get(), get()) } + factoryOf(::AddUserParamsUseCase) + factoryOf(::ApplyParamUseCase) + factoryOf(::ClearUserParamUseCase) + factoryOf(::GetParamsFromFilesUseCase) + factoryOf(::GetUserParamsUseCase) + factoryOf(::GetRuntimeParamsUseCase) + factoryOf(::GetRuntimeParamUseCase) + factoryOf(::GetUserParamByNameUseCase) + factoryOf(::RemoveUserParamUseCase) + factoryOf(::UpsertUserParamUseCase) + factoryOf(::BackupParamsUseCase) + factoryOf(::ExportParamsUseCase) + factoryOf(::GetAppSettingsUseCase) + factoryOf(::GetParamDocumentationUseCase) + factory { IsTaskerInstalledUseCase(androidContext()) } } diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/enums/CommitMode.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/enums/CommitMode.kt new file mode 100644 index 0000000..6b4b178 --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/enums/CommitMode.kt @@ -0,0 +1,27 @@ +package com.androidvip.sysctlgui.domain.enums + +/** + * Defines the method used to commit kernel parameter changes. + */ +enum class CommitMode { + /** + * Commits the value using the `sysctl -w` command. + * This is the default mode. + */ + SYSCTL, + /** + * Commits the value to the file using `echo` command. + * This method is generally safer and more reliable. + */ + ECHO; + + companion object { + fun parse(value: String): CommitMode { + return when (value) { + "sysctl" -> SYSCTL + "echo" -> ECHO + else -> SYSCTL + } + } + } +} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/enums/SettingItemType.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/enums/SettingItemType.kt new file mode 100644 index 0000000..52a17e9 --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/enums/SettingItemType.kt @@ -0,0 +1,27 @@ +package com.androidvip.sysctlgui.domain.enums + +/** + * Represents the different types of settings component that can be displayed in the UI. + * Each type corresponds to a specific UI element used to interact with the setting. + */ +enum class SettingItemType { + /** + * Simple setting header with no behavior + */ + Text, + + /** + * Represents a switch setting component that can be toggled on or off. + */ + Switch, + + /** + * Represents a list of options that can be selected from. + */ + List, + + /** + * Represents a slider component that allows the user to select a value within a range. + */ + Slider +} \ No newline at end of file diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/exceptions/ApplyValueException.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/exceptions/ApplyValueException.kt index dc60125..dd90a5f 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/exceptions/ApplyValueException.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/exceptions/ApplyValueException.kt @@ -1,4 +1,22 @@ package com.androidvip.sysctlgui.domain.exceptions +// TODO: Use sealed classes instead of exceptions +/** + * Exception thrown when a value commit fails or refuses to be applied (value remains the same) + */ class ApplyValueException(message: String) : Exception(message) + +/** + * Exception thrown when a value commit fails and the commit mode is "sysctl" + */ class CommitModeException(message: String) : Exception(message) + +/** + * Exception thrown when a value to be committed is blank and blank values are not allowed + */ +class BlankValueNotAllowedException() : IllegalArgumentException() + +/** + * Exception thrown when a shell command fails + */ +class ShellCommandException(message: String, cause: Throwable) : Exception(message, cause) diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/exceptions/ExportExceptions.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/exceptions/ExportExceptions.kt index 3d774bd..f4ced82 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/exceptions/ExportExceptions.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/exceptions/ExportExceptions.kt @@ -1,3 +1,6 @@ package com.androidvip.sysctlgui.domain.exceptions +/** + * Exception thrown when no parameter is found for a given kernel file path. + */ class NoParameterFoundException : Exception() diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/exceptions/ImportExceptions.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/exceptions/ImportExceptions.kt index 2e29597..1c5ca30 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/exceptions/ImportExceptions.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/exceptions/ImportExceptions.kt @@ -1,6 +1,12 @@ package com.androidvip.sysctlgui.domain.exceptions class InvalidFileExtensionException : Exception() +/** + * Thrown when an imported file is empty + */ class EmptyFileException : Exception() -class MalformedLineException : Exception() +/** + * Thrown when an invalid line is found during import. + */ +class MalformedLineException(message: String, cause: Throwable? = null) : Exception(message, cause) class NoValidParamException : Exception() diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/models/AppSetting.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/models/AppSetting.kt new file mode 100644 index 0000000..7754d4a --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/models/AppSetting.kt @@ -0,0 +1,31 @@ +package com.androidvip.sysctlgui.domain.models + +import com.androidvip.sysctlgui.domain.enums.SettingItemType + + +/** + * Represents an application setting. + * + * This data class encapsulates the properties of a single application setting, + * including its key, current value, enabled state, display information, and type. + * + * @param T The type of the setting's value. + * @property key A unique identifier for the setting. + * @property value The current value of the setting. + * @property enabled Indicates whether the setting is currently active or can be modified. Defaults to `true`. + * @property title A user-friendly name for the setting, displayed in the UI. + * @property description An optional detailed explanation of what the setting does. Defaults to `null`. + * @property category The group or section this setting belongs to, used for organization in the UI. + * @property type Defines how the setting is presented and interacted with in the UI (e.g., switch, list). + * @property values An optional list of possible values for the setting, typically used for dropdowns or selection lists. + */ +data class AppSetting( + val key: String, + val value: T, + val enabled: Boolean = true, + val title: String, + val description: String? = null, + val category: String, + val type: SettingItemType, + val values: List? = null +) diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/models/DomainKernelParam.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/models/DomainKernelParam.kt deleted file mode 100644 index 4ae163a..0000000 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/models/DomainKernelParam.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.androidvip.sysctlgui.domain.models - -open class DomainKernelParam( - open var id: Int = 0, - open var name: String = "", - open var path: String = "", - open var value: String = "", - open var favorite: Boolean = false, - open var taskerParam: Boolean = false, - open var taskerList: Int = LIST_NUMBER_PRIMARY_TASKER -) : KernelParamContract { - override val shortName: String get() = name.split(".").last() - - val configName: String get() = name.removeSuffix(shortName).removeSuffix(".") - - override fun setNameFromPath(path: String) { - if (path.trim().isEmpty() || !path.startsWith(PROC_SYS)) return - if (path.contains(".")) return - - name = path.removeSuffix("/") - .removePrefix(PROC_SYS) - .replace("/", ".") - .removePrefix(".") - } - - override fun setPathFromName(kernelParam: String) { - if (kernelParam.trim().isEmpty() || kernelParam.contains("/")) return - if (kernelParam.startsWith(".") || kernelParam.endsWith(".")) return - - path = "$PROC_SYS/${kernelParam.replace(".", "/")}" - } - - override fun hasValidPath(): Boolean { - if (path.trim().isEmpty() || !path.startsWith(PROC_SYS)) return false - if (path.contains(".")) return false - - return true - } - - override fun hasValidName(): Boolean { - if (name.trim().isEmpty() || name.contains("/")) return false - if (name.startsWith(".") || name.endsWith(".")) return false - - return true - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as DomainKernelParam - if (name != other.name) return false - - return true - } - - override fun hashCode(): Int { - return name.hashCode() - } - - override fun toString(): String { - return "$name = $value" - } - - companion object { - private const val PROC_SYS = "/proc/sys" - const val LIST_NUMBER_PRIMARY_TASKER: Int = 0 - } -} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/models/KernelParam.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/models/KernelParam.kt new file mode 100644 index 0000000..4128970 --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/models/KernelParam.kt @@ -0,0 +1,133 @@ +package com.androidvip.sysctlgui.domain.models + +import com.androidvip.sysctlgui.utils.Consts + +/** + * Represents a kernel parameter. + */ +open class KernelParam( + /** + * The name of the kernel parameter (e.g., "vm.swappiness") + */ + open val name: String, + + /** + * The path of the kernel parameter (e.g., "/proc/sys/vm/swappiness") + */ + open val path: String, + + /** + * The value of the kernel parameter (e.g., "60") + */ + open val value: String, + + /** + * Indicates whether the parameter is marked as a favorite by the user. + */ + open val isFavorite: Boolean = false, + + /** + * Indicates whether the parameter is used in a Tasker profile + */ + open val isTaskerParam: Boolean = false, + + /** + * Indicates the Tasker list number (primary or secondary) + */ + open val taskerList: Int = Consts.LIST_NUMBER_INVALID, +) { + + open val lastNameSegment: String + get() = name.substringAfterLast('.', name) + + /** + * The configuration part of the name, excluding the lastNameSegment. + * For example, for `vm.swappiness`, configName would be `vm`. + */ + open val groupName: String + get() = name.substringBeforeLast('.', "") + + /** + * Checks if the [path] is valid for a kernel parameter. + */ + fun hasValidPath() = path.isKernelPathValid() + + /** + * Checks if the [name] is valid for a kernel parameter. + */ + fun hasValidName() = name.isKernelNameValid() + + companion object { + + /** + * Creates a new instance with its `path` derived from a given `newName`. + * Example: If `newName` is "vm.swappiness", the derived path will be "/proc/sys/vm/swappiness". + * + * @param name The name to derive the path from. It must be a valid kernel parameter name. + * @return A new [KernelParam] instance with the derived path. + * @throws IllegalArgumentException if `newName` is not a valid kernel parameter name. + */ + fun createFromName( + name: String, + value: String, + isFavorite: Boolean = false + ): KernelParam { + require(name.isKernelNameValid()) { "Invalid name: $name" } + val derivedPath = "${Consts.PROC_SYS}/${name.replace(".", "/")}" + return KernelParam(name, value, derivedPath, isFavorite) + } + + /** + * Creates a [KernelParam] instance from a given path and value. + * The name is derived from the path. + * For example, for `/proc/sys/vm/swappiness/`, the derived name will be `vm.swappiness`. + * + * @param path The path of the kernel parameter (e.g., "/proc/sys/vm/swappiness"). + * It must be a valid path as defined by [isKernelPathValid]. + * @param value The value of the kernel parameter (e.g., "60"). + * @return A new [KernelParam] instance. + * @throws IllegalArgumentException if the provided [path] is invalid. + */ + fun createFromPath(path: String, value: String): KernelParam { + require(path.isKernelPathValid()) { "Invalid path: $path" } + + val derivedName = path.removeSuffix("/") + .removePrefix(Consts.PROC_SYS) + .replace("/", ".") + .removePrefix(".") + + return KernelParam(derivedName, value, path) + } + } +} + +/** + * Checks if the path of this kernel parameter is valid. + * A path is considered valid if: + * - It is not empty after trimming whitespace. + * - It starts with [Consts.PROC_SYS]. + * - It does not contain any "." characters (as paths use "/" as separators). + * + * @return `true` if the path is valid, `false` otherwise. + */ +private fun String.isKernelPathValid(): Boolean { + if (this.trim().isEmpty() || !this.startsWith(Consts.PROC_SYS)) return false + if (this.contains(".")) return false + return true +} + +/** + * Checks if a string is a valid kernel parameter name. + * A valid name: + * - Is not empty or blank. + * - Does not contain forward slashes ('/'). + * - Does not start or end with a dot ('.'). + * + * @return `true` if the string is a valid name, `false` otherwise. + */ +private fun String.isKernelNameValid(): Boolean { + if (this.trim().isEmpty() || this.contains("/")) return false + if (this.startsWith(".") || this.endsWith(".")) return false + return true +} + diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/models/KernelParamContract.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/models/KernelParamContract.kt deleted file mode 100644 index 0affdb3..0000000 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/models/KernelParamContract.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.androidvip.sysctlgui.domain.models - -interface KernelParamContract { - val shortName: String - - fun setNameFromPath(path: String) - fun setPathFromName(kernelParam: String) - fun hasValidPath(): Boolean - fun hasValidName(): Boolean -} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/models/ParamDocumentation.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/models/ParamDocumentation.kt new file mode 100644 index 0000000..671d7af --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/models/ParamDocumentation.kt @@ -0,0 +1,16 @@ +package com.androidvip.sysctlgui.domain.models + +/** + * Represents documentation for a kernel parameter. + * + * @property title The title of the documentation. + * @property documentationText The plain text documentation. + * @property documentationHtml The HTML formatted documentation, if available. + * @property url The URL to the online documentation, if available. + */ +data class ParamDocumentation( + val title: String = "", + val documentationText: String = "", + val documentationHtml: String? = null, + val url: String? = null +) diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/AppPrefs.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/AppPrefs.kt index b2dc3e5..251b8cc 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/AppPrefs.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/AppPrefs.kt @@ -1,5 +1,11 @@ package com.androidvip.sysctlgui.domain.repository +/** + * Interface for accessing and modifying application preferences. + * + * This interface defines the contract for interacting with the application's settings, + * allowing various parts of the app to read and write preference values. + */ interface AppPrefs { var listFoldersFirst: Boolean var guessInputType: Boolean @@ -9,8 +15,12 @@ interface AppPrefs { var runOnStartUp: Boolean var startUpDelay: Int var showTaskerToast: Boolean - var migrationCompleted: Boolean var forceDark: Boolean var dynamicColors: Boolean var askedForNotificationPermission: Boolean + var useOnlineDocs: Boolean + var contrastLevel: Int + val searchHistory: Set + fun addSearchToHistory(query: String) + fun removeSearchFromHistory(query: String) } diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/AppSettingsRepository.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/AppSettingsRepository.kt new file mode 100644 index 0000000..ae0f1ed --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/AppSettingsRepository.kt @@ -0,0 +1,7 @@ +package com.androidvip.sysctlgui.domain.repository + +import com.androidvip.sysctlgui.domain.models.AppSetting + +fun interface AppSettingsRepository { + suspend fun getAppSettings(): List> +} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/DocumentationRepository.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/DocumentationRepository.kt new file mode 100644 index 0000000..dd28609 --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/DocumentationRepository.kt @@ -0,0 +1,18 @@ +package com.androidvip.sysctlgui.domain.repository + +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.models.ParamDocumentation + +/** + * Repository interface for fetching documentation for kernel parameters. + */ +fun interface DocumentationRepository { + /** + * Retrieves documentation for a given kernel parameter. + * + * @param param The kernel parameter for which to fetch documentation. + * @param online Whether to use the online documentation source. + * @return The documentation if found, null otherwise. + */ + suspend fun getDocumentation(param: KernelParam, online: Boolean): ParamDocumentation? +} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/ParamsRepository.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/ParamsRepository.kt index 81bf7aa..79849aa 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/ParamsRepository.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/ParamsRepository.kt @@ -1,33 +1,69 @@ package com.androidvip.sysctlgui.domain.repository -import com.androidvip.sysctlgui.domain.models.DomainKernelParam +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.enums.CommitMode +import kotlinx.coroutines.flow.Flow import java.io.File -import java.io.FileDescriptor -import java.io.InputStream +/** + * Repository interface for managing kernel parameters. + */ interface ParamsRepository { - suspend fun getUserParams(): List - suspend fun getJsonParams(): List - suspend fun getRuntimeParams(useBusybox: Boolean): List - suspend fun getParamsFromFiles(files: List): List - - suspend fun applyParam( - param: DomainKernelParam, - commitMode: String, + /** + * Gets all available kernel parameters at runtime. + * + * @param useBusybox whether to use busybox or not. + * @param userParams optional user params list to be merged with runtime params. + * @return a [List] of [KernelParam]s. + */ + fun getRuntimeParams( useBusybox: Boolean, - allowBlank: Boolean - ) - suspend fun updateUserParam(param: DomainKernelParam, allowBlank: Boolean) + userParams: List = emptyList() + ): Flow> + + /** + * Gets a kernel parameter value at runtime. + * + * @param paramName the name of the parameter to get, in the group.name format: + * - **vm.admin_reserve_kbytes (OK ✅)** + * - admin_reserve_kbytes (NO ❌) + * - vm/admin_reserve_kbytes (NO ❌) + * - /proc/sys/vm/admin_reserve_kbytes (NO ❌) + * @param useBusybox whether to use busybox or not. + * @return the [KernelParam] or null if not found or an error occurred. + */ + suspend fun getRuntimeParam(paramName: String, useBusybox: Boolean): KernelParam? + + /** + * Sets the value of a kernel parameter at runtime. + * @param param The [KernelParam] object representing the kernel parameter to be set. + * @param commitMode The commit mode to use when setting the parameter. + * @param useBusybox Whether to use busybox or not. + */ + suspend fun setRuntimeParam( + param: KernelParam, + commitMode: CommitMode, + useBusybox: Boolean + ): String - suspend fun addUserParam(param: DomainKernelParam, allowBlank: Boolean) - suspend fun addUserParams(params: List, allowBlank: Boolean) - suspend fun removeUserParam(param: DomainKernelParam) - suspend fun clearUserParams() + /** + * Reads kernel parameters from a list of files. + * Each file is expected to contain a single line with the parameter value. + * + * @param files A list of [File] objects representing the files to read parameters from. + * @return A [Flow] emitting a list of [KernelParam] objects. + */ + fun getParamsFromFiles(files: List): Flow> - suspend fun performDatabaseMigration() + /** + * Gets a list of kernel parameters from the given [path]. + * + * @param path The path to search for kernel parameters. + * @return A list of [KernelParam] objects found in the given path. + */ + fun getParamsFromPath(path: String): Flow> - suspend fun importParamsFromJson(stream: InputStream): List - suspend fun importParamsFromConf(stream: InputStream): List - suspend fun exportParams(params: List, fileDescriptor: FileDescriptor) - suspend fun backupParams(params: List, fileDescriptor: FileDescriptor) + companion object { + const val DEFAULT_ERROR_MESSAGE = "error" + } } diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/PresetRepository.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/PresetRepository.kt new file mode 100644 index 0000000..dabc145 --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/PresetRepository.kt @@ -0,0 +1,44 @@ +package com.androidvip.sysctlgui.domain.repository + +import com.androidvip.sysctlgui.domain.models.KernelParam +import java.io.FileDescriptor +import java.io.InputStream + + +/** + * Interface defining operations for managing kernel parameter presets. + * This interface provides methods to read and write kernel parameter presets, + * allowing users to save and load configurations. + */ +interface PresetRepository { + /** + * Reads a preset of kernel parameters from an input stream. + * + * This function attempts to determine if the input stream contains JSON or CONF formatted data + * and parses it accordingly. + * + * @param stream The input stream containing the kernel parameter preset. + * @return A list of [KernelParam] objects parsed from the stream. + * @throws IllegalArgumentException if the stream format cannot be determined or if parsing fails. + */ + suspend fun readPreset(stream: InputStream): List + + /** + * Exports a list of kernel parameters to a preset file. + * + * This function writes the provided kernel parameters to a specified file descriptor, + * typically for creating a user-defined preset. + * + * @param params The list of [KernelParam] objects to export. + * @param fileDescriptor The `FileDescriptor` of the file to write the parameters to. + */ + suspend fun exportToPreset(params: List, fileDescriptor: FileDescriptor) + + /** + * Backs up a list of kernel parameters. + * + * @param params The list of `KernelParam` objects to backup. + * @param fileDescriptor The `FileDescriptor` of the file to write the backup to. + */ + suspend fun backupParams(params: List, fileDescriptor: FileDescriptor) +} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/UserRepository.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/UserRepository.kt new file mode 100644 index 0000000..b577cdc --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/UserRepository.kt @@ -0,0 +1,47 @@ +package com.androidvip.sysctlgui.domain.repository + +import com.androidvip.sysctlgui.domain.models.KernelParam +import kotlinx.coroutines.flow.Flow + +/** + * Interface for managing user-specific kernel parameters. + */ +interface UserRepository { + /** + * Retrieves a [Flow] that emits a list of user-configurable kernel parameters. + * The [Flow] will emit a new list whenever the underlying data changes. + */ + val userParams: Flow> + + suspend fun getParamByName(name: String): KernelParam? + + /** + * Inserts or updates a user-configurable kernel parameter. + * If a parameter with the same ID already exists, it will be updated. + * Otherwise, a new parameter will be inserted. + * + * @param param The [KernelParam] to upsert. + * @return The row ID of the inserted or updated parameter. + */ + suspend fun upsertUserParam(param: KernelParam): Long + + /** + * Adds a list of kernel parameters to the list of user-configurable parameters. + * + * @param params The list of [KernelParam] objects to be added. + * @return A list of Long values representing the row IDs of the newly inserted parameters. + */ + suspend fun upsertUserParams(params: List): List + + /** + * Removes a kernel parameter from the list of user-configurable parameters. + * @param param The [KernelParam] to be removed. + * @return The number of rows deleted. + */ + suspend fun removeUserParam(param: KernelParam): Int + + /** + * Clears all user-configurable kernel parameters. + */ + suspend fun clearUserParams() +} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/AddUserParamUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/AddUserParamUseCase.kt deleted file mode 100644 index 2ad4552..0000000 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/AddUserParamUseCase.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.androidvip.sysctlgui.domain.usecase - -import com.androidvip.sysctlgui.domain.models.DomainKernelParam -import com.androidvip.sysctlgui.domain.repository.AppPrefs -import com.androidvip.sysctlgui.domain.repository.ParamsRepository - -class AddUserParamUseCase( - private val repository: ParamsRepository, - private val appPrefs: AppPrefs -) { - suspend operator fun invoke(param: DomainKernelParam) { - return repository.addUserParam(param, appPrefs.allowBlankValues) - } -} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/AddUserParamsUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/AddUserParamsUseCase.kt index dbd5d6f..d723500 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/AddUserParamsUseCase.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/AddUserParamsUseCase.kt @@ -1,14 +1,20 @@ package com.androidvip.sysctlgui.domain.usecase -import com.androidvip.sysctlgui.domain.models.DomainKernelParam +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.exceptions.BlankValueNotAllowedException import com.androidvip.sysctlgui.domain.repository.AppPrefs -import com.androidvip.sysctlgui.domain.repository.ParamsRepository +import com.androidvip.sysctlgui.domain.repository.UserRepository class AddUserParamsUseCase( - private val repository: ParamsRepository, + private val repository: UserRepository, private val appPrefs: AppPrefs ) { - suspend operator fun invoke(params: List) { - return repository.addUserParams(params, appPrefs.allowBlankValues) + suspend operator fun invoke(params: List): List { + if (!appPrefs.allowBlankValues) { + if (params.any { it.value.isBlank() }) { + throw BlankValueNotAllowedException() + } + } + return repository.upsertUserParams(params) } } diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ApplyParamUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ApplyParamUseCase.kt new file mode 100644 index 0000000..1001864 --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ApplyParamUseCase.kt @@ -0,0 +1,76 @@ +package com.androidvip.sysctlgui.domain.usecase + +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.enums.CommitMode +import com.androidvip.sysctlgui.domain.exceptions.ApplyValueException +import com.androidvip.sysctlgui.domain.exceptions.BlankValueNotAllowedException +import com.androidvip.sysctlgui.domain.exceptions.CommitModeException +import com.androidvip.sysctlgui.domain.exceptions.ShellCommandException +import com.androidvip.sysctlgui.domain.repository.AppPrefs +import com.androidvip.sysctlgui.domain.repository.ParamsRepository + +class ApplyParamUseCase( + private val repository: ParamsRepository, + private val appPrefs: AppPrefs +) { + suspend operator fun invoke(param: KernelParam) { + if (param.value.isBlank() && !appPrefs.allowBlankValues) { + throw BlankValueNotAllowedException() + } + + val commitMode = CommitMode.parse(appPrefs.commitMode) + + try { + val output = repository.setRuntimeParam( + param = param, + commitMode = commitMode, + useBusybox = appPrefs.useBusybox, + ) + when (commitMode) { + CommitMode.SYSCTL -> { + if (!output.contains(param.name)) { + throw CommitModeException( + "Sysctl command for '${param.name}' executed, but output did not confirm the change. " + + "Output: '$output'. Try using '${CommitMode.ECHO}' mode." + ) + } + } + + CommitMode.ECHO -> { + if (output.isEmpty().not()) { + throw CommitModeException( + "Echo command for '${param.path}' executed, but output was not empty. " + + "Output: '$output'. Try using '${CommitMode.SYSCTL}' mode." + ) + } + } + } + + } catch (e: ShellCommandException) { + val message = e.cause?.message.orEmpty() + throwApplyValueException( + message = "$message <- ${e.message}", + commitMode = commitMode, + param = param + ) + } catch (e: Exception) { + throwApplyValueException( + message = e.message.orEmpty(), + commitMode = commitMode, + param = param + ) + } + } + + private fun throwApplyValueException( + message: String, + commitMode: CommitMode, + param: KernelParam + ) { + val errorMessage = when (commitMode) { + CommitMode.SYSCTL -> "Failed to execute sysctl command for '${param.name}'" + CommitMode.ECHO -> "Failed to write value '${param.value}' to '${param.path}'" + } + throw ApplyValueException(errorMessage) + } +} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ApplyParamsUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ApplyParamsUseCase.kt deleted file mode 100644 index a8b3284..0000000 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ApplyParamsUseCase.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.androidvip.sysctlgui.domain.usecase - -import com.androidvip.sysctlgui.domain.models.DomainKernelParam -import com.androidvip.sysctlgui.domain.repository.AppPrefs -import com.androidvip.sysctlgui.domain.repository.ParamsRepository - -class ApplyParamsUseCase( - private val repository: ParamsRepository, - private val appPrefs: AppPrefs -) { - suspend operator fun invoke(param: DomainKernelParam) { - return repository.applyParam( - param, - appPrefs.commitMode, - appPrefs.useBusybox, - appPrefs.allowBlankValues - ) - } -} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/BackupParamsUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/BackupParamsUseCase.kt index e6dff15..372acd7 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/BackupParamsUseCase.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/BackupParamsUseCase.kt @@ -1,15 +1,15 @@ package com.androidvip.sysctlgui.domain.usecase import com.androidvip.sysctlgui.domain.exceptions.NoParameterFoundException -import com.androidvip.sysctlgui.domain.repository.ParamsRepository +import com.androidvip.sysctlgui.domain.repository.PresetRepository import java.io.FileDescriptor class BackupParamsUseCase( - private val getRuntimeParamsUseCase: GetRuntimeParamsUseCase, - private val repository: ParamsRepository + private val getRuntimeParams: GetRuntimeParamsUseCase, + private val repository: PresetRepository ) { suspend operator fun invoke(fileDescriptor: FileDescriptor) { - val params = getRuntimeParamsUseCase() + val params = getRuntimeParams() if (params.isEmpty()) throw NoParameterFoundException() return repository.backupParams(params, fileDescriptor) diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ClearUserParamUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ClearUserParamUseCase.kt index 0d70a25..fb3eea2 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ClearUserParamUseCase.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ClearUserParamUseCase.kt @@ -1,7 +1,7 @@ package com.androidvip.sysctlgui.domain.usecase -import com.androidvip.sysctlgui.domain.repository.ParamsRepository +import com.androidvip.sysctlgui.domain.repository.UserRepository -class ClearUserParamUseCase(private val repository: ParamsRepository) { +class ClearUserParamUseCase(private val repository: UserRepository) { suspend operator fun invoke() = repository.clearUserParams() } diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ExportParamsUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ExportParamsUseCase.kt index 7179b4a..0d32551 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ExportParamsUseCase.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ExportParamsUseCase.kt @@ -1,17 +1,25 @@ package com.androidvip.sysctlgui.domain.usecase import com.androidvip.sysctlgui.domain.exceptions.NoParameterFoundException -import com.androidvip.sysctlgui.domain.repository.ParamsRepository +import com.androidvip.sysctlgui.domain.repository.PresetRepository import java.io.FileDescriptor +/** + * Exports the current user parameters to a preset file. + * + * @throws NoParameterFoundException if there are no parameters to export. + */ class ExportParamsUseCase( - private val getUserParamUseCase: GetUserParamsUseCase, - private val repository: ParamsRepository + private val getUserParams: GetUserParamsUseCase, + private val repository: PresetRepository ) { + /** + * @param fileDescriptor The file descriptor to write the preset to. + */ suspend operator fun invoke(fileDescriptor: FileDescriptor) { - val params = getUserParamUseCase() + val params = getUserParams() if (params.isEmpty()) throw NoParameterFoundException() - return repository.exportParams(params, fileDescriptor) + return repository.exportToPreset(params, fileDescriptor) } } diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetAppSettingsUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetAppSettingsUseCase.kt new file mode 100644 index 0000000..32ff388 --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetAppSettingsUseCase.kt @@ -0,0 +1,20 @@ +package com.androidvip.sysctlgui.domain.usecase + +import com.androidvip.sysctlgui.domain.models.AppSetting +import com.androidvip.sysctlgui.domain.repository.AppSettingsRepository + +/** + * Use case for retrieving app settings. + * + * This class provides a way to fetch app settings, optionally filtering them based on a + * provided predicate. + * + * @property repository The [AppSettingsRepository] used to access app settings data. + */ +class GetAppSettingsUseCase(private val repository: AppSettingsRepository) { + suspend operator fun invoke( + filterPredicate: (AppSetting<*>) -> Boolean = { true } + ): List> { + return repository.getAppSettings().filter(filterPredicate) + } +} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetJsonParamsUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetJsonParamsUseCase.kt deleted file mode 100644 index 157dc69..0000000 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetJsonParamsUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.androidvip.sysctlgui.domain.usecase - -import com.androidvip.sysctlgui.domain.repository.ParamsRepository - -class GetJsonParamsUseCase(private val repository: ParamsRepository) { - suspend operator fun invoke() = repository.getJsonParams() -} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetParamDocumentationUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetParamDocumentationUseCase.kt new file mode 100644 index 0000000..021a2cc --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetParamDocumentationUseCase.kt @@ -0,0 +1,15 @@ +package com.androidvip.sysctlgui.domain.usecase + +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.models.ParamDocumentation +import com.androidvip.sysctlgui.domain.repository.AppPrefs +import com.androidvip.sysctlgui.domain.repository.DocumentationRepository + +class GetParamDocumentationUseCase( + private val repository: DocumentationRepository, + private val appPrefs: AppPrefs +) { + suspend operator fun invoke(param: KernelParam): ParamDocumentation? { + return repository.getDocumentation(param, appPrefs.useOnlineDocs) + } +} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetParamsFromFilesUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetParamsFromFilesUseCase.kt index 3c3347e..4a91398 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetParamsFromFilesUseCase.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetParamsFromFilesUseCase.kt @@ -1,11 +1,12 @@ package com.androidvip.sysctlgui.domain.usecase -import com.androidvip.sysctlgui.domain.models.DomainKernelParam +import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.domain.repository.ParamsRepository +import kotlinx.coroutines.flow.single import java.io.File class GetParamsFromFilesUseCase(private val repository: ParamsRepository) { - suspend operator fun invoke(files: List): List { - return repository.getParamsFromFiles(files) + suspend operator fun invoke(files: List): List { + return repository.getParamsFromFiles(files).single() } } diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetRuntimeParamUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetRuntimeParamUseCase.kt new file mode 100644 index 0000000..62bb645 --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetRuntimeParamUseCase.kt @@ -0,0 +1,25 @@ +package com.androidvip.sysctlgui.domain.usecase + +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.repository.AppPrefs +import com.androidvip.sysctlgui.domain.repository.ParamsRepository +import kotlinx.coroutines.flow.single + + +/** + * Fetches a single runtime kernel parameter by its name. + * + * This use case interacts with the [ParamsRepository] to retrieve a specific kernel parameter + * and respects the user's preference for using BusyBox, as defined in [AppPrefs]. + */ +class GetRuntimeParamUseCase( + private val repository: ParamsRepository, + private val appPrefs: AppPrefs +) { + suspend operator fun invoke(paramName: String): KernelParam? { + return repository.getRuntimeParam( + useBusybox = appPrefs.useBusybox, + paramName = paramName + ) + } +} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetRuntimeParamsUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetRuntimeParamsUseCase.kt index 2746fae..6876b1d 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetRuntimeParamsUseCase.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetRuntimeParamsUseCase.kt @@ -1,14 +1,24 @@ package com.androidvip.sysctlgui.domain.usecase -import com.androidvip.sysctlgui.domain.models.DomainKernelParam +import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.domain.repository.AppPrefs import com.androidvip.sysctlgui.domain.repository.ParamsRepository +import kotlinx.coroutines.flow.single +/** + * Fetches the list of runtime kernel parameters. + * + * This use case interacts with the [ParamsRepository] to retrieve the current kernel parameters + * and respects the user's preference for using BusyBox, as defined in [AppPrefs]. + */ class GetRuntimeParamsUseCase( private val repository: ParamsRepository, private val appPrefs: AppPrefs ) { - suspend operator fun invoke(): List { - return repository.getRuntimeParams(appPrefs.useBusybox) + suspend operator fun invoke(userParams: List = emptyList()): List { + return repository.getRuntimeParams( + useBusybox = appPrefs.useBusybox, + userParams = userParams + ).single() } } diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetUserParamByNameUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetUserParamByNameUseCase.kt new file mode 100644 index 0000000..5569c21 --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetUserParamByNameUseCase.kt @@ -0,0 +1,7 @@ +package com.androidvip.sysctlgui.domain.usecase + +import com.androidvip.sysctlgui.domain.repository.UserRepository + +class GetUserParamByNameUseCase(private val repository: UserRepository) { + suspend operator fun invoke(paramName: String) = repository.getParamByName(paramName) +} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetUserParamsUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetUserParamsUseCase.kt index 73222bf..529fdca 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetUserParamsUseCase.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetUserParamsUseCase.kt @@ -1,7 +1,8 @@ package com.androidvip.sysctlgui.domain.usecase -import com.androidvip.sysctlgui.domain.repository.ParamsRepository +import com.androidvip.sysctlgui.domain.repository.UserRepository +import kotlinx.coroutines.flow.single -class GetUserParamsUseCase(private val repository: ParamsRepository) { - suspend operator fun invoke() = repository.getUserParams() +class GetUserParamsUseCase(private val repository: UserRepository) { + suspend operator fun invoke() = repository.userParams.single() } diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ImportParamsUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ImportParamsUseCase.kt deleted file mode 100644 index b7c92c7..0000000 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ImportParamsUseCase.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.androidvip.sysctlgui.domain.usecase - -import com.androidvip.sysctlgui.domain.exceptions.InvalidFileExtensionException -import com.androidvip.sysctlgui.domain.exceptions.NoValidParamException -import com.androidvip.sysctlgui.domain.models.DomainKernelParam -import com.androidvip.sysctlgui.domain.repository.ParamsRepository -import java.io.InputStream - -class ImportParamsUseCase( - private val clearUserParamUseCase: ClearUserParamUseCase, - private val addUserParamsUseCase: AddUserParamsUseCase, - private val applyParamsUseCase: ApplyParamsUseCase, - private val repository: ParamsRepository -) { - suspend operator fun invoke( - stream: InputStream, - fileExtension: String - ): List { - val isBackup = fileExtension.endsWith(".conf") - val params = when { - fileExtension.endsWith(".json") -> repository.importParamsFromJson(stream) - isBackup -> repository.importParamsFromConf(stream) - else -> throw InvalidFileExtensionException() - } - - if (params.isEmpty()) throw NoValidParamException() - - val successfulParams = mutableListOf() - params.forEach { param -> - // Apply the param to check if valid - runCatching { applyParamsUseCase(param) }.onSuccess { - successfulParams.add(param) - } - } - - clearUserParamUseCase() - - // Prevent adding full backups to the apply-on-boot list - if (!isBackup) { - addUserParamsUseCase(successfulParams) - } - - return successfulParams - } -} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/IsTaskerInstalledUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/IsTaskerInstalledUseCase.kt new file mode 100644 index 0000000..52706a9 --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/IsTaskerInstalledUseCase.kt @@ -0,0 +1,27 @@ +package com.androidvip.sysctlgui.domain.usecase + +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build + +class IsTaskerInstalledUseCase(private val context: Context) { + operator fun invoke(): Boolean { + val packageManager = context.packageManager + + return runCatching { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + packageManager.getPackageInfo( + TASKER_PACKAGE_NAME, + PackageManager.PackageInfoFlags.of(0L) + ) + } else { + packageManager.getPackageInfo(TASKER_PACKAGE_NAME, 0) + } + true + }.getOrDefault(false) + } + + companion object { + private const val TASKER_PACKAGE_NAME = "net.dinglisch.android.taskerm" + } +} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/PerformDatabaseMigrationUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/PerformDatabaseMigrationUseCase.kt deleted file mode 100644 index eea6e29..0000000 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/PerformDatabaseMigrationUseCase.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.androidvip.sysctlgui.domain.usecase - -import com.androidvip.sysctlgui.domain.repository.ParamsRepository - -class PerformDatabaseMigrationUseCase(private val repository: ParamsRepository) { - suspend operator fun invoke() { - return repository.performDatabaseMigration() - } -} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/RemoveUserParamUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/RemoveUserParamUseCase.kt index de0bd61..f592deb 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/RemoveUserParamUseCase.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/RemoveUserParamUseCase.kt @@ -1,8 +1,10 @@ package com.androidvip.sysctlgui.domain.usecase -import com.androidvip.sysctlgui.domain.models.DomainKernelParam -import com.androidvip.sysctlgui.domain.repository.ParamsRepository +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.repository.UserRepository -class RemoveUserParamUseCase(private val repository: ParamsRepository) { - suspend fun execute(param: DomainKernelParam) = repository.removeUserParam(param) +class RemoveUserParamUseCase(private val repository: UserRepository) { + suspend operator fun invoke(param: KernelParam) { + repository.removeUserParam(param) + } } diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/UpdateUserParamUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/UpdateUserParamUseCase.kt deleted file mode 100644 index 97192e0..0000000 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/UpdateUserParamUseCase.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.androidvip.sysctlgui.domain.usecase - -import com.androidvip.sysctlgui.domain.models.DomainKernelParam -import com.androidvip.sysctlgui.domain.repository.AppPrefs -import com.androidvip.sysctlgui.domain.repository.ParamsRepository - -class UpdateUserParamUseCase( - private val repository: ParamsRepository, - private val appPrefs: AppPrefs -) { - suspend operator fun invoke(param: DomainKernelParam) { - return repository.updateUserParam(param, appPrefs.allowBlankValues) - } -} diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/UpsertUserParamUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/UpsertUserParamUseCase.kt new file mode 100644 index 0000000..3ce75c7 --- /dev/null +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/UpsertUserParamUseCase.kt @@ -0,0 +1,26 @@ +package com.androidvip.sysctlgui.domain.usecase + +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.exceptions.BlankValueNotAllowedException +import com.androidvip.sysctlgui.domain.repository.AppPrefs +import com.androidvip.sysctlgui.domain.repository.UserRepository + +/** + * Updates a user-defined kernel parameter. + * + * @property repository The [UserRepository] to interact with user parameters. + * @property appPrefs The [AppPrefs] to check application preferences + * @throws BlankValueNotAllowedException if the parameter value is blank and blank values are not allowed. + */ +class UpsertUserParamUseCase( + private val repository: UserRepository, + private val appPrefs: AppPrefs +) { + suspend operator fun invoke(param: KernelParam): Long { + if (param.value.isBlank() && !appPrefs.allowBlankValues) { + throw BlankValueNotAllowedException() + } + + return repository.upsertUserParam(param) + } +} From 40a95b65dbd8239768ad9916d7f6b0dd94607943 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Mon, 11 Aug 2025 21:29:18 -0300 Subject: [PATCH 04/23] refactor: [WIP] updated data layer --- .../sysctlgui/utils/BaseViewModel.kt | 5 +- .../com/androidvip/sysctlgui/utils/Consts.kt | 15 - .../sysctlgui/utils/ContextUtils.kt | 11 + .../com/androidvip/sysctlgui/utils/Misc.kt | 32 ++ .../androidvip/sysctlgui/utils/ViewState.kt | 16 - data/build.gradle.kts | 13 +- data/consumer-rules.pro | 0 .../com/androidvip/sysctlgui/data/Prefs.kt | 18 ++ .../data/datasource/JsonParamDataSource.kt | 93 ------ .../data/datasource/RoomParamDataSource.kt | 61 ---- .../data/datasource/RuntimeParamDataSource.kt | 93 ------ .../androidvip/sysctlgui/data/db/ParamDao.kt | 31 +- .../sysctlgui/data/db/ParamDatabase.kt | 4 +- .../sysctlgui/data/di/DataModule.kt | 78 ++++- .../sysctlgui/data/mapper/Mapper.kt | 6 - .../sysctlgui/data/mapper/RoomParamMapper.kt | 26 -- .../sysctlgui/data/models/KernelParamDTO.kt | 30 ++ .../sysctlgui/data/models/RoomKernelParam.kt | 24 -- .../sysctlgui/data/repository/AppPrefsImpl.kt | 78 +++-- .../repository/AppSettingsRepositoryImpl.kt | 145 +++++++++ .../repository/DocumentationRepositoryImpl.kt | 34 ++ .../data/repository/ParamsRepositoryImpl.kt | 298 +++++++----------- .../data/repository/PresetRepositoryImpl.kt | 70 ++++ .../data/repository/UserRepositoryImpl.kt | 45 +++ .../data/source/DocumentationDataSource.kt | 20 ++ .../source/OfflineDocumentationDataSource.kt | 122 +++++++ .../source/OnlineDocumentationDataSource.kt | 100 ++++++ .../data/utils/AndroidStringProvider.kt | 11 + .../data/utils/KernelParamSerializer.kt | 65 ++++ .../data/utils/PresetsFileProcessor.kt | 52 +++ .../sysctlgui/data/utils/RootUtils.kt | 57 ++-- {app => data}/src/main/res/raw/abi.txt | 0 {app => data}/src/main/res/raw/fs.txt | 0 {app => data}/src/main/res/raw/kernel.txt | 0 {app => data}/src/main/res/raw/net.txt | 0 {app => data}/src/main/res/raw/vm.txt | 0 36 files changed, 1046 insertions(+), 607 deletions(-) create mode 100644 common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/ContextUtils.kt create mode 100644 common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/Misc.kt delete mode 100644 common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/ViewState.kt create mode 100644 data/consumer-rules.pro create mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/Prefs.kt delete mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/datasource/JsonParamDataSource.kt delete mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/datasource/RoomParamDataSource.kt delete mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/datasource/RuntimeParamDataSource.kt delete mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/mapper/Mapper.kt delete mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/mapper/RoomParamMapper.kt create mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt delete mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/models/RoomKernelParam.kt create mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt create mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt create mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/repository/PresetRepositoryImpl.kt create mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt create mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/source/DocumentationDataSource.kt create mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/source/OfflineDocumentationDataSource.kt create mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/source/OnlineDocumentationDataSource.kt create mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/utils/AndroidStringProvider.kt create mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/utils/KernelParamSerializer.kt create mode 100644 data/src/main/java/com/androidvip/sysctlgui/data/utils/PresetsFileProcessor.kt rename {app => data}/src/main/res/raw/abi.txt (100%) rename {app => data}/src/main/res/raw/fs.txt (100%) rename {app => data}/src/main/res/raw/kernel.txt (100%) rename {app => data}/src/main/res/raw/net.txt (100%) rename {app => data}/src/main/res/raw/vm.txt (100%) diff --git a/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/BaseViewModel.kt b/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/BaseViewModel.kt index 0e06803..b5da4e6 100644 --- a/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/BaseViewModel.kt +++ b/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/BaseViewModel.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch abstract class BaseViewModel : ViewModel() { @@ -28,7 +29,7 @@ abstract class BaseViewModel : ViewModel() { abstract fun onEvent(event: Event) protected fun setState(block: State.() -> State) { - _uiState.value = currentState.block() + _uiState.update(block) } protected fun setEffect(block: () -> Effect) { @@ -36,4 +37,4 @@ abstract class BaseViewModel : ViewModel() { _effect.send(block()) } } -} \ No newline at end of file +} diff --git a/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/Consts.kt b/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/Consts.kt index fa34402..1c16f66 100644 --- a/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/Consts.kt +++ b/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/Consts.kt @@ -8,19 +8,4 @@ object Consts { const val LIST_NUMBER_SECONDARY_TASKER: Int = 1 const val LIST_NUMBER_FAVORITES: Int = 2 const val LIST_NUMBER_APPLY_ON_BOOT: Int = 3 - - object Prefs { - const val LIST_FOLDERS_FIRST = "list_folders_first" - const val GUESS_INPUT_TYPE = "guess_input_type" - const val COMMIT_MODE = "commit_mode" - const val ALLOW_BLANK = "allow_blank_values" - const val USE_BUSYBOX = "use_busybox" - const val RUN_ON_START_UP = "run_on_start_up" - const val START_UP_DELAY = "startup_delay" - const val SHOW_TASKER_TOAST = "show_tasker_toast" - const val MIGRATION_COMPLETED = "migration_completed" - const val FORCE_DARK_THEME = "force_dark_theme" - const val DYNAMIC_COLORS = "dynamic_colors" - const val ASKED_NOTIFICATION_PERMISSION = "asked_notification_permission" - } } diff --git a/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/ContextUtils.kt b/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/ContextUtils.kt new file mode 100644 index 0000000..d084717 --- /dev/null +++ b/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/ContextUtils.kt @@ -0,0 +1,11 @@ +package com.androidvip.sysctlgui.utils + +import android.content.Context +import android.content.Intent +import androidx.core.net.toUri + +fun Context.browse(url: String) { + val intent = Intent(Intent.ACTION_VIEW, url.toUri()) + runCatching { startActivity(intent) } +} + diff --git a/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/Misc.kt b/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/Misc.kt new file mode 100644 index 0000000..806811e --- /dev/null +++ b/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/Misc.kt @@ -0,0 +1,32 @@ +package com.androidvip.sysctlgui.utils + +import android.view.View +import androidx.core.view.HapticFeedbackConstantsCompat +import androidx.core.view.ViewCompat + +/** + * Checks if a string is a valid sysctl line. + * A valid sysctl line must: + * - Contain exactly one "=" character. + * - Not have blank parts before or after the "=". + * - Have a key that matches the pattern: `^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)+$` + * (e.g., "vm.swappiness", "net.ipv4.tcp_congestion_control"). + * + * @return `true` if the string is a valid sysctl line, `false` otherwise. + */ +fun String.isValidSysctlLine(): Boolean { + val parts = this.split("=", limit = 2) + if (parts.size != 2 || parts.any { it.isBlank() }) return false + + val keyPattern = "^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)+$".toRegex() + return keyPattern.matches(parts.first()) +} + +fun performHapticFeedbackForToggle(newState: Boolean, view: View) { + val feedbackConst = if (newState) { + HapticFeedbackConstantsCompat.TOGGLE_ON + } else { + HapticFeedbackConstantsCompat.TOGGLE_OFF + } + ViewCompat.performHapticFeedback(view, feedbackConst) +} diff --git a/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/ViewState.kt b/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/ViewState.kt deleted file mode 100644 index d44fac1..0000000 --- a/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/ViewState.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.androidvip.sysctlgui.utils - -open class ViewState( - var data: List = listOf(), - var isLoading: Boolean = true, - var showEmptyState: Boolean = false, - var searchExpression: String = "", -) { - fun copyState( - data: List = this.data, - isLoading: Boolean = this.isLoading, - showEmptyState: Boolean = this.showEmptyState - ): ViewState { - return ViewState(data, isLoading, showEmptyState) - } -} diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 28f6e5e..56b4f2d 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -50,16 +50,17 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.preference) - // Room - implementation(libs.androidx.room.runtime) - implementation(libs.androidx.room.ktx) - ksp(libs.androidx.room.compiler) - implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.serialization.json) implementation(libs.koin) + implementation(libs.jsoup) + implementation(libs.bundles.ktor.clients) implementation(libs.bundles.libsu) - implementation(Google.gson) + + // Room + implementation(libs.androidx.room.runtime) + implementation(libs.androidx.room.ktx) + ksp(libs.androidx.room.compiler) testImplementation(libs.junit) } diff --git a/data/consumer-rules.pro b/data/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/Prefs.kt b/data/src/main/java/com/androidvip/sysctlgui/data/Prefs.kt new file mode 100644 index 0000000..94c6827 --- /dev/null +++ b/data/src/main/java/com/androidvip/sysctlgui/data/Prefs.kt @@ -0,0 +1,18 @@ +package com.androidvip.sysctlgui.data + +enum class Prefs(val key: String) { + ListFoldersFirst("list_folders_first"), + GuessInputType("guess_input_type"), + CommitMode("commit_mode"), + ALLOW_BLANK("allow_blank_values"), + UseBusybox("use_busybox"), + RunOnStartup("run_on_start_up"), + StartupDelay("startup_delay"), + ShowTaskerToast("show_tasker_toast"), + ForceDarkTheme("force_dark_theme"), + DynamicColors("dynamic_colors"), + AskedNotificationPermission("asked_notification_permission"), + UseOnlineDocs("use_online_docs"), + ContrastLevel("contrast_level"), + SearchHistory("search_history") +} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/datasource/JsonParamDataSource.kt b/data/src/main/java/com/androidvip/sysctlgui/data/datasource/JsonParamDataSource.kt deleted file mode 100644 index ec5e755..0000000 --- a/data/src/main/java/com/androidvip/sysctlgui/data/datasource/JsonParamDataSource.kt +++ /dev/null @@ -1,93 +0,0 @@ -package com.androidvip.sysctlgui.data.datasource - -import android.content.Context -import com.androidvip.sysctlgui.utils.Consts -import com.androidvip.sysctlgui.domain.datasource.LocalDataSourceContract -import com.androidvip.sysctlgui.domain.models.DomainKernelParam -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.io.File -import java.lang.reflect.Type - -class JsonParamDataSource( - private val context: Context, - private val dispatcher: CoroutineDispatcher = Dispatchers.IO -) : LocalDataSourceContract { - @Deprecated( - "JSON database is no longer updated.", - replaceWith = ReplaceWith("roomParamDatasource.add(param)"), - level = DeprecationLevel.ERROR - ) - override suspend fun add(param: DomainKernelParam, allowBlank: Boolean) { - throw UnsupportedOperationException("Adding json params is not supported") - } - - @Deprecated( - "JSON database is no longer updated.", - replaceWith = ReplaceWith("roomParamDatasource.addAll(param)"), - level = DeprecationLevel.ERROR - ) - override suspend fun addAll(params: List, allowBlank: Boolean){ - throw UnsupportedOperationException("Adding json params is not supported") - } - - @Deprecated( - "JSON database is no longer updated.", - replaceWith = ReplaceWith("roomParamDatasource.remove(param)"), - level = DeprecationLevel.ERROR - ) - override suspend fun remove(param: DomainKernelParam) { - throw UnsupportedOperationException("Deleting params is only supported in room database") - } - - @Deprecated( - "JSON database is no longer updated.", - replaceWith = ReplaceWith("roomParamDatasource.edit(param)"), - level = DeprecationLevel.ERROR - ) - override suspend fun edit( - param: DomainKernelParam, - allowBlank: Boolean - ) { - throw UnsupportedOperationException("Updating json params is no longer supported") - } - - override suspend fun clear() = withContext(dispatcher) { - arrayOf( - "favorites-params", - "user-params", - "tasker-params-${Consts.LIST_NUMBER_PRIMARY_TASKER}", - "tasker-params-${Consts.LIST_NUMBER_SECONDARY_TASKER}", - "tasker-params-${Consts.LIST_NUMBER_FAVORITES}", - "tasker-params-${Consts.LIST_NUMBER_APPLY_ON_BOOT}" - ).forEach { fileName -> - val paramFile = File(context.filesDir, fileName) - paramFile.writeText("[]") - } - } - - override suspend fun getData(): List = withContext(dispatcher) { - val gson = Gson() - val params = mutableListOf() - - arrayOf( - "favorites-params", - "user-params", - "tasker-params-${Consts.LIST_NUMBER_PRIMARY_TASKER}", - "tasker-params-${Consts.LIST_NUMBER_SECONDARY_TASKER}", - "tasker-params-${Consts.LIST_NUMBER_FAVORITES}", - "tasker-params-${Consts.LIST_NUMBER_APPLY_ON_BOOT}" - ).forEach { fileName -> - val paramsFile = File(context.filesDir, "$fileName.json") - if (!paramsFile.exists()) return@forEach - - val type: Type = object : TypeToken>() {}.type - params.addAll(gson.fromJson(paramsFile.readText(), type)) - } - - return@withContext params.distinct() - } -} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/datasource/RoomParamDataSource.kt b/data/src/main/java/com/androidvip/sysctlgui/data/datasource/RoomParamDataSource.kt deleted file mode 100644 index 0bd2815..0000000 --- a/data/src/main/java/com/androidvip/sysctlgui/data/datasource/RoomParamDataSource.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.androidvip.sysctlgui.data.datasource - -import com.androidvip.sysctlgui.data.db.ParamDao -import com.androidvip.sysctlgui.data.mapper.RoomParamMapper -import com.androidvip.sysctlgui.domain.datasource.LocalDataSourceContract -import com.androidvip.sysctlgui.domain.models.DomainKernelParam -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -class RoomParamDataSource( - private val paramDao: ParamDao, - private val dispatcher: CoroutineDispatcher = Dispatchers.IO -) : LocalDataSourceContract { - override suspend fun add( - param: DomainKernelParam, - allowBlank: Boolean - ) = withContext(dispatcher) { - if (!allowBlank) require(param.value.isNotBlank()) { - "Param contains blank value while ALLOW_BLANK is not active" - } - paramDao.insert(RoomParamMapper.unmap(param)) - } - - override suspend fun addAll( - params: List, - allowBlank: Boolean - ) = withContext(dispatcher) { - val filteredParams = if (allowBlank) { - params - } else params.filter { - it.value.isNotEmpty() - } - - paramDao.insert(*filteredParams.map { RoomParamMapper.unmap(it) }.toTypedArray()) - } - - override suspend fun remove(param: DomainKernelParam) = withContext(dispatcher) { - paramDao.delete(RoomParamMapper.unmap(param)) - } - - override suspend fun edit( - param: DomainKernelParam, - allowBlank: Boolean - ) = withContext(dispatcher) { - if (!allowBlank) require(param.value.isNotBlank()) { - "Param contains blank value while ALLOW_BLANK is not active" - } - paramDao.update(RoomParamMapper.unmap(param)) - } - - override suspend fun clear() = withContext(dispatcher) { - paramDao.clearTable() - } - - override suspend fun getData(): List = withContext(dispatcher) { - paramDao.getAll()?.map { - RoomParamMapper.map(it) - } ?: throw Exception("Failed to get params from the local database") - } -} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/datasource/RuntimeParamDataSource.kt b/data/src/main/java/com/androidvip/sysctlgui/data/datasource/RuntimeParamDataSource.kt deleted file mode 100644 index 39a0b2b..0000000 --- a/data/src/main/java/com/androidvip/sysctlgui/data/datasource/RuntimeParamDataSource.kt +++ /dev/null @@ -1,93 +0,0 @@ -package com.androidvip.sysctlgui.data.datasource - -import com.androidvip.sysctlgui.data.utils.RootUtils -import com.androidvip.sysctlgui.domain.datasource.RuntimeDataSourceContract -import com.androidvip.sysctlgui.domain.exceptions.ApplyValueException -import com.androidvip.sysctlgui.domain.exceptions.CommitModeException -import com.androidvip.sysctlgui.domain.models.DomainKernelParam -import java.io.File -import java.lang.IllegalArgumentException - -class RuntimeParamDataSource( - private val rootUtils: RootUtils -) : RuntimeDataSourceContract { - override suspend fun edit( - param: DomainKernelParam, - commitMode: String, - useBusybox: Boolean, - allowBlank: Boolean - ) { - val commitResult = commitChanges(param, commitMode, useBusybox, allowBlank) - - when { - commitMode == "sysctl" -> { - if (commitResult == "error" || !commitResult.contains(param.name)) { - throw CommitModeException("Value refused to apply. Try using 'echo' mode.") - } - } - commitResult == "error" -> { - throw ApplyValueException("Value refused to apply") - } - } - } - - override suspend fun getData(useBusybox: Boolean): List { - val command = if (useBusybox) "busybox sysctl -a" else "sysctl -a" - val lines = mutableListOf() - rootUtils.executeWithOutput(command) { lines += it } - - return lines.filter { - it.isValidSysctlOutput() - }.map { - // Expected output: grandparent.parent.name = value - val split = it.split("=") - split.first().trim() to split.last().trim() - }.mapIndexed { index, paramPair -> - DomainKernelParam( - id = index + 1, - name = paramPair.first, - value = paramPair.second - ).apply { - setPathFromName(paramPair.first) - } - } - } - - override suspend fun getParamsFromFiles(files: List): List { - return files.map { - it.absolutePath - }.mapIndexed { index, path -> - DomainKernelParam( - id = index + 1, - path = path - ).apply { - setNameFromPath(path) - value = rootUtils.executeWithOutput("cat $path", "") - } - } - } - - private suspend fun commitChanges( - param: DomainKernelParam, - commitMode: String, - useBusybox: Boolean, - allowBlank: Boolean - ): String { - if (!allowBlank && param.value.isBlank()) throw IllegalArgumentException( - "Param contains blank value while ALLOW_BLANK is not active" - ) - - val prefix = if (useBusybox) "busybox " else "" - val command = when (commitMode) { - "sysctl" -> "${prefix}sysctl -w ${param.name}=${param.value}" - "echo" -> "echo '${param.value}' > ${param.path}" - else -> "busybox sysctl -w ${param.name}=${param.value}" - } - - return rootUtils.executeWithOutput(command, "error") - } - - private fun String.isValidSysctlOutput(): Boolean { - return !contains("denied") && !startsWith("sysctl") && contains("=") - } -} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/db/ParamDao.kt b/data/src/main/java/com/androidvip/sysctlgui/data/db/ParamDao.kt index 6870cdb..968166c 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/db/ParamDao.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/db/ParamDao.kt @@ -2,25 +2,32 @@ package com.androidvip.sysctlgui.data.db import androidx.room.Dao import androidx.room.Delete -import androidx.room.Insert import androidx.room.Query -import androidx.room.Update -import com.androidvip.sysctlgui.data.models.RoomKernelParam +import androidx.room.Upsert +import com.androidvip.sysctlgui.data.models.KernelParamDTO +import com.androidvip.sysctlgui.data.models.PARAMS_TABLE_NAME +import kotlinx.coroutines.flow.Flow @Dao interface ParamDao { - @Query("SELECT * FROM roomKernelParam") - suspend fun getAll(): List? + @Query("SELECT * FROM $PARAMS_TABLE_NAME") + suspend fun getAll(): List - @Insert - suspend fun insert(vararg params: RoomKernelParam) + @Query("SELECT * FROM $PARAMS_TABLE_NAME") + fun getAllAsFlow(): Flow> - @Delete - suspend fun delete(param: RoomKernelParam) + @Query("SELECT * FROM $PARAMS_TABLE_NAME WHERE name = :name") + suspend fun getParamByName(name: String): KernelParamDTO? + + @Upsert + suspend fun upsert(param: KernelParamDTO): Long - @Update - suspend fun update(param: RoomKernelParam) + @Upsert + suspend fun upsertAll(params: List): List + + @Delete + suspend fun delete(param: KernelParamDTO): Int - @Query("DELETE FROM roomKernelParam") + @Query("DELETE FROM $PARAMS_TABLE_NAME") suspend fun clearTable() } diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/db/ParamDatabase.kt b/data/src/main/java/com/androidvip/sysctlgui/data/db/ParamDatabase.kt index ff2a1ed..7376bd3 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/db/ParamDatabase.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/db/ParamDatabase.kt @@ -2,9 +2,9 @@ package com.androidvip.sysctlgui.data.db import androidx.room.Database import androidx.room.RoomDatabase -import com.androidvip.sysctlgui.data.models.RoomKernelParam +import com.androidvip.sysctlgui.data.models.KernelParamDTO -@Database(entities = [RoomKernelParam::class], version = 1, exportSchema = false) +@Database(entities = [KernelParamDTO::class], version = 1, exportSchema = false) abstract class ParamDatabase : RoomDatabase() { abstract fun paramDao(): ParamDao } diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt b/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt index 9251daa..1cbd751 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt @@ -1,22 +1,44 @@ package com.androidvip.sysctlgui.data.di +import android.util.Log import androidx.preference.PreferenceManager -import com.androidvip.sysctlgui.data.datasource.JsonParamDataSource -import com.androidvip.sysctlgui.data.datasource.RoomParamDataSource -import com.androidvip.sysctlgui.data.datasource.RuntimeParamDataSource import com.androidvip.sysctlgui.data.db.ParamDatabase import com.androidvip.sysctlgui.data.db.ParamDatabaseManager import com.androidvip.sysctlgui.data.repository.AppPrefsImpl +import com.androidvip.sysctlgui.data.repository.AppSettingsRepositoryImpl +import com.androidvip.sysctlgui.data.repository.DocumentationRepositoryImpl import com.androidvip.sysctlgui.data.repository.ParamsRepositoryImpl +import com.androidvip.sysctlgui.data.repository.PresetRepositoryImpl +import com.androidvip.sysctlgui.data.repository.UserRepositoryImpl +import com.androidvip.sysctlgui.data.source.DocumentationDataSource +import com.androidvip.sysctlgui.data.source.OfflineDocumentationDataSource +import com.androidvip.sysctlgui.data.source.OnlineDocumentationDataSource +import com.androidvip.sysctlgui.data.utils.AndroidStringProvider +import com.androidvip.sysctlgui.data.utils.PresetsFileProcessor import com.androidvip.sysctlgui.data.utils.RootUtils +import com.androidvip.sysctlgui.domain.StringProvider import com.androidvip.sysctlgui.domain.repository.AppPrefs +import com.androidvip.sysctlgui.domain.repository.AppSettingsRepository +import com.androidvip.sysctlgui.domain.repository.DocumentationRepository import com.androidvip.sysctlgui.domain.repository.ParamsRepository -import kotlinx.coroutines.Dispatchers +import com.androidvip.sysctlgui.domain.repository.PresetRepository +import com.androidvip.sysctlgui.domain.repository.UserRepository +import io.ktor.client.HttpClient +import io.ktor.client.engine.android.Android +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger +import io.ktor.client.plugins.logging.Logging +import org.koin.android.ext.koin.androidApplication import org.koin.android.ext.koin.androidContext +import org.koin.core.module.dsl.factoryOf +import org.koin.core.qualifier.named +import org.koin.dsl.bind import org.koin.dsl.module val utilsModule = module { - factory { RootUtils(Dispatchers.Default) } + factoryOf(::RootUtils) + factory { PresetsFileProcessor(androidContext().contentResolver) } + factory { AndroidStringProvider(androidApplication()) } } val dbModule = module { @@ -25,17 +47,49 @@ val dbModule = module { } val repositoryModule = module { - factory { AppPrefsImpl(get()) } - single { ParamsRepositoryImpl(get(), get(), get(), get()) } + factoryOf(::AppPrefsImpl) bind AppPrefs::class + factoryOf(::ParamsRepositoryImpl) bind ParamsRepository::class + factoryOf(::PresetRepositoryImpl) bind PresetRepository::class + factoryOf(::AppSettingsRepositoryImpl) bind AppSettingsRepository::class + + single { UserRepositoryImpl(paramDao = get().paramDao()) } + + factory { + DocumentationRepositoryImpl( + offlineDataSource = get(named()), + onlineDataSource = get(named()) + ) + } } val dataSourceModule = module { - single { JsonParamDataSource(androidContext()) } - single { RuntimeParamDataSource(rootUtils = get()) } + factory(named()) { + OfflineDocumentationDataSource(androidContext()) + } + + factory(named()) { + OnlineDocumentationDataSource(get()) + } +} + +val networkModule = module { single { - val db: ParamDatabase = get() - RoomParamDataSource(db.paramDao()) + HttpClient(engineFactory = Android) { + engine { + connectTimeout = 5000 + socketTimeout = 5000 + } + + install(Logging) { + logger = object : Logger { + override fun log(message: String) { + Log.v("KtorHttpClient", message) + } + } + level = LogLevel.BODY + } + } } } -val dataModules = utilsModule + dbModule + repositoryModule + dataSourceModule +val dataModules = utilsModule + dbModule + repositoryModule + dataSourceModule + networkModule diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/mapper/Mapper.kt b/data/src/main/java/com/androidvip/sysctlgui/data/mapper/Mapper.kt deleted file mode 100644 index 0adeea9..0000000 --- a/data/src/main/java/com/androidvip/sysctlgui/data/mapper/Mapper.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.androidvip.sysctlgui.data.mapper - -interface Mapper { - fun map(from: F): T - fun unmap(from: T): F -} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/mapper/RoomParamMapper.kt b/data/src/main/java/com/androidvip/sysctlgui/data/mapper/RoomParamMapper.kt deleted file mode 100644 index 8047c20..0000000 --- a/data/src/main/java/com/androidvip/sysctlgui/data/mapper/RoomParamMapper.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.androidvip.sysctlgui.data.mapper - -import com.androidvip.sysctlgui.data.models.RoomKernelParam -import com.androidvip.sysctlgui.domain.models.DomainKernelParam - -object RoomParamMapper : Mapper { - override fun map(from: RoomKernelParam): DomainKernelParam = DomainKernelParam().apply { - id = from.id - name = from.name - path = from.path - value = from.value - favorite = from.favorite - taskerParam = from.taskerParam - taskerList = from.taskerList - } - - override fun unmap(from: DomainKernelParam): RoomKernelParam = RoomKernelParam().apply { - id = from.id - name = from.name - path = from.path - value = from.value - favorite = from.favorite - taskerParam = from.taskerParam - taskerList = from.taskerList - } -} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt b/data/src/main/java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt new file mode 100644 index 0000000..fd31d0e --- /dev/null +++ b/data/src/main/java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt @@ -0,0 +1,30 @@ +package com.androidvip.sysctlgui.data.models + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.androidvip.sysctlgui.data.utils.KernelParamSerializer +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.utils.Consts +import kotlinx.serialization.Serializable + +@Entity(tableName = PARAMS_TABLE_NAME) +@Serializable(with = KernelParamSerializer::class) +data class KernelParamDTO( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + @ColumnInfo(name = "name") + override val name: String = "", + @ColumnInfo(name = "path") + override val path: String = "", + @ColumnInfo(name = "value") + override val value: String = "", + @ColumnInfo(name = "favorite") + override val isFavorite: Boolean = false, + @ColumnInfo(name = "tasker_param") + override val isTaskerParam: Boolean = false, + @ColumnInfo(name = "tasker_list") + override val taskerList: Int = Consts.LIST_NUMBER_PRIMARY_TASKER +) : KernelParam(name, path, value, isFavorite, isTaskerParam, taskerList) + +internal const val PARAMS_TABLE_NAME = "roomKernelParam" diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/models/RoomKernelParam.kt b/data/src/main/java/com/androidvip/sysctlgui/data/models/RoomKernelParam.kt deleted file mode 100644 index 143e3a6..0000000 --- a/data/src/main/java/com/androidvip/sysctlgui/data/models/RoomKernelParam.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.androidvip.sysctlgui.data.models - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey -import com.androidvip.sysctlgui.utils.Consts - -@Entity -data class RoomKernelParam( - @PrimaryKey(autoGenerate = true) - var id: Int = 0, - @ColumnInfo(name = "name") - var name: String = "", - @ColumnInfo(name = "path") - var path: String = "", - @ColumnInfo(name = "value") - var value: String = "", - @ColumnInfo(name = "favorite") - var favorite: Boolean = false, - @ColumnInfo(name = "tasker_param") - var taskerParam: Boolean = false, - @ColumnInfo(name = "tasker_list") - var taskerList: Int = Consts.LIST_NUMBER_PRIMARY_TASKER -) diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppPrefsImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppPrefsImpl.kt index a48e7cc..4528454 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppPrefsImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppPrefsImpl.kt @@ -2,68 +2,90 @@ package com.androidvip.sysctlgui.data.repository import android.content.SharedPreferences import androidx.core.content.edit +import com.androidvip.sysctlgui.data.Prefs import com.androidvip.sysctlgui.domain.repository.AppPrefs -import com.androidvip.sysctlgui.utils.Consts +/** + * Implementation of [AppPrefs] that uses [SharedPreferences] to store and retrieve app preferences. + */ class AppPrefsImpl(private val prefs: SharedPreferences) : AppPrefs { override var listFoldersFirst: Boolean - get() = prefs.getBoolean(Consts.Prefs.LIST_FOLDERS_FIRST, true) + get() = prefs.getBoolean(Prefs.ListFoldersFirst.key, true) set(value) { - prefs.edit { putBoolean(Consts.Prefs.LIST_FOLDERS_FIRST, value) } + prefs.edit { putBoolean(Prefs.ListFoldersFirst.key, value) } } override var guessInputType: Boolean - get() = prefs.getBoolean(Consts.Prefs.GUESS_INPUT_TYPE, true) + get() = prefs.getBoolean(Prefs.GuessInputType.key, true) set(value) { - prefs.edit { putBoolean(Consts.Prefs.GUESS_INPUT_TYPE, value) } + prefs.edit { putBoolean(Prefs.GuessInputType.key, value) } } override var commitMode: String - get() = prefs.getString(Consts.Prefs.COMMIT_MODE, "sysctl") ?: "sysctl" + get() = prefs.getString(Prefs.CommitMode.key, "sysctl") ?: "sysctl" set(value) { - prefs.edit { putString(Consts.Prefs.COMMIT_MODE, value) } + prefs.edit { putString(Prefs.CommitMode.key, value) } } override var allowBlankValues: Boolean - get() = prefs.getBoolean(Consts.Prefs.ALLOW_BLANK, false) + get() = prefs.getBoolean(Prefs.ALLOW_BLANK.key, false) set(value) { - prefs.edit { putBoolean(Consts.Prefs.ALLOW_BLANK, value) } + prefs.edit { putBoolean(Prefs.ALLOW_BLANK.key, value) } } override var useBusybox: Boolean - get() = prefs.getBoolean(Consts.Prefs.USE_BUSYBOX, false) + get() = prefs.getBoolean(Prefs.UseBusybox.key, false) set(value) { - prefs.edit { putBoolean(Consts.Prefs.USE_BUSYBOX, value) } + prefs.edit { putBoolean(Prefs.UseBusybox.key, value) } } override var runOnStartUp: Boolean - get() = prefs.getBoolean(Consts.Prefs.RUN_ON_START_UP, false) + get() = prefs.getBoolean(Prefs.RunOnStartup.key, false) set(value) { - prefs.edit { putBoolean(Consts.Prefs.RUN_ON_START_UP, value) } + prefs.edit { putBoolean(Prefs.RunOnStartup.key, value) } } override var startUpDelay: Int - get() = prefs.getInt(Consts.Prefs.START_UP_DELAY, 0) + get() = prefs.getInt(Prefs.StartupDelay.key, 0) set(value) { - prefs.edit { putInt(Consts.Prefs.START_UP_DELAY, value) } + prefs.edit { putInt(Prefs.StartupDelay.key, value) } } override var showTaskerToast: Boolean - get() = prefs.getBoolean(Consts.Prefs.SHOW_TASKER_TOAST, true) + get() = prefs.getBoolean(Prefs.ShowTaskerToast.key, true) set(value) { - prefs.edit { putBoolean(Consts.Prefs.SHOW_TASKER_TOAST, value) } - } - override var migrationCompleted: Boolean - get() = prefs.getBoolean(Consts.Prefs.MIGRATION_COMPLETED, false) - set(value) { - prefs.edit { putBoolean(Consts.Prefs.MIGRATION_COMPLETED, value) } + prefs.edit { putBoolean(Prefs.ShowTaskerToast.key, value) } } override var forceDark: Boolean - get() = prefs.getBoolean(Consts.Prefs.FORCE_DARK_THEME, false) + get() = prefs.getBoolean(Prefs.ForceDarkTheme.key, false) set(value) { - prefs.edit { putBoolean(Consts.Prefs.FORCE_DARK_THEME, value) } + prefs.edit { putBoolean(Prefs.ForceDarkTheme.key, value) } } override var dynamicColors: Boolean - get() = prefs.getBoolean(Consts.Prefs.DYNAMIC_COLORS, false) + get() = prefs.getBoolean(Prefs.DynamicColors.key, false) set(value) { - prefs.edit { putBoolean(Consts.Prefs.DYNAMIC_COLORS, value) } + prefs.edit { putBoolean(Prefs.DynamicColors.key, value) } } override var askedForNotificationPermission: Boolean - get() = prefs.getBoolean(Consts.Prefs.ASKED_NOTIFICATION_PERMISSION, false) + get() = prefs.getBoolean(Prefs.AskedNotificationPermission.key, false) + set(value) { + prefs.edit { putBoolean(Prefs.AskedNotificationPermission.key, value) } + } + override var useOnlineDocs: Boolean + get() = prefs.getBoolean(Prefs.UseOnlineDocs.key, true) set(value) { - prefs.edit { putBoolean(Consts.Prefs.ASKED_NOTIFICATION_PERMISSION, value) } + prefs.edit { putBoolean(Prefs.UseOnlineDocs.key, value) } } + override var contrastLevel: Int + get() = prefs.getInt(Prefs.ContrastLevel.key, 1) + set(value) { + prefs.edit { putInt(Prefs.ContrastLevel.key, value) } + } + override val searchHistory: Set + get() = prefs.getStringSet(Prefs.SearchHistory.key, emptySet()) ?: emptySet() + + override fun addSearchToHistory(query: String) { + val currentHistory = searchHistory.toMutableSet() + currentHistory.add(query) + prefs.edit { putStringSet(Prefs.SearchHistory.key, currentHistory) } + } + + override fun removeSearchFromHistory(query: String) { + val currentHistory = searchHistory.toMutableSet() + currentHistory.remove(query) + prefs.edit { putStringSet(Prefs.SearchHistory.key, currentHistory) } + } } diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt new file mode 100644 index 0000000..386e7f8 --- /dev/null +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt @@ -0,0 +1,145 @@ +package com.androidvip.sysctlgui.data.repository + +import android.content.Context +import android.content.SharedPreferences +import android.os.Build +import com.androidvip.sysctlgui.data.Prefs +import com.androidvip.sysctlgui.data.utils.RootUtils +import com.androidvip.sysctlgui.domain.enums.CommitMode +import com.androidvip.sysctlgui.domain.enums.SettingItemType +import com.androidvip.sysctlgui.domain.models.AppSetting +import com.androidvip.sysctlgui.domain.repository.AppSettingsRepository +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext + +class AppSettingsRepositoryImpl( + private val context: Context, + private val sharedPreferences: SharedPreferences, + private val rootUtils: RootUtils, + private val ioContext: CoroutineContext +) : AppSettingsRepository { + override suspend fun getAppSettings(): List> = withContext(ioContext) { + val usingDynamicColors = sharedPreferences.getBoolean(Prefs.DynamicColors.key, false) + val supportsDynamicColors = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + + listOf( + /////////// GENERAL SETTINGS //////////// + AppSetting( + key = Prefs.ListFoldersFirst.key, + value = sharedPreferences.getBoolean(Prefs.ListFoldersFirst.key, true), + category = "General", + title = "List folders first", + description = "List folders first when using the kernel parameter browser option", + type = SettingItemType.Switch, + ), + AppSetting( + key = Prefs.GuessInputType.key, + value = sharedPreferences.getBoolean(Prefs.GuessInputType.key, true), + category = "General", + title = "Guess input type", + description = "Try to set the best input type for the keyboard based on the value of the parameter", + type = SettingItemType.Switch, + ), + AppSetting( + key = Prefs.UseOnlineDocs.key, + value = sharedPreferences.getBoolean(Prefs.UseOnlineDocs.key, true), + category = "General", + title = "Use online docs", + description = "Try to use online documentation when displaying parameter descriptions", + type = SettingItemType.Switch, + ), + + /////////// THEME SETTINGS //////////// + + AppSetting( + key = Prefs.ForceDarkTheme.key, + value = sharedPreferences.getBoolean(Prefs.ForceDarkTheme.key, false), + category = "Theme", + title = "Force Dark", + description = "Force dark theme when available", + type = SettingItemType.Switch, + ), + AppSetting( + key = Prefs.DynamicColors.key, + value = usingDynamicColors, + enabled = supportsDynamicColors, + category = "Theme", + title = "Dynamic Colors", + description = "Use dynamic colors when available", + type = SettingItemType.Switch, + ), + AppSetting( + key = Prefs.ContrastLevel.key, + enabled = !usingDynamicColors, + value = sharedPreferences.getInt(Prefs.ContrastLevel.key, 1), + category = "Theme", + title = "Contrast level", + description = "Contrast level for the theme colors", + type = SettingItemType.Slider, + values = listOf(1, 2, 3), + ), + + /////////// COMMIT SETTINGS //////////// + + AppSetting( + key = Prefs.CommitMode.key, + value = sharedPreferences.getString( + Prefs.CommitMode.key, + CommitMode.SYSCTL.name.lowercase() + ) ?: "sysctl", + category = "Operations", + title = "Commit mode", + description = "Command used when applying the parameter value", + type = SettingItemType.List, + values = listOf( + CommitMode.SYSCTL.name.lowercase(), + CommitMode.ECHO.name.lowercase(), + ) + ), + AppSetting( + key = Prefs.UseBusybox.key, + value = sharedPreferences.getBoolean(Prefs.UseBusybox.key, false), + enabled = rootUtils.isBusyboxAvailable(), + category = "Operations", + title = "Use busybox", + description = "Use busybox to execute commands", + type = SettingItemType.Switch, + ), + AppSetting( + key = Prefs.ALLOW_BLANK.key, + value = sharedPreferences.getBoolean(Prefs.ALLOW_BLANK.key, false), + category = "Operations", + title = "Allow blank values", + type = SettingItemType.Switch, + ), + + /////////// STARTUP SETTINGS //////////// + + AppSetting( + key = Prefs.RunOnStartup.key, + value = sharedPreferences.getBoolean(Prefs.RunOnStartup.key, false), + category = "Startup", + title = "Run on startup", + description = "Allow the application to apply parameters on startup", + type = SettingItemType.Switch, + ), + AppSetting( + key = Prefs.StartupDelay.key, + value = sharedPreferences.getInt(Prefs.StartupDelay.key, 0), + category = "Startup", + title = "Startup delay", + description = "Delay in seconds before applying parameters on startup", + type = SettingItemType.Slider, + values = (0..10).toList(), + ), + AppSetting( + key = "", + value = Unit, + category = "Startup", + title = "Manage parameters", + description = "Manage the parameters that will be applied at startup", + type = SettingItemType.Text, + ) + ) + } +} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt new file mode 100644 index 0000000..09ae565 --- /dev/null +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt @@ -0,0 +1,34 @@ +package com.androidvip.sysctlgui.data.repository + +import com.androidvip.sysctlgui.data.source.DocumentationDataSource +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.models.ParamDocumentation +import com.androidvip.sysctlgui.domain.repository.AppPrefs +import com.androidvip.sysctlgui.domain.repository.DocumentationRepository + +/** + * Repository for fetching documentation for kernel parameters. + * + * This repository can fetch documentation from either an online or offline data source, + * depending on the user's preference set in [AppPrefs]. + * + * @property offlineDataSource The data source for fetching documentation offline. + * @property onlineDataSource The data source for fetching documentation online. + * @property appPrefs The application preferences, used to determine whether to use online or + * offline documentation. + */ +class DocumentationRepositoryImpl( + private val offlineDataSource: DocumentationDataSource, + private val onlineDataSource: DocumentationDataSource +) : DocumentationRepository { + override suspend fun getDocumentation( + param: KernelParam, + online: Boolean + ): ParamDocumentation? { + return if (online) { + onlineDataSource.getDocumentation(param) + } else { + offlineDataSource.getDocumentation(param) + } + } +} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/ParamsRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/ParamsRepositoryImpl.kt index 5400846..768c335 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/ParamsRepositoryImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/ParamsRepositoryImpl.kt @@ -1,207 +1,133 @@ package com.androidvip.sysctlgui.data.repository -import com.androidvip.sysctlgui.data.datasource.JsonParamDataSource -import com.androidvip.sysctlgui.data.datasource.RoomParamDataSource -import com.androidvip.sysctlgui.data.datasource.RuntimeParamDataSource -import com.androidvip.sysctlgui.domain.exceptions.EmptyFileException -import com.androidvip.sysctlgui.domain.exceptions.MalformedLineException -import com.androidvip.sysctlgui.domain.models.DomainKernelParam +import android.util.Log +import com.androidvip.sysctlgui.data.utils.RootUtils +import com.androidvip.sysctlgui.domain.enums.CommitMode +import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.domain.repository.ParamsRepository -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken +import com.androidvip.sysctlgui.utils.isValidSysctlLine +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.single +import kotlinx.coroutines.flow.toList import java.io.File -import java.io.FileDescriptor -import java.io.FileOutputStream -import java.io.InputStream -import java.lang.reflect.Type -import kotlin.coroutines.CoroutineContext class ParamsRepositoryImpl( - private val jsonParamDataSource: JsonParamDataSource, - private val roomParamDataSource: RoomParamDataSource, - private val runtimeParamDataSource: RuntimeParamDataSource, - private val changeListener: ChangeListener?, - private val ioContext: CoroutineContext = Dispatchers.IO, - private val workerContext: CoroutineContext = Dispatchers.Default + private val rootUtils: RootUtils, + private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : ParamsRepository { - - override suspend fun getUserParams(): List = withContext(ioContext) { - return@withContext roomParamDataSource.getData() - } - - override suspend fun getJsonParams(): List = withContext(ioContext) { - return@withContext jsonParamDataSource.getData() - } - - override suspend fun getRuntimeParams( - useBusybox: Boolean - ): List = withContext(workerContext) { - val localParams = getUserParams() - val runtimeParams = runtimeParamDataSource.getData(useBusybox) - - return@withContext runtimeParams.onEach { runtimeParam -> - runtimeParam.updateParamWithLocalData(localParams) - } - } - - override suspend fun getParamsFromFiles( - files: List - ): List = withContext(ioContext) { - val localParams = getUserParams() - val fileParams = runtimeParamDataSource.getParamsFromFiles(files) - - return@withContext fileParams.onEach { runtimeParam -> - runtimeParam.updateParamWithLocalData(localParams) - } - } - - override suspend fun applyParam( - param: DomainKernelParam, - commitMode: String, + override fun getRuntimeParams( useBusybox: Boolean, - allowBlank: Boolean - ) = withContext(workerContext) { - runtimeParamDataSource.edit(param, commitMode, useBusybox, allowBlank).also { - changeListener?.onChange() - } - } - - override suspend fun updateUserParam( - param: DomainKernelParam, - allowBlank: Boolean - ) = withContext(ioContext) { - val storedParam = getUserParams().find { - it.name == param.name - } ?: return@withContext addUserParam(param, allowBlank) - - param.id = storedParam.id - return@withContext roomParamDataSource.edit(param, allowBlank).also { - changeListener?.onChange() - } - } - - override suspend fun addUserParam( - param: DomainKernelParam, - allowBlank: Boolean - ) = withContext(ioContext) { - return@withContext roomParamDataSource.add(param, allowBlank).also { - changeListener?.onChange() - } - } - - override suspend fun addUserParams( - params: List, - allowBlank: Boolean - ) = withContext(ioContext) { - return@withContext roomParamDataSource.addAll(params, allowBlank).also { - changeListener?.onChange() - } - } - - override suspend fun removeUserParam(param: DomainKernelParam) = withContext(ioContext) { - return@withContext roomParamDataSource.remove(param).also { - changeListener?.onChange() - } - } - - override suspend fun clearUserParams() = withContext(ioContext) { - return@withContext roomParamDataSource.clear().also { - jsonParamDataSource.clear() - changeListener?.onChange() - } - } - - override suspend fun performDatabaseMigration() = withContext(ioContext) { - val jsonParams = getJsonParams() - - return@withContext roomParamDataSource.addAll(jsonParams, true).also { - changeListener?.onChange() - } - } - - override suspend fun importParamsFromJson( - stream: InputStream - ): List = withContext(ioContext) { - if (stream.available() == 0) throw EmptyFileException() - - val rawText = buildString { - stream.bufferedReader().use { reader -> - reader.forEachLine { line -> - append(line) - } + userParams: List + ): Flow> = flow { + val command = if (useBusybox) BUSYBOX_SYSCTL_GET_ALL_COMMAND else SYSCTL_GET_ALL_COMMAND + val paramsList = rootUtils.executeCommandAndStreamOutput(command) + .filter { line -> line.isValidSysctlOutput() } + .mapNotNull { line -> + // Expected output: "grandparent.parent.name = value" + val parts = line.split("=", limit = 2) + val paramName = parts.first().trim() + val paramValue = if (parts.size > 1) parts.last().trim() else "" + runCatching { + KernelParam.createFromName( + name = paramName, + value = paramValue, + isFavorite = userParams.any { it.name == paramName } + ) + }.getOrNull() } - } - val type: Type = object : TypeToken>() {}.type - return@withContext Gson().fromJson(rawText, type) - } + .toList() - override suspend fun importParamsFromConf( - stream: InputStream - ): List = withContext(ioContext) { - fun String.validConfLine() = !startsWith("#") && !startsWith(";") && isNotEmpty() - val readParams = mutableListOf() + emit(paramsList) + }.flowOn(ioDispatcher) - if (stream.available() == 0) throw EmptyFileException() + override suspend fun getRuntimeParam(paramName: String, useBusybox: Boolean): KernelParam? { + val command = String.format( + SYSCTL_GET_PARAM_COMMAND_FORMAT, + if (useBusybox) BUSYBOX_PREFIX else "", + paramName + ) - var cont = 0 - stream.bufferedReader().forEachLine { line -> - if (line.validConfLine()) runCatching { - readParams.add( - DomainKernelParam( - id = ++cont, - name = line.split("=").first().trim(), - value = line.split("=")[1].trim() - ).apply { - setPathFromName(this.name) - } - ) - }.onFailure { - throw MalformedLineException() - } - } - return@withContext readParams - } + val paramValue = runCatching { + rootUtils.executeCommandAndStreamOutput(command).single() + }.getOrNull() ?: return null - override suspend fun exportParams( - params: List, - fileDescriptor: FileDescriptor - ) = withContext(ioContext) { - return@withContext FileOutputStream(fileDescriptor).use { stream -> - stream.write(Gson().toJson(params).toByteArray()) - } + return KernelParam.createFromName( + name = paramName, + value = paramValue + ) } - override suspend fun backupParams( - params: List, - fileDescriptor: FileDescriptor - ) = withContext(ioContext) { - val rawText = buildString { - params.forEach { param -> - appendLine(param.toString()) - } - } - - return@withContext FileOutputStream(fileDescriptor).use { stream -> - stream.write(rawText.toByteArray()) + override suspend fun setRuntimeParam( + param: KernelParam, + commitMode: CommitMode, + useBusybox: Boolean + ): String { + val command = when (commitMode) { + CommitMode.SYSCTL -> String.format( + SYSCTL_SET_PARAM_COMMAND_FORMAT, + if (useBusybox) BUSYBOX_PREFIX else "", + param.name, + param.value + ) + + CommitMode.ECHO -> String.format(ECHO_SET_PARAM_COMMAND_FORMAT, param.value, param.path) } - } - private fun DomainKernelParam.updateParamWithLocalData( - localParams: List - ): DomainKernelParam { - return apply { - favorite = localParams.firstOrNull { roomParam -> - (roomParam.name == name) && roomParam.favorite - } != null - taskerParam = localParams.firstOrNull { roomParam -> - (roomParam.name == name) && roomParam.taskerParam - } != null + val output = rootUtils.executeCommandAndStreamOutput(command).toList() + return output.joinToString("\n") + } + + /** + * Reads kernel parameters from a list of files. + * The parameter name is derived from the file path. + * + * @param files A list of [File] objects representing the kernel parameter files. + * @return A [Flow] emitting a list of [KernelParam] objects. + * Returns an empty list if no files are provided or if errors occur during processing. + * Emits null for files that could not be processed. + */ + override fun getParamsFromFiles(files: List): Flow> = flow { + val params = files.mapNotNull { file -> + try { + val path = file.absolutePath + val value = rootUtils.executeCommandAndStreamOutput( + command = String.format(CAT_COMMAND_FORMAT, path) + ).toList().joinToString("\n") + KernelParam.createFromPath(path, value) + } catch (e: Exception) { + Log.e(TAG, "Failed to process file: ${file.path}", e) + null + } } - } - - interface ChangeListener { - fun onChange() + emit(params) + }.flowOn(ioDispatcher) + + override fun getParamsFromPath(path: String): Flow> { + val files = File(path).listFiles()?.toList() ?: emptyList() + return getParamsFromFiles(files) + } + + private fun String.isValidSysctlOutput(): Boolean { + return isValidSysctlLine() && + !this.contains("denied", ignoreCase = true) && + !this.startsWith("sysctl") + } + + companion object { + private const val BUSYBOX_PREFIX = "busybox " + private const val SYSCTL_GET_ALL_COMMAND = "sysctl -a" + private const val BUSYBOX_SYSCTL_GET_ALL_COMMAND = "$BUSYBOX_PREFIX$SYSCTL_GET_ALL_COMMAND" + private const val SYSCTL_GET_PARAM_COMMAND_FORMAT = "%ssysctl -n %s" // prefix, name + private const val SYSCTL_SET_PARAM_COMMAND_FORMAT = + "%ssysctl -w %s=%s" // prefix, name, value + private const val ECHO_SET_PARAM_COMMAND_FORMAT = "echo '%s' > %s" // value, path + private const val CAT_COMMAND_FORMAT = "cat %s" // path + private const val TAG = "ParamsRepositoryImpl" } } diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/PresetRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/PresetRepositoryImpl.kt new file mode 100644 index 0000000..4d6a735 --- /dev/null +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/PresetRepositoryImpl.kt @@ -0,0 +1,70 @@ +package com.androidvip.sysctlgui.data.repository + +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.exceptions.EmptyFileException +import com.androidvip.sysctlgui.domain.exceptions.MalformedLineException +import com.androidvip.sysctlgui.domain.repository.PresetRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.json.Json +import java.io.FileDescriptor +import java.io.FileOutputStream +import java.io.InputStream +import kotlin.coroutines.CoroutineContext + +class PresetRepositoryImpl( + private val ioCoroutineContext: CoroutineContext = Dispatchers.IO +) : PresetRepository { + override suspend fun readPreset( + stream: InputStream + ): List = withContext(ioCoroutineContext) { + if (stream.available() == 0) throw EmptyFileException() + + return@withContext stream.bufferedReader().use { reader -> + reader.lineSequence() + .filter { it.validConfLine() } + .map { line -> + val parts = line.split("=", limit = 2) + if (parts.size == 2) { + val name = parts[0].trim() + val value = parts[1].trim() + runCatching { + KernelParam.createFromName(name = name, value = value) + }.getOrElse { + throw MalformedLineException("Invalid format for line: $line", it) + } + } else { + throw MalformedLineException("Line doesn't contain '=' separator: $line") + } + }.toList() + } + } + + override suspend fun exportToPreset(params: List, fileDescriptor: FileDescriptor) { + val content = params.joinToString(separator = "\n") { param -> + "${param.name}=${param.value}" + } + + writeContentToFileDescriptor(fileDescriptor, content) + } + + override suspend fun backupParams(params: List, fileDescriptor: FileDescriptor) { + val content = Json.encodeToString(params) + writeContentToFileDescriptor(fileDescriptor, content) + } + + private suspend fun writeContentToFileDescriptor( + fileDescriptor: FileDescriptor, + content: String + ) = withContext(ioCoroutineContext) { + FileOutputStream(fileDescriptor).use { fileOutputStream -> + fileOutputStream.writer(Charsets.UTF_8).buffered().use { writer -> + writer.write(content) + } + } + } +} + +private fun String.validConfLine(): Boolean { + return !startsWith("#") && !startsWith(";") && isNotEmpty() +} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt new file mode 100644 index 0000000..3dbb29b --- /dev/null +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt @@ -0,0 +1,45 @@ +package com.androidvip.sysctlgui.data.repository + +import com.androidvip.sysctlgui.data.db.ParamDao +import com.androidvip.sysctlgui.data.models.KernelParamDTO +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.repository.UserRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext + +/** + * Implementation of [UserRepository] that uses a [ParamDao] to store and retrieve user parameters. + * + * @param paramDao The DAO used to interact with the database. + * @param coroutineContext The coroutine context to use for database operations. Defaults to [Dispatchers.IO]. + */ +class UserRepositoryImpl( + private val paramDao: ParamDao, + private val coroutineContext: CoroutineContext = Dispatchers.IO +) : UserRepository { + override val userParams: Flow> + get() = paramDao.getAllAsFlow() + + override suspend fun getParamByName(name: String) = withContext(coroutineContext) { + paramDao.getParamByName(name) + } + + override suspend fun upsertUserParam(param: KernelParam) = withContext(coroutineContext) { + paramDao.upsert(param as KernelParamDTO) + } + + override suspend fun upsertUserParams(params: List) = + withContext(coroutineContext) { + paramDao.upsertAll(params.map { it as KernelParamDTO }) + } + + override suspend fun removeUserParam(param: KernelParam) = withContext(coroutineContext) { + paramDao.delete(param as KernelParamDTO) + } + + override suspend fun clearUserParams() = withContext(coroutineContext) { + paramDao.clearTable() + } +} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/source/DocumentationDataSource.kt b/data/src/main/java/com/androidvip/sysctlgui/data/source/DocumentationDataSource.kt new file mode 100644 index 0000000..de5672d --- /dev/null +++ b/data/src/main/java/com/androidvip/sysctlgui/data/source/DocumentationDataSource.kt @@ -0,0 +1,20 @@ +package com.androidvip.sysctlgui.data.source + +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.models.ParamDocumentation + + +/** + * Data source interface for fetching documentation for kernel parameters. + * This interface defines the contract for any class that provides access + * to kernel parameter documentation. + */ +fun interface DocumentationDataSource { + /** + * Retrieves documentation for a given kernel parameter. + * + * @param param The kernel parameter for which to fetch documentation. + * @return The documentation if found, null otherwise. + */ + suspend fun getDocumentation(param: KernelParam): ParamDocumentation? +} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/source/OfflineDocumentationDataSource.kt b/data/src/main/java/com/androidvip/sysctlgui/data/source/OfflineDocumentationDataSource.kt new file mode 100644 index 0000000..82d88c4 --- /dev/null +++ b/data/src/main/java/com/androidvip/sysctlgui/data/source/OfflineDocumentationDataSource.kt @@ -0,0 +1,122 @@ +package com.androidvip.sysctlgui.data.source + +import android.annotation.SuppressLint +import android.content.Context +import com.androidvip.sysctlgui.data.R +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.models.ParamDocumentation +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.InputStream +import kotlin.coroutines.CoroutineContext + +/** + * Fetches documentation from offline sources. + * + * It first tries to find a string resource matching the parameter name. + * If not found, it attempts to extract documentation from raw text files + * bundled with the application, categorized by the parameter's path. + * + * @property context The application context, used to access resources. + * @property coroutineContext The coroutine context on which to perform operations. + */ +class OfflineDocumentationDataSource( + private val context: Context, + private val coroutineContext: CoroutineContext = Dispatchers.IO +) : DocumentationDataSource { + + /** + * Retrieves documentation for a given kernel parameter. + * + * This function attempts to find documentation in the following order: + * 1. **String Resource:** Checks for a string resource matching the parameter's name (normalized by replacing hyphens with underscores). + * 2. **Raw Text File:** If no string resource is found, it tries to locate documentation within a raw text file based on the parameter's path. + * - The path is expected to be in the format `/proc/sys/category/...`. + * - The "category" segment determines which raw file to read (e.g., `abi.txt`, `fs.txt`). + * - Inside the raw file, it searches for a section matching the parameter's name, delimited by "====" lines. + * + * @param param The [KernelParam] for which to retrieve documentation. + * @return A [String] containing the documentation if found, or `null` otherwise. + */ + @SuppressLint("DiscouragedApi") // Resource name is determined dynamically from name. + override suspend fun getDocumentation( + param: KernelParam) + : ParamDocumentation? = withContext(coroutineContext) { + val paramName = param.lastNameSegment + val resources = context.resources + + val normalizedResourceName = paramName.replace("-", "_") + val resId = resources.getIdentifier( + normalizedResourceName, + "string", + context.packageName + ) + val stringRes = runCatching { context.getString(resId) }.getOrNull() + + // Prefer the documented string resource + if (stringRes != null) return@withContext ParamDocumentation( + title = param.name, + documentationText = stringRes + ) + + // Assuming path is like /proc/sys/category/further/path + val pathSegments = param.path.trim('/').split('/') + if (pathSegments.size < MIN_PATH_SEGMENTS_FOR_CATEGORY) return@withContext null + + // Validate fixed parts like "proc" and "sys" + if (pathSegments.getOrNull(0) != "proc" || pathSegments.getOrNull(1) != "sys") { + // We did our best + return@withContext null + } + + // Index 2 after splitting by '/' and removing leading '/' + val category = pathSegments.getOrNull(2) + val rawInputStream: InputStream? = when (category) { + "abi" -> resources.openRawResource(R.raw.abi) + "fs" -> resources.openRawResource(R.raw.fs) + "kernel" -> resources.openRawResource(R.raw.kernel) + "net" -> resources.openRawResource(R.raw.net) + "vm" -> resources.openRawResource(R.raw.vm) + else -> null + } + + val documentation = rawInputStream?.use { inputStream -> + inputStream.bufferedReader().use { reader -> + reader.readText() + } + } + if (documentation.isNullOrEmpty()) return@withContext null + + /* + Trying to match: + + =============== + + paramName + + the <== + actual <== + documentation <== + + =============== + */ + val info: String? = runCatching { + documentation + .split("=+".toRegex()) + .last { it.contains("$paramName\n") } + .split("$paramName\n") + .last() + }.getOrNull() + + val documentationText = info.takeIf { it.isNullOrEmpty().not() } + if (documentationText == null) return@withContext null + return@withContext ParamDocumentation( + title = param.name, + documentationText = documentationText + ) + } + + companion object { + private const val MIN_PATH_SEGMENTS_FOR_CATEGORY = 4 + } +} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/source/OnlineDocumentationDataSource.kt b/data/src/main/java/com/androidvip/sysctlgui/data/source/OnlineDocumentationDataSource.kt new file mode 100644 index 0000000..c79b528 --- /dev/null +++ b/data/src/main/java/com/androidvip/sysctlgui/data/source/OnlineDocumentationDataSource.kt @@ -0,0 +1,100 @@ +package com.androidvip.sysctlgui.data.source + +import android.util.Log +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.models.ParamDocumentation +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.isSuccess +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jsoup.Jsoup +import kotlin.coroutines.CoroutineContext + + +class OnlineDocumentationDataSource( + private val client: HttpClient, + private val coroutineContext: CoroutineContext = Dispatchers.IO +) : DocumentationDataSource { + + /** + * Fetches the documentation for a given kernel parameter. + * + * This function constructs the documentation URL based on the parameter's name, + * retrieves the HTML content from that URL using Ktor and extracts the + * relevant documentation text using Jsoup. + * + * @param param The [KernelParam] for which to fetch documentation. + * @return The documentation text as a [ParamDocumentation], or `null` if the + * documentation could not be found or an error occurred. + */ + override suspend fun getDocumentation( + param: KernelParam + ): ParamDocumentation? = withContext(coroutineContext) { + val url = getDocumentationUrl(param) + + return@withContext runCatching { + val response = client.get(urlString = url) + + if (!response.status.isSuccess()) { + Log.w( + "OnlineDocRepo", + "Failed to fetch docs from $url. Status: ${response.status}" + ) + return@withContext null + } + + val html = response.bodyAsText() + val document = Jsoup.parse(html) + val htmlElementId = param.lastNameSegment.replace('_', '-') + val elements = document.select("section#$htmlElementId p") + + if (elements.isEmpty()) { + Log.w( + "OnlineDocRepo", + "No documentation found for ${param.name} with id $htmlElementId on $url" + ) + return@withContext null + } + + return@withContext ParamDocumentation( + title = param.name, + documentationText = elements.text(), + documentationHtml = elements.html().optimizedDocumentationHtml(), + url = url + ) + }.getOrElse { + Log.w("OnlineDocRepo", "Failed to fetch docs from $url", it) + return@withContext null + } + } + + private fun getDocumentationUrl(param: KernelParam): String { + val configName = param.groupName + return "${DOC_BASE_URL}$configName.html#${param.name}" + } + + /** + * Optimizes HTML documentation for display. + * + * This function performs a series of replacements on the input HTML string + * to try and improve its rendering in a basic HTML text renderer, such as Android's TextView. + * @return The optimized HTML string. + */ + private fun String.optimizedDocumentationHtml(): String { + return this.trimIndent() + .replace("
", "")
+            .replace("
", "") // For "code" blocks + .replace("", "") + .replace("", "") // For code tags + .replace("
  • ", "

  • ") + .replace("

  • ", "") // For spaced bullet points + .replace("

    ", "

    ") // For line breaks in paragraphs + .removeSuffix("
    ") // Remove the last line break + } + + companion object { + internal const val DOC_BASE_URL = "https://docs.kernel.org/admin-guide/sysctl/" + } +} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/utils/AndroidStringProvider.kt b/data/src/main/java/com/androidvip/sysctlgui/data/utils/AndroidStringProvider.kt new file mode 100644 index 0000000..666fc53 --- /dev/null +++ b/data/src/main/java/com/androidvip/sysctlgui/data/utils/AndroidStringProvider.kt @@ -0,0 +1,11 @@ +package com.androidvip.sysctlgui.data.utils + +import android.app.Application +import com.androidvip.sysctlgui.domain.StringProvider + +class AndroidStringProvider(private val application: Application) : StringProvider { + override fun getString(resId: Int): String = application.getString(resId) + override fun getString(resId: Int, vararg formatArgs: Any): String { + return application.getString(resId, *formatArgs) + } +} diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/utils/KernelParamSerializer.kt b/data/src/main/java/com/androidvip/sysctlgui/data/utils/KernelParamSerializer.kt new file mode 100644 index 0000000..0357c1b --- /dev/null +++ b/data/src/main/java/com/androidvip/sysctlgui/data/utils/KernelParamSerializer.kt @@ -0,0 +1,65 @@ +package com.androidvip.sysctlgui.data.utils + +import com.androidvip.sysctlgui.data.models.KernelParamDTO +import com.androidvip.sysctlgui.utils.Consts +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor +import kotlinx.serialization.descriptors.element +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure +import kotlinx.serialization.encoding.encodeStructure + +object KernelParamSerializer : KSerializer { + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("KernelParamDTO") { + element("id") + element("name") + element("path") + element("value") + element("isFavorite") + element("isTaskerParam") + element("taskerList") + } + + override fun serialize(encoder: Encoder, value: KernelParamDTO) { + encoder.encodeStructure(descriptor) { + encodeIntElement(descriptor, 0, value.id) + encodeStringElement(descriptor, 1, value.name) + encodeStringElement(descriptor, 2, value.path) + encodeStringElement(descriptor, 3, value.value) + encodeBooleanElement(descriptor, 4, value.isFavorite) + encodeBooleanElement(descriptor, 5, value.isTaskerParam) + encodeIntElement(descriptor, 6, value.taskerList) + } + } + + override fun deserialize(decoder: Decoder): KernelParamDTO { + return decoder.decodeStructure(descriptor) { + var id = 0 + var name = "" + var path = "" + var value = "" + var isFavorite = false + var isTaskerParam = false + var taskerList = Consts.LIST_NUMBER_PRIMARY_TASKER // Default + + while (true) { + when (val index = decodeElementIndex(descriptor)) { + 0 -> id = decodeIntElement(descriptor, 0) + 1 -> name = decodeStringElement(descriptor, 1) + 2 -> path = decodeStringElement(descriptor, 2) + 3 -> value = decodeStringElement(descriptor, 3) + 4 -> isFavorite = decodeBooleanElement(descriptor, 4) + 5 -> isTaskerParam = decodeBooleanElement(descriptor, 5) + 6 -> taskerList = decodeIntElement(descriptor, 6) + CompositeDecoder.Companion.DECODE_DONE -> break + else -> throw SerializationException("Unknown index $index") + } + } + KernelParamDTO(id, name, path, value, isFavorite, isTaskerParam, taskerList) + } + } +} \ No newline at end of file diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/utils/PresetsFileProcessor.kt b/data/src/main/java/com/androidvip/sysctlgui/data/utils/PresetsFileProcessor.kt new file mode 100644 index 0000000..696b4e1 --- /dev/null +++ b/data/src/main/java/com/androidvip/sysctlgui/data/utils/PresetsFileProcessor.kt @@ -0,0 +1,52 @@ +package com.androidvip.sysctlgui.data.utils + +import android.content.ContentResolver +import android.net.Uri +import android.util.Log +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.utils.isValidSysctlLine +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.IOException + +class PresetsFileProcessor( + private val contentResolver: ContentResolver, + private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO +) { + suspend fun getKernelParamsFromUri( + uri: Uri + ): List = withContext(ioDispatcher) { + contentResolver.openInputStream(uri)?.use { inputStream -> + val lines = inputStream.bufferedReader().readLines() + lines.mapNotNull { line -> + if (line.isValidSysctlLine()) { + runCatching { + KernelParam.Companion.createFromName( + name = line.substringBefore('=').trim(), + value = line.substringAfter('=').trim(), + isFavorite = true + ) + }.getOrNull() + } else { + Log.w("PresetsFileProcessor", "Invalid line: $line") + null + } + } + } ?: throw IOException("Failed to open input stream for URI: $uri") + } + + suspend fun backupParamsToUri( + uri: Uri, + params: List + ) = withContext(ioDispatcher) { + val fileContent = params.joinToString("\n") { "${it.name}=${it.value}" } + + contentResolver.openOutputStream(uri)?.use { outputStream -> + outputStream.bufferedWriter().use { writer -> + writer.write(fileContent) + writer.flush() + } + } ?: throw IOException("Failed to open output stream for URI: $uri") + } +} \ No newline at end of file diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/utils/RootUtils.kt b/data/src/main/java/com/androidvip/sysctlgui/data/utils/RootUtils.kt index 901304a..93bcfb1 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/utils/RootUtils.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/utils/RootUtils.kt @@ -1,42 +1,49 @@ package com.androidvip.sysctlgui.data.utils +import android.util.Log +import com.androidvip.sysctlgui.domain.exceptions.ShellCommandException import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.ShellUtils import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.withContext -class RootUtils(private val dispatcher: CoroutineDispatcher = Dispatchers.Default) { +class RootUtils(private val shellDispatcher: CoroutineDispatcher = Dispatchers.Default) { + suspend fun getRootShell(): Shell? = withContext(shellDispatcher) { + Shell.getShell().takeIf { it.isRoot } + } + + suspend fun isRootAvailable(): Boolean = withContext(shellDispatcher) { + Shell.isAppGrantedRoot() == true + } - suspend fun isBusyboxAvailable(): Boolean = withContext(dispatcher) { + suspend fun isBusyboxAvailable(): Boolean = withContext(shellDispatcher) { val results: List = Shell.cmd("which busybox").exec().out return@withContext ShellUtils.isValidOutput(results) && results.firstOrNull() ?.isNotEmpty() == true } - suspend fun executeWithOutput( - command: String, - defaultOutput: String = "", - forEachLine: ((String) -> Unit)? = null - ): String = withContext(dispatcher) { - return@withContext runCatching { - buildString { - val outputs = Shell.cmd(command).exec().out - if (!ShellUtils.isValidOutput(outputs)) { - append(defaultOutput) - return@buildString - } - outputs.forEach { line -> - if (forEachLine != null) { - forEachLine(line.orEmpty()) - appendLine(line.orEmpty()) - } else { - appendLine(line.orEmpty()) - } - } - }.trim().removeSuffix("\n") - }.getOrDefault(defaultOutput) - } + fun executeCommandAndStreamOutput(command: String): Flow = flow { + val result = Shell.cmd(command).exec() + val outputs = result.out + + if (ShellUtils.isValidOutput(outputs)) { + outputs.forEach { line -> + emit(line.orEmpty()) + } + } else { + if (result.isSuccess.not()) { + result.err.forEach { errorLine -> Log.e("RootUtils", errorLine) } + throw ShellCommandException( + message = "Command execution failed", + cause = Exception(result.err.joinToString("\n")) + ) + } + } + }.flowOn(shellDispatcher) fun finishProcess() { runCatching { diff --git a/app/src/main/res/raw/abi.txt b/data/src/main/res/raw/abi.txt similarity index 100% rename from app/src/main/res/raw/abi.txt rename to data/src/main/res/raw/abi.txt diff --git a/app/src/main/res/raw/fs.txt b/data/src/main/res/raw/fs.txt similarity index 100% rename from app/src/main/res/raw/fs.txt rename to data/src/main/res/raw/fs.txt diff --git a/app/src/main/res/raw/kernel.txt b/data/src/main/res/raw/kernel.txt similarity index 100% rename from app/src/main/res/raw/kernel.txt rename to data/src/main/res/raw/kernel.txt diff --git a/app/src/main/res/raw/net.txt b/data/src/main/res/raw/net.txt similarity index 100% rename from app/src/main/res/raw/net.txt rename to data/src/main/res/raw/net.txt diff --git a/app/src/main/res/raw/vm.txt b/data/src/main/res/raw/vm.txt similarity index 100% rename from app/src/main/res/raw/vm.txt rename to data/src/main/res/raw/vm.txt From 20c84f7b977d36498431e240faba1311ee5e44c2 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Tue, 12 Aug 2025 20:50:04 -0300 Subject: [PATCH 05/23] refactor: [WIP] updated presentation layer --- app/build.gradle.kts | 22 +- app/src/main/AndroidManifest.xml | 20 +- .../com/androidvip/sysctlgui/SysctlGuiApp.kt | 4 +- .../core/navigation/TopLevelRoute.kt | 20 + .../sysctlgui/core/navigation/UiRoute.kt | 27 + .../data/mapper/DomainParamMapper.kt | 26 - .../sysctlgui/data/models/KernelParam.kt | 22 - .../sysctlgui/data/models/SettingsItem.kt | 10 - .../sysctlgui/di/PresentationModule.kt | 25 +- .../helpers/OnSettingsItemClickedListener.kt | 7 - .../sysctlgui/helpers/ParamDiffCallback.kt | 18 - .../helpers/SettingsItemDiffCallback.kt | 14 - .../sysctlgui/helpers/UiKernelParamMapper.kt | 17 + .../androidvip/sysctlgui/models/SearchHint.kt | 15 + .../sysctlgui/models/UiKernelParam.kt | 58 ++ .../services/tiles/StartAppTileService.kt | 26 +- .../services/tiles/StartUpTileService.kt | 60 +- .../ui/base/BaseAppCompatActivity.kt | 25 - .../sysctlgui/ui/base/BaseSearchFragment.kt | 57 -- .../sysctlgui/ui/base/BaseViewHolder.kt | 10 - .../sysctlgui/ui/components/ErrorContainer.kt | 96 +++ .../ui/components/SingleChoiceDialog.kt | 77 ++ .../ui/export/ExportOptionsFragment.kt | 152 ---- .../ui/export/ExportOptionsItemAdapter.kt | 40 - .../ui/export/ExportOptionsViewEffect.kt | 17 - .../ui/export/ExportOptionsViewModel.kt | 160 ---- .../sysctlgui/ui/main/AppNavHost.kt | 90 ++ .../sysctlgui/ui/main/MainActivity.kt | 81 +- .../sysctlgui/ui/main/MainNavBar.kt | 107 +++ .../sysctlgui/ui/main/MainScreen.kt | 105 +++ .../sysctlgui/ui/main/MainTopBar.kt | 75 ++ .../sysctlgui/ui/main/MainViewEffect.kt | 9 - .../sysctlgui/ui/main/MainViewModel.kt | 61 +- .../sysctlgui/ui/main/MainViewState.kt | 26 + .../ui/params/DocumentationBottomSheet.kt | 157 ++++ .../sysctlgui/ui/params/EmptyParamsWarning.kt | 76 -- .../ui/params/OnParamItemClickedListener.kt | 8 - .../params/OnPopUpMenuItemSelectedListener.kt | 13 - .../ui/params/browse/BrowseParamsViewModel.kt | 161 ---- .../browse/KernelParamBrowseFragment.kt | 277 ------ .../ui/params/browse/ParamBrowseItem.kt | 100 --- .../ui/params/browse/ParamBrowseScreen.kt | 313 +++++++ .../ui/params/browse/ParamBrowseState.kt | 23 + .../ui/params/browse/ParamBrowseViewModel.kt | 115 +++ .../params/browse/ParamBrowserViewEffect.kt | 11 - .../ui/params/browse/ParamBrowserViewEvent.kt | 13 - .../ui/params/browse/ParamBrowserViewState.kt | 14 - .../ui/params/browse/ParamFileRow.kt | 159 ++++ .../sysctlgui/ui/params/browse/ParamRow.kt | 106 +++ .../ui/params/edit/ActionToggleButton.kt | 188 ++++ .../ui/params/edit/EditKernelParamActivity.kt | 108 --- .../ui/params/edit/EditParamScreen.kt | 816 +++++++++++------- .../ui/params/edit/EditParamViewEffect.kt | 10 - .../ui/params/edit/EditParamViewEvent.kt | 15 - .../ui/params/edit/EditParamViewModel.kt | 253 +++--- .../ui/params/edit/EditParamViewState.kt | 26 +- .../ui/params/list/KernelParamListFragment.kt | 135 --- .../ui/params/list/ListParamsViewModel.kt | 47 - .../sysctlgui/ui/params/list/ParamItem.kt | 53 -- .../ui/params/list/ParamViewEffect.kt | 7 - .../ui/params/list/ParamViewEvent.kt | 9 - .../ui/params/list/ParamViewState.kt | 9 - .../params/user/BaseManageParamsActivity.kt | 47 - .../user/ManageFavoritesParamsActivity.kt | 12 - .../user/ManageOnStartUpParamsActivity.kt | 12 - .../ui/params/user/UserParamsScreen.kt | 242 ------ .../ui/params/user/UserParamsViewEvent.kt | 12 - .../ui/params/user/UserParamsViewModel.kt | 80 -- .../ui/params/user/UserParamsViewState.kt | 8 - .../ui/presets/ImportPresetScreen.kt | 357 ++++++++ .../sysctlgui/ui/presets/PresetsScreen.kt | 215 +++++ .../sysctlgui/ui/presets/PresetsViewModel.kt | 109 +++ .../sysctlgui/ui/presets/PresetsViewState.kt | 30 + .../sysctlgui/ui/search/SearchScreen.kt | 462 ++++++++++ .../sysctlgui/ui/search/SearchViewModel.kt | 140 +++ .../sysctlgui/ui/search/SearchViewState.kt | 23 + .../sysctlgui/ui/settings/SettingsFragment.kt | 120 --- .../sysctlgui/ui/settings/SettingsScreen.kt | 233 +++++ .../ui/settings/SettingsViewModel.kt | 68 ++ .../ui/settings/components/HeaderComponent.kt | 46 + .../components/SettingsComponentColumn.kt | 45 + .../components/SliderSettingComponent.kt | 89 ++ .../components/SwitchSettingComponent.kt | 91 ++ .../components/TextSettingComponent.kt | 111 +++ .../ui/settings/model/SettingsViewEvent.kt | 15 + .../sysctlgui/ui/start/StartActivity.kt | 44 +- .../sysctlgui/ui/start/StartErrorActivity.kt | 4 +- .../ui/tasker/TaskerPluginActivity.kt | 6 +- .../sysctlgui/ui/user/UserParamsScreen.kt | 262 ++++++ .../sysctlgui/ui/user/UserParamsViewModel.kt | 69 ++ .../sysctlgui/ui/user/UserParamsViewState.kt | 20 + .../sysctlgui/utils/DataBindingUtils.kt | 10 - .../sysctlgui/utils/KernelParamUtils.kt | 32 - .../androidvip/sysctlgui/utils/ThemeExt.kt | 28 - .../widgets/FavoriteWidgetParamUpdater.kt | 7 +- .../sysctlgui/widgets/FavoritesWidget.kt | 34 +- .../widgets/FavoritesWidgetService.kt | 13 +- .../sysctlgui/work/StartUpWorker.kt | 17 +- .../androidvip/sysctlgui/work/TaskerWorker.kt | 14 +- .../drawable-night/ic_launcher_background.xml | 10 - app/src/main/res/drawable/circle_file.xml | 10 - app/src/main/res/drawable/circle_folder.xml | 10 - .../main/res/drawable/fast_scroll_thumb.xml | 13 - .../main/res/drawable/fast_scroll_track.xml | 5 - .../main/res/drawable/ic_action_tasker.xml | 10 - app/src/main/res/drawable/ic_arrow_upward.xml | 12 + app/src/main/res/drawable/ic_close.xml | 9 - app/src/main/res/drawable/ic_config.xml | 5 - app/src/main/res/drawable/ic_delete_sweep.xml | 14 +- .../main/res/drawable/ic_documentation.xml | 14 +- app/src/main/res/drawable/ic_edit_outline.xml | 9 - app/src/main/res/drawable/ic_export.xml | 5 + app/src/main/res/drawable/ic_favorite.xml | 5 + .../res/drawable/ic_favorite_outlined.xml | 5 + app/src/main/res/drawable/ic_file.xml | 9 + app/src/main/res/drawable/ic_file_outline.xml | 9 - app/src/main/res/drawable/ic_folder.xml | 11 + .../main/res/drawable/ic_folder_outline.xml | 9 - app/src/main/res/drawable/ic_heart_broken.xml | 5 + app/src/main/res/drawable/ic_history.xml | 5 + app/src/main/res/drawable/ic_import.xml | 5 + app/src/main/res/drawable/ic_info_outline.xml | 9 - .../res/drawable/ic_launcher_background.xml | 2 +- app/src/main/res/drawable/ic_more_vert.xml | 9 - app/src/main/res/drawable/ic_name.xml | 5 - .../main/res/drawable/ic_open_in_browser.xml | 12 + app/src/main/res/drawable/ic_search.xml | 9 - .../main/res/drawable/ic_settings_outline.xml | 9 - app/src/main/res/drawable/ic_tasker.xml | 10 + .../main/res/drawable/ic_tasker_outlined.xml | 10 + .../main/res/layout-land/activity_main2.xml | 50 -- app/src/main/res/layout/activity_main2.xml | 43 - app/src/main/res/layout/activity_splash.xml | 1 - .../res/layout/activity_tasker_plugin.xml | 10 +- .../res/layout/fragment_export_options.xml | 43 - .../main/res/layout/list_item_settings.xml | 74 -- app/src/main/res/layout/settings_activity.xml | 26 - app/src/main/res/menu/menu_browse_params.xml | 32 - app/src/main/res/menu/menu_main.xml | 15 - app/src/main/res/menu/menu_main_search.xml | 23 - app/src/main/res/menu/menu_search.xml | 11 - app/src/main/res/menu/nav_main.xml | 24 - app/src/main/res/menu/popup_manage_params.xml | 5 - .../main/res/navigation/main_navigation.xml | 50 -- app/src/main/res/values-land/integers.xml | 4 - app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 1 - app/src/main/res/values-v14/dimens.xml | 10 - app/src/main/res/values/integers.xml | 4 - app/src/main/res/values/strings.xml | 9 +- app/src/main/res/xml/preferences.xml | 86 -- buildSrc/src/main/kotlin/AndroidX.kt | 26 - buildSrc/src/main/kotlin/BuildPlugins.kt | 7 - buildSrc/src/main/kotlin/Compose.kt | 8 - buildSrc/src/main/kotlin/Dependencies.kt | 12 - buildSrc/src/main/kotlin/Google.kt | 4 - buildSrc/src/main/kotlin/Modules.kt | 7 - common/design/build.gradle.kts | 6 +- .../design/BaseBottomSheetFragment.kt | 67 -- .../sysctlgui/design/DesignResources.kt | 5 - .../sysctlgui/design/ModalBottomSheet.kt | 88 -- .../sysctlgui/design/theme/Color.kt | 290 +++++-- .../sysctlgui/design/theme/Shape.kt | 26 - .../sysctlgui/design/theme/Theme.kt | 303 +++++-- .../androidvip/sysctlgui/design/theme/Type.kt | 133 +++ .../src/main/res/font/passionone_bold.ttf | Bin 0 -> 22876 bytes .../src/main/res/font/passionone_regular.ttf | Bin 0 -> 23076 bytes .../src/main/res/font/sansation_bold.ttf | Bin 0 -> 37012 bytes .../main/res/font/sansation_bold_italic.ttf | Bin 0 -> 38740 bytes .../src/main/res/font/sansation_regular.ttf | Bin 0 -> 36764 bytes .../res/font/sansation_regular_italic.ttf | Bin 0 -> 38596 bytes .../design/src/main/res/layout/dialog_web.xml | 28 - .../main/res/layout/modal_bottom_sheet.xml | 79 -- .../preference_widget_switch_compat.xml | 9 - .../sysctlgui/data/ExampleInstrumentedTest.kt | 24 - .../sysctlgui/data/di/DataModule.kt | 14 +- .../sysctlgui/data/models/KernelParamDTO.kt | 15 +- .../repository/AppSettingsRepositoryImpl.kt | 3 +- .../data/repository/ParamsRepositoryImpl.kt | 22 +- .../data/repository/UserRepositoryImpl.kt | 6 +- .../source/OnlineDocumentationDataSource.kt | 24 +- .../data/utils/PresetsFileProcessor.kt | 11 +- .../sysctlgui/data/ExampleUnitTest.kt | 17 - .../sysctlgui/domain/enums}/Actions.kt | 5 +- .../sysctlgui/domain/models/KernelParam.kt | 13 +- gradle/libs.versions.toml | 19 +- 186 files changed, 5748 insertions(+), 4238 deletions(-) create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/core/navigation/TopLevelRoute.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/core/navigation/UiRoute.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/data/mapper/DomainParamMapper.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/data/models/KernelParam.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/data/models/SettingsItem.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/helpers/OnSettingsItemClickedListener.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/helpers/ParamDiffCallback.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/helpers/SettingsItemDiffCallback.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/helpers/UiKernelParamMapper.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/models/SearchHint.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/models/UiKernelParam.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseAppCompatActivity.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseSearchFragment.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseViewHolder.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/ErrorContainer.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/SingleChoiceDialog.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsFragment.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsItemAdapter.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsViewEffect.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsViewModel.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainTopBar.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewEffect.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/DocumentationBottomSheet.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/EmptyParamsWarning.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/OnParamItemClickedListener.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/OnPopUpMenuItemSelectedListener.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/BrowseParamsViewModel.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/KernelParamBrowseFragment.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseItem.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseState.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowserViewEffect.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowserViewEvent.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowserViewState.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamFileRow.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamRow.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/ActionToggleButton.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditKernelParamActivity.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewEffect.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewEvent.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/KernelParamListFragment.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ListParamsViewModel.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamItem.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamViewEffect.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamViewEvent.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamViewState.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/BaseManageParamsActivity.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/ManageFavoritesParamsActivity.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/ManageOnStartUpParamsActivity.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsScreen.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewEvent.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewModel.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewState.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/ImportPresetScreen.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsViewModel.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsViewState.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewModel.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewState.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsFragment.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsViewModel.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/HeaderComponent.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/SettingsComponentColumn.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/SliderSettingComponent.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/SwitchSettingComponent.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/TextSettingComponent.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/model/SettingsViewEvent.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsViewModel.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsViewState.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/utils/DataBindingUtils.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/utils/KernelParamUtils.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/utils/ThemeExt.kt delete mode 100644 app/src/main/res/drawable-night/ic_launcher_background.xml delete mode 100644 app/src/main/res/drawable/circle_file.xml delete mode 100644 app/src/main/res/drawable/circle_folder.xml delete mode 100644 app/src/main/res/drawable/fast_scroll_thumb.xml delete mode 100644 app/src/main/res/drawable/fast_scroll_track.xml delete mode 100644 app/src/main/res/drawable/ic_action_tasker.xml create mode 100644 app/src/main/res/drawable/ic_arrow_upward.xml delete mode 100644 app/src/main/res/drawable/ic_close.xml delete mode 100644 app/src/main/res/drawable/ic_config.xml delete mode 100644 app/src/main/res/drawable/ic_edit_outline.xml create mode 100644 app/src/main/res/drawable/ic_export.xml create mode 100644 app/src/main/res/drawable/ic_favorite.xml create mode 100644 app/src/main/res/drawable/ic_favorite_outlined.xml create mode 100644 app/src/main/res/drawable/ic_file.xml delete mode 100644 app/src/main/res/drawable/ic_file_outline.xml create mode 100644 app/src/main/res/drawable/ic_folder.xml delete mode 100644 app/src/main/res/drawable/ic_folder_outline.xml create mode 100644 app/src/main/res/drawable/ic_heart_broken.xml create mode 100644 app/src/main/res/drawable/ic_history.xml create mode 100644 app/src/main/res/drawable/ic_import.xml delete mode 100644 app/src/main/res/drawable/ic_info_outline.xml delete mode 100644 app/src/main/res/drawable/ic_more_vert.xml delete mode 100644 app/src/main/res/drawable/ic_name.xml create mode 100644 app/src/main/res/drawable/ic_open_in_browser.xml delete mode 100644 app/src/main/res/drawable/ic_search.xml delete mode 100644 app/src/main/res/drawable/ic_settings_outline.xml create mode 100644 app/src/main/res/drawable/ic_tasker.xml create mode 100644 app/src/main/res/drawable/ic_tasker_outlined.xml delete mode 100644 app/src/main/res/layout-land/activity_main2.xml delete mode 100644 app/src/main/res/layout/activity_main2.xml delete mode 100644 app/src/main/res/layout/fragment_export_options.xml delete mode 100644 app/src/main/res/layout/list_item_settings.xml delete mode 100644 app/src/main/res/layout/settings_activity.xml delete mode 100644 app/src/main/res/menu/menu_browse_params.xml delete mode 100644 app/src/main/res/menu/menu_main.xml delete mode 100644 app/src/main/res/menu/menu_main_search.xml delete mode 100644 app/src/main/res/menu/menu_search.xml delete mode 100644 app/src/main/res/menu/nav_main.xml delete mode 100644 app/src/main/res/menu/popup_manage_params.xml delete mode 100644 app/src/main/res/navigation/main_navigation.xml delete mode 100644 app/src/main/res/values-land/integers.xml delete mode 100644 app/src/main/res/values-v14/dimens.xml delete mode 100644 app/src/main/res/values/integers.xml delete mode 100644 app/src/main/res/xml/preferences.xml delete mode 100644 buildSrc/src/main/kotlin/AndroidX.kt delete mode 100644 buildSrc/src/main/kotlin/BuildPlugins.kt delete mode 100644 buildSrc/src/main/kotlin/Compose.kt delete mode 100644 buildSrc/src/main/kotlin/Dependencies.kt delete mode 100644 buildSrc/src/main/kotlin/Google.kt delete mode 100644 buildSrc/src/main/kotlin/Modules.kt delete mode 100644 common/design/src/main/java/com/androidvip/sysctlgui/design/BaseBottomSheetFragment.kt delete mode 100644 common/design/src/main/java/com/androidvip/sysctlgui/design/DesignResources.kt delete mode 100644 common/design/src/main/java/com/androidvip/sysctlgui/design/ModalBottomSheet.kt delete mode 100644 common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Shape.kt create mode 100644 common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Type.kt create mode 100644 common/design/src/main/res/font/passionone_bold.ttf create mode 100644 common/design/src/main/res/font/passionone_regular.ttf create mode 100644 common/design/src/main/res/font/sansation_bold.ttf create mode 100644 common/design/src/main/res/font/sansation_bold_italic.ttf create mode 100644 common/design/src/main/res/font/sansation_regular.ttf create mode 100644 common/design/src/main/res/font/sansation_regular_italic.ttf delete mode 100644 common/design/src/main/res/layout/dialog_web.xml delete mode 100644 common/design/src/main/res/layout/modal_bottom_sheet.xml delete mode 100644 common/design/src/main/res/layout/preference_widget_switch_compat.xml delete mode 100644 data/src/androidTest/java/com/androidvip/sysctlgui/data/ExampleInstrumentedTest.kt delete mode 100644 data/src/test/java/com/androidvip/sysctlgui/data/ExampleUnitTest.kt rename {app/src/main/kotlin/com/androidvip/sysctlgui/helpers => domain/src/main/java/com/androidvip/sysctlgui/domain/enums}/Actions.kt (60%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b202615..1643914 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,7 +6,6 @@ plugins { alias(libs.plugins.kotlin.compose) alias(libs.plugins.ksp) alias(libs.plugins.jetbrains.kotlin.serialization) - kotlin("kapt") id("kotlin-parcelize") } @@ -65,7 +64,6 @@ android { buildFeatures { viewBinding = true - dataBinding = true compose = true } @@ -108,12 +106,14 @@ dependencies { implementation(libs.kotlin.stdlib) implementation(libs.kotlinx.coroutines.android) - implementation(libs.androidx.core.ktx) implementation(libs.androidx.activity.compose) + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.appcompat) implementation(libs.androidx.material) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.window) + implementation(libs.androidx.work.runtime.ktx) // Lifecycle implementation(libs.androidx.lifecycle.runtime.ktx) @@ -124,23 +124,7 @@ dependencies { implementation(libs.androidx.lifecycle.viewmodel.savedstate) //ksp(libs.androidx.lifecycle.compiler) - implementation(AndroidX.splashScreen) - implementation(AndroidX.lifecycleLiveData) - implementation(AndroidX.navigationFragment) - implementation(AndroidX.navigationUi) - implementation(AndroidX.preference) - implementation(AndroidX.room) - implementation(AndroidX.roomRuntime) - implementation(AndroidX.workManager) - ksp(AndroidX.roomCompiler) - - implementation(Google.gson) - implementation(libs.koin) implementation(libs.koin.compose) implementation(libs.bundles.libsu) - - implementation(Dependencies.libSuIo) - implementation(Dependencies.liveEvent) - implementation(Dependencies.tapTargetView) } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e683bad..dad076a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,25 +22,7 @@ android:theme="@style/AppTheme" android:enableOnBackInvokedCallback="true" tools:ignore="GoogleAppIndexingWarning"> - - - - - + ( + val name: String, + val route: T, + val selectedIcon: ImageVector, + val unselectedIcon: ImageVector +) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/core/navigation/UiRoute.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/core/navigation/UiRoute.kt new file mode 100644 index 0000000..b53090c --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/core/navigation/UiRoute.kt @@ -0,0 +1,27 @@ +package com.androidvip.sysctlgui.core.navigation + +import kotlinx.serialization.Serializable + +/** + * Represents the different routes in the application's UI. + * This is used for navigation purposes. + */ +@Serializable +sealed interface UiRoute { + @Serializable + data object BrowseParams : UiRoute + @Serializable + data class EditParam(val paramName: String) : UiRoute + @Serializable + data object Presets : UiRoute + @Serializable + data object ImportPresets : UiRoute + @Serializable + data object Favorites : UiRoute + @Serializable + data object UserParams : UiRoute + @Serializable + data object Search : UiRoute + @Serializable + data object Settings : UiRoute +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/data/mapper/DomainParamMapper.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/data/mapper/DomainParamMapper.kt deleted file mode 100644 index b60af5b..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/data/mapper/DomainParamMapper.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.androidvip.sysctlgui.data.mapper - -import com.androidvip.sysctlgui.data.models.KernelParam -import com.androidvip.sysctlgui.domain.models.DomainKernelParam - -object DomainParamMapper : Mapper { - override fun map(from: DomainKernelParam): KernelParam = KernelParam().apply { - id = from.id - name = from.name - path = from.path - value = from.value - favorite = from.favorite - taskerParam = from.taskerParam - taskerList = from.taskerList - } - - override fun unmap(from: KernelParam): DomainKernelParam = DomainKernelParam().apply { - id = from.id - name = from.name - path = from.path - value = from.value - favorite = from.favorite - taskerParam = from.taskerParam - taskerList = from.taskerList - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/data/models/KernelParam.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/data/models/KernelParam.kt deleted file mode 100644 index 5843783..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/data/models/KernelParam.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.androidvip.sysctlgui.data.models - -import android.os.Parcelable -import com.androidvip.sysctlgui.domain.models.DomainKernelParam -import com.androidvip.sysctlgui.utils.Consts -import kotlinx.parcelize.Parcelize - -@Parcelize -data class KernelParam( - override var id: Int = 0, - override var name: String = "", - override var path: String = "", - override var value: String = "", - override var favorite: Boolean = false, - override var taskerParam: Boolean = false, - override var taskerList: Int = Consts.LIST_NUMBER_PRIMARY_TASKER -) : DomainKernelParam(), Parcelable { - override fun toString(): String { - if (name.isEmpty()) setNameFromPath(path) - return "$name = $value" - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/data/models/SettingsItem.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/data/models/SettingsItem.kt deleted file mode 100644 index edba905..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/data/models/SettingsItem.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.androidvip.sysctlgui.data.models - -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes - -data class SettingsItem( - @StringRes val titleRes: Int, - @StringRes val descriptionRes: Int, - @DrawableRes val iconRes: Int -) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/di/PresentationModule.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/di/PresentationModule.kt index 0acae81..87aac32 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/di/PresentationModule.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/di/PresentationModule.kt @@ -1,24 +1,25 @@ package com.androidvip.sysctlgui.di -import com.androidvip.sysctlgui.ui.export.ExportOptionsViewModel import com.androidvip.sysctlgui.ui.main.MainViewModel -import com.androidvip.sysctlgui.ui.params.browse.BrowseParamsViewModel +import com.androidvip.sysctlgui.ui.params.browse.ParamBrowseViewModel import com.androidvip.sysctlgui.ui.params.edit.EditParamViewModel -import com.androidvip.sysctlgui.ui.params.list.ListParamsViewModel -import com.androidvip.sysctlgui.ui.params.user.UserParamsViewModel +import com.androidvip.sysctlgui.ui.presets.PresetsViewModel +import com.androidvip.sysctlgui.ui.search.SearchViewModel +import com.androidvip.sysctlgui.ui.settings.SettingsViewModel +import com.androidvip.sysctlgui.ui.user.UserParamsViewModel import com.androidvip.sysctlgui.widgets.FavoriteWidgetParamUpdater import org.koin.android.ext.koin.androidContext -import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.androidx.viewmodel.dsl.viewModelOf +import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module -internal val presentationModules = module { - viewModel { BrowseParamsViewModel(get(), get()) } - viewModelOf(::ListParamsViewModel) - viewModelOf(::UserParamsViewModel) - viewModelOf(::EditParamViewModel) +internal val presentationModule = module { viewModelOf(::MainViewModel) - viewModel { ExportOptionsViewModel(get(), get(), get()) } + viewModelOf(::SettingsViewModel) + viewModelOf(::ParamBrowseViewModel) + viewModelOf(::EditParamViewModel) + viewModelOf(::SearchViewModel) + viewModelOf(::PresetsViewModel) + viewModelOf(::UserParamsViewModel) single { FavoriteWidgetParamUpdater(androidContext()).getListener() } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/OnSettingsItemClickedListener.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/OnSettingsItemClickedListener.kt deleted file mode 100644 index 97e3be3..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/OnSettingsItemClickedListener.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.androidvip.sysctlgui.helpers - -import com.androidvip.sysctlgui.data.models.SettingsItem - -interface OnSettingsItemClickedListener { - fun onSettingsItemClicked(item: SettingsItem, position: Int) -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/ParamDiffCallback.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/ParamDiffCallback.kt deleted file mode 100644 index cec3940..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/ParamDiffCallback.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.androidvip.sysctlgui.helpers - -import androidx.recyclerview.widget.DiffUtil -import com.androidvip.sysctlgui.data.models.KernelParam - -class ParamDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: KernelParam, newItem: KernelParam): Boolean { - return oldItem.name == newItem.name - } - - override fun areContentsTheSame(oldItem: KernelParam, newItem: KernelParam): Boolean { - return oldItem.name == newItem.name - && oldItem.path == newItem.path - && oldItem.value == newItem.value - && oldItem.favorite == newItem.favorite - && oldItem.taskerParam == newItem.taskerParam - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/SettingsItemDiffCallback.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/SettingsItemDiffCallback.kt deleted file mode 100644 index baab603..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/SettingsItemDiffCallback.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.androidvip.sysctlgui.helpers - -import androidx.recyclerview.widget.DiffUtil -import com.androidvip.sysctlgui.data.models.SettingsItem - -internal object SettingsItemDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean { - return oldItem.titleRes == newItem.titleRes - } - - override fun areContentsTheSame(oldItem: SettingsItem, newItem: SettingsItem): Boolean { - return oldItem == newItem - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/UiKernelParamMapper.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/UiKernelParamMapper.kt new file mode 100644 index 0000000..04dbaa2 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/UiKernelParamMapper.kt @@ -0,0 +1,17 @@ +package com.androidvip.sysctlgui.helpers + +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.models.UiKernelParam + +object UiKernelParamMapper { + fun map(param: KernelParam): UiKernelParam { + return UiKernelParam( + name = param.name, + path = param.path, + value = param.value, + isFavorite = param.isFavorite, + isTaskerParam = param.isTaskerParam, + taskerList = param.taskerList + ) + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/models/SearchHint.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/models/SearchHint.kt new file mode 100644 index 0000000..881e305 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/models/SearchHint.kt @@ -0,0 +1,15 @@ +package com.androidvip.sysctlgui.models + +/** + * Represents a search hint displayed to the user. + * + * This holds information about a single search suggestion, including the text of the hint + * and whether it originates from the user's search history. + * + * @property hint The text of the search hint. + * @property isFromHistory A boolean flag indicating whether the hint is from the user's search history + */ +data class SearchHint( + val hint: String, + val isFromHistory: Boolean = false +) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/models/UiKernelParam.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/models/UiKernelParam.kt new file mode 100644 index 0000000..379d49d --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/models/UiKernelParam.kt @@ -0,0 +1,58 @@ +package com.androidvip.sysctlgui.models + +import android.os.Build +import android.os.Parcelable +import androidx.compose.runtime.Stable +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.utils.Consts +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize +import java.io.File +import java.nio.file.Paths +import kotlin.io.path.isDirectory + +/** + * Represents a kernel parameter with additional UI-specific properties. + */ +@Stable +@Parcelize +data class UiKernelParam( + override val name: String = "", + override val path: String = "", + override val value: String = "", + override val isFavorite: Boolean = false, + override val isTaskerParam: Boolean = false, + override val taskerList: Int = Consts.LIST_NUMBER_PRIMARY_TASKER +) : KernelParam(name, path, value, isFavorite, isTaskerParam, taskerList), Parcelable { + + /** + * Lazily determines if the [path] represents a directory. + * Uses [Paths] for Android O and above, otherwise falls back to [File]. + */ + @IgnoredOnParcel + val isDirectory by lazy { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Paths.get(path).isDirectory() + } else { + File(path).isDirectory + } + } + + /** + * The last segment of the parameter's name or path. + * + * If the parameter represents a directory, this will be the last segment of its [path] + * after the last `/`. For example: `/proc/sys/vm/` -> `vm`. + * + * If the parameter represents a file, this will be the last segment of its [name] + * after the last `.`. For example: `vm.swappiness` -> `swappiness`. + * + * If there is no `.` in the name, the full [name] is returned. + */ + override val lastNameSegment: String + get() = if (isDirectory) { + path.substringAfterLast('/') + } else { + name.substringAfterLast('.', name) + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/services/tiles/StartAppTileService.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/services/tiles/StartAppTileService.kt index 1c9bb6b..0cbebac 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/services/tiles/StartAppTileService.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/services/tiles/StartAppTileService.kt @@ -1,23 +1,39 @@ package com.androidvip.sysctlgui.services.tiles +import android.annotation.SuppressLint +import android.app.PendingIntent import android.content.Intent import android.os.Build import android.service.quicksettings.Tile import android.service.quicksettings.TileService import androidx.annotation.RequiresApi +import androidx.core.service.quicksettings.PendingIntentActivityWrapper +import androidx.core.service.quicksettings.TileServiceCompat import com.androidvip.sysctlgui.ui.start.StartActivity -@RequiresApi(Build.VERSION_CODES.N) + class StartAppTileService : TileService() { + + @SuppressLint("StartActivityAndCollapseDeprecated") override fun onClick() { super.onClick() qsTile.apply { state = Tile.STATE_INACTIVE updateTile() } - startActivityAndCollapse( - Intent(this, StartActivity::class.java) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) + + val intent = Intent(this, StartActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) + } + + val wrapper = PendingIntentActivityWrapper( + this, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT, + false ) + + TileServiceCompat.startActivityAndCollapse(this, wrapper) } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/services/tiles/StartUpTileService.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/services/tiles/StartUpTileService.kt index b73db99..84aee78 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/services/tiles/StartUpTileService.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/services/tiles/StartUpTileService.kt @@ -1,55 +1,69 @@ package com.androidvip.sysctlgui.services.tiles -import android.os.Build import android.service.quicksettings.Tile import android.service.quicksettings.TileService import android.widget.Toast -import androidx.annotation.RequiresApi import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.data.utils.RootUtils import com.androidvip.sysctlgui.domain.repository.AppPrefs import com.androidvip.sysctlgui.helpers.StartUpServiceToggle -import com.topjohnwu.superuser.Shell +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject -@RequiresApi(Build.VERSION_CODES.N) class StartUpTileService : TileService(), KoinComponent { private val prefs: AppPrefs by inject() + private val rootUtils: RootUtils by inject() + private val serviceJob = Job() + private val serviceScope = CoroutineScope(Dispatchers.Main.immediate + serviceJob) + override fun onStartListening() { super.onStartListening() - qsTile.apply { - if (Shell.rootAccess()) { - state = if (isStartUpEnabled()) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE - } else { - state = Tile.STATE_UNAVAILABLE - label = resources.getString(R.string.tile_toggle_start_up_no_root_access_label) + serviceScope.launch { + qsTile.apply { + if (rootUtils.isRootAvailable()) { + state = if (isStartUpEnabled()) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE + } else { + state = Tile.STATE_UNAVAILABLE + label = resources.getString(R.string.tile_toggle_start_up_no_root_access_label) + } + updateTile() } - updateTile() } } override fun onClick() { super.onClick() - if (!Shell.rootAccess()) { - Toast.makeText( - this, - resources.getString(R.string.tile_toggle_start_up_no_root_access_toast), - Toast.LENGTH_LONG - ).show() - return - } + serviceScope.launch { + if (!rootUtils.isRootAvailable()) { + Toast.makeText( + this@StartUpTileService, + resources.getString(R.string.tile_toggle_start_up_no_root_access_toast), + Toast.LENGTH_LONG + ).show() + return@launch + } - toggleService(isStartUpEnabled().not()) + toggleService(isStartUpEnabled().not()) - qsTile.apply { - state = if (isStartUpEnabled()) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE - updateTile() + qsTile.apply { + state = if (isStartUpEnabled()) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE + updateTile() + } } } + override fun onDestroy() { + super.onDestroy() + serviceJob.cancel() + } + private fun isStartUpEnabled() = prefs.runOnStartUp private fun toggleService(enabled: Boolean) { diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseAppCompatActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseAppCompatActivity.kt deleted file mode 100644 index 12896ea..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseAppCompatActivity.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.androidvip.sysctlgui.ui.base - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.WindowCompat -import com.androidvip.sysctlgui.design.DesignStyles -import com.androidvip.sysctlgui.domain.repository.AppPrefs -import org.koin.android.ext.android.inject - -/** - * Base activity that uses AppCompat for theming - * TODO: Temporary until 100% compose - */ -abstract class BaseAppCompatActivity : AppCompatActivity() { - protected val prefs by inject() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - WindowCompat.setDecorFitsSystemWindows(window, false) - - if (prefs.forceDark) { - setTheme(DesignStyles.AppTheme_ForceDark) - } - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseSearchFragment.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseSearchFragment.kt deleted file mode 100644 index be07193..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseSearchFragment.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.androidvip.sysctlgui.ui.base - -import android.os.Bundle -import android.view.Menu -import android.view.MenuInflater -import android.widget.SearchView -import androidx.fragment.app.Fragment -import com.androidvip.sysctlgui.R - -abstract class BaseSearchFragment : Fragment() { - protected var searchExpression: String = "" - private var searchView: SearchView? = null - - abstract fun onQueryTextChanged() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setHasOptionsMenu(true) - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - super.onCreateOptionsMenu(menu, inflater) - - inflater.inflate(R.menu.menu_search, menu) - setUpSearchView(menu) - } - - protected fun setUpSearchView(menu: Menu?) { - searchView = (menu?.findItem(R.id.action_search)?.actionView as? SearchView)?.apply { - setOnQueryTextListener( - object : - androidx.appcompat.widget.SearchView.OnQueryTextListener, - SearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String?): Boolean { - return true - } - - override fun onQueryTextChange(newText: String?): Boolean { - searchExpression = newText.orEmpty().replace(".", "") - - this@BaseSearchFragment.onQueryTextChanged() - return true - } - } - ) - - // expand and show keyboard - isIconifiedByDefault = false - onActionViewExpanded() - } - } - - protected fun resetSearchExpression() { - searchExpression = "" - searchView?.setQuery("", false) - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseViewHolder.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseViewHolder.kt deleted file mode 100644 index 8aab4cb..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/base/BaseViewHolder.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.androidvip.sysctlgui.ui.base - -import androidx.databinding.ViewDataBinding -import androidx.recyclerview.widget.RecyclerView - -abstract class BaseViewHolder( - binding: ViewDataBinding -) : RecyclerView.ViewHolder(binding.root) { - abstract fun bind(item: T, position: Int) -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/ErrorContainer.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/ErrorContainer.kt new file mode 100644 index 0000000..d1f6940 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/ErrorContainer.kt @@ -0,0 +1,96 @@ +package com.androidvip.sysctlgui.ui.components + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Close +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +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.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +private const val ERROR_CONTAINER_ANIMATION_DURATION = 4000 + +@Composable +internal fun ErrorContainer(message: String, onAnimationEnd: () -> Unit) { + var animationStarted by remember { mutableStateOf(false) } + val progressTarget = if (animationStarted) 0f else 1f + + val progress by animateFloatAsState( + targetValue = progressTarget, + animationSpec = tween( + durationMillis = ERROR_CONTAINER_ANIMATION_DURATION, + easing = LinearEasing + ), + finishedListener = { value -> + if (value == 0f) { + onAnimationEnd() + } + } + ) + + LaunchedEffect(Unit) { animationStarted = true } + + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer + ) + ) { + Box { + LinearProgressIndicator( + progress = { progress }, + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth(), + color = MaterialTheme.colorScheme.onErrorContainer, + trackColor = MaterialTheme.colorScheme.errorContainer + ) + + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Icon( + modifier = Modifier.size(40.dp), + imageVector = Icons.Rounded.Close, + contentDescription = null, + tint = MaterialTheme.colorScheme.onErrorContainer + ) + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text( + text = "Error", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onErrorContainer + ) + Text( + text = message, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onErrorContainer + ) + } + } + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/SingleChoiceDialog.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/SingleChoiceDialog.kt new file mode 100644 index 0000000..45ddb18 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/SingleChoiceDialog.kt @@ -0,0 +1,77 @@ +package com.androidvip.sysctlgui.ui.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import android.R as AndroidResources + +@Composable +fun SingleChoiceDialog( + showDialog: Boolean, + title: String, + options: List, + initialSelectedOptionIndex: Int, + onDismissRequest: () -> Unit, + onOptionSelected: (Int) -> Unit +) { + if (showDialog) { + var selectedOptionIndex by remember { mutableIntStateOf(initialSelectedOptionIndex) } + + AlertDialog( + onDismissRequest = onDismissRequest, + title = { Text(text = title) }, + text = { + Column { + options.forEachIndexed { index, option -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { selectedOptionIndex = index } + .padding(vertical = 4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = (index == selectedOptionIndex), + onClick = { selectedOptionIndex = index } + ) + Text( + text = option, + modifier = Modifier.padding(start = 4.dp) + ) + } + } + } + }, + confirmButton = { + TextButton( + onClick = { + onOptionSelected(selectedOptionIndex) + onDismissRequest() + } + ) { + Text(stringResource(AndroidResources.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = onDismissRequest) { + Text(stringResource(AndroidResources.string.cancel)) + } + } + ) + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsFragment.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsFragment.kt deleted file mode 100644 index 4f2a3ba..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsFragment.kt +++ /dev/null @@ -1,152 +0,0 @@ -package com.androidvip.sysctlgui.ui.export - -import android.app.Activity -import android.content.Intent -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.annotation.StringRes -import androidx.fragment.app.Fragment -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.SettingsItem -import com.androidvip.sysctlgui.databinding.FragmentExportOptionsBinding -import com.androidvip.sysctlgui.design.ModalBottomSheet -import com.androidvip.sysctlgui.helpers.OnSettingsItemClickedListener -import com.androidvip.sysctlgui.toast -import org.koin.androidx.viewmodel.ext.android.viewModel - -class ExportOptionsFragment : Fragment(), OnSettingsItemClickedListener { - private var _binding: FragmentExportOptionsBinding? = null - private val binding get() = _binding!! - private val viewModel: ExportOptionsViewModel by viewModel() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentExportOptionsBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val adapter = ExportOptionsItemAdapter(this) - binding.recyclerView.adapter = adapter - adapter.submitList(viewModel.getBackOptionItems()) - - observeUi() - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (resultCode != Activity.RESULT_OK) return toast(R.string.error) - - when (requestCode) { - RC_IMPORT_USER_PARAMS, - RC_RESTORE_PARAMS -> { - val uri = data?.data ?: return toast(R.string.import_error) - val extension = uri.lastPathSegment.orEmpty() - val stream = requireContext().contentResolver.openInputStream(uri) - ?: return toast(R.string.import_error) - viewModel.importParams(stream, extension) - } - - RC_EXPORT_USER_PARAMS -> { - val uri = data?.data ?: return toast(R.string.export_error) - viewModel.exportParams(uri, requireContext(), false) - } - - RC_BACKUP_PARAMS -> { - val uri = data?.data ?: return toast(R.string.export_error) - viewModel.exportParams(uri, requireContext(), true) - } - } - super.onActivityResult(requestCode, resultCode, data) - } - - override fun onSettingsItemClicked(item: SettingsItem, position: Int) { - when (position) { - 0 -> viewModel.doWhenImportUserParamsPressed() - 1 -> viewModel.doWhenExportUserParamsPressed() - 2 -> viewModel.doWhenBackupPressed() - 3 -> viewModel.doWhenRestorePressed() - } - } - - private fun observeUi() { - viewModel.viewEffect.observe(viewLifecycleOwner) { - when (it) { - is ExportOptionsViewEffect.ImportUserParams -> requestImportFile(RC_IMPORT_USER_PARAMS) - is ExportOptionsViewEffect.ExportUserParams -> requestExportFile(RC_EXPORT_USER_PARAMS) - is ExportOptionsViewEffect.RestoreRuntimeParams -> requestImportFile(RC_RESTORE_PARAMS) - is ExportOptionsViewEffect.BackupRuntimeParams -> requestExportFile(RC_BACKUP_PARAMS) - is ExportOptionsViewEffect.ShowImportError -> showErrorModal(it.messageRes) - is ExportOptionsViewEffect.ShowImportSuccess -> showSuccessModal( - getString(R.string.import_success_message, it.paramCount) - ) - is ExportOptionsViewEffect.ShowExportError -> showErrorModal(it.messageRes) - is ExportOptionsViewEffect.ShowExportSuccess -> showSuccessModal( - getString(R.string.export_success_message) - ) - } - } - - viewModel.viewState.observe(viewLifecycleOwner) { - binding.progress.visibility = if (it.isLoading) View.VISIBLE else View.GONE - binding.loadingText.visibility = if (it.isLoading) View.VISIBLE else View.GONE - binding.recyclerView.visibility = if (it.isLoading) View.GONE else View.VISIBLE - } - } - - private fun requestImportFile(requestCode: Int) { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "*/*" - } - startActivityForResult(intent, requestCode) - } - - private fun requestExportFile(requestCode: Int) { - val extension = if (requestCode == RC_BACKUP_PARAMS) "conf" else "json" - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "*/*" - putExtra(Intent.EXTRA_TITLE, "params.$extension") - } - startActivityForResult(intent, requestCode) - } - - private fun showErrorModal(@StringRes messageRes: Int) { - ModalBottomSheet.newInstance( - getString(R.string.error), - getString(messageRes), - getString(android.R.string.ok) - ).also { - if (isAdded) it.show(childFragmentManager, "sheet") - } - } - - private fun showSuccessModal(message: String) { - ModalBottomSheet.newInstance( - getString(R.string.done), - message, - getString(android.R.string.ok) - ).also { - if (isAdded) it.show(childFragmentManager, "sheet") - } - } - - companion object { - private const val RC_IMPORT_USER_PARAMS: Int = 1 - private const val RC_EXPORT_USER_PARAMS: Int = 2 - private const val RC_BACKUP_PARAMS: Int = 3 - private const val RC_RESTORE_PARAMS: Int = 4 - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsItemAdapter.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsItemAdapter.kt deleted file mode 100644 index b031975..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsItemAdapter.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.androidvip.sysctlgui.ui.export - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.ListAdapter -import com.androidvip.sysctlgui.data.models.SettingsItem -import com.androidvip.sysctlgui.databinding.ListItemSettingsBinding -import com.androidvip.sysctlgui.helpers.OnSettingsItemClickedListener -import com.androidvip.sysctlgui.helpers.SettingsItemDiffCallback -import com.androidvip.sysctlgui.ui.base.BaseViewHolder - -class ExportOptionsItemAdapter( - private val itemClickedListener: OnSettingsItemClickedListener -) : ListAdapter>(SettingsItemDiffCallback) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeItemViewHolder { - val inflater = LayoutInflater.from(parent.context) - val binding = ListItemSettingsBinding.inflate( - inflater, parent, false - ) - return HomeItemViewHolder(binding) - } - - override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) { - if (holder is HomeItemViewHolder) { - holder.bind(getItem(position), position) - } - } - - inner class HomeItemViewHolder( - private val binding: ListItemSettingsBinding - ) : BaseViewHolder(binding) { - override fun bind(item: SettingsItem, position: Int) { - binding.item = item - binding.position = position - binding.onSettingsItemClickedListener = itemClickedListener - binding.executePendingBindings() - } - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsViewEffect.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsViewEffect.kt deleted file mode 100644 index 8767785..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsViewEffect.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.androidvip.sysctlgui.ui.export - -import androidx.annotation.StringRes - -sealed interface ExportOptionsViewEffect { - object ImportUserParams : ExportOptionsViewEffect - object ExportUserParams : ExportOptionsViewEffect - - object BackupRuntimeParams : ExportOptionsViewEffect - object RestoreRuntimeParams : ExportOptionsViewEffect - - class ShowImportError(@StringRes val messageRes: Int) : ExportOptionsViewEffect - class ShowImportSuccess(val paramCount: Int) : ExportOptionsViewEffect - - class ShowExportError(@StringRes val messageRes: Int) : ExportOptionsViewEffect - object ShowExportSuccess : ExportOptionsViewEffect -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsViewModel.kt deleted file mode 100644 index 9caec46..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/export/ExportOptionsViewModel.kt +++ /dev/null @@ -1,160 +0,0 @@ -package com.androidvip.sysctlgui.ui.export - -import android.content.Context -import android.net.Uri -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.SettingsItem -import com.androidvip.sysctlgui.domain.exceptions.EmptyFileException -import com.androidvip.sysctlgui.domain.exceptions.InvalidFileExtensionException -import com.androidvip.sysctlgui.domain.exceptions.MalformedLineException -import com.androidvip.sysctlgui.domain.exceptions.NoParameterFoundException -import com.androidvip.sysctlgui.domain.exceptions.NoValidParamException -import com.androidvip.sysctlgui.utils.ViewState -import com.androidvip.sysctlgui.domain.usecase.BackupParamsUseCase -import com.androidvip.sysctlgui.domain.usecase.ExportParamsUseCase -import com.androidvip.sysctlgui.domain.usecase.ImportParamsUseCase -import com.google.gson.JsonParseException -import com.google.gson.JsonSyntaxException -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.IOException -import java.io.InputStream - -class ExportOptionsViewModel( - private val importParamsUseCase: ImportParamsUseCase, - private val exportParamsUseCase: ExportParamsUseCase, - private val backupParamsUseCase: BackupParamsUseCase, - private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO -) : ViewModel() { - private val _viewEffect = MutableLiveData() - internal val viewEffect: LiveData = _viewEffect - - private val _viewState = MutableLiveData>() - val viewState: LiveData> = _viewState - - fun getBackOptionItems(): List = listOf( - SettingsItem( - R.string.import_parameters, - R.string.read_from_file_sum, - R.drawable.ic_import_params - ), - SettingsItem( - R.string.export_parameters, - R.string.export_parameters_sum, - R.drawable.ic_export_params - ), - SettingsItem( - R.string.backup_parameters, - R.string.backup_parameters_sum, - R.drawable.ic_backup_params - ), - SettingsItem( - R.string.restore_parameters, - R.string.restore_parameters_sum, - R.drawable.ic_restore_params - ) - ) - - fun doWhenImportUserParamsPressed() = _viewEffect.postValue( - ExportOptionsViewEffect.ImportUserParams - ) - - fun doWhenExportUserParamsPressed() = _viewEffect.postValue( - ExportOptionsViewEffect.ExportUserParams - ) - - fun doWhenBackupPressed() = _viewEffect.postValue(ExportOptionsViewEffect.BackupRuntimeParams) - - fun doWhenRestorePressed() = _viewEffect.postValue(ExportOptionsViewEffect.RestoreRuntimeParams) - - fun importParams(stream: InputStream, fileExtension: String) = viewModelScope.launch { - _viewState.postValue(currentViewState.copyState(isLoading = true)) - - val postError: (Int) -> Unit = { - _viewEffect.postValue(ExportOptionsViewEffect.ShowImportError(it)) - } - val result = runCatching { importParamsUseCase(stream, fileExtension) } - when (result.exceptionOrNull()) { - is JsonParseException, - is JsonSyntaxException -> postError(R.string.import_error_invalid_json) - - is InvalidFileExtensionException -> postError(R.string.import_error_invalid_file_type) - - is EmptyFileException -> postError(R.string.import_error_empty_file) - - is MalformedLineException -> postError(R.string.import_error_malformed_line) - - is NoValidParamException -> postError(R.string.no_parameters_found) - - null -> { - val successfulParams = result.getOrNull().orEmpty() - _viewEffect.postValue( - ExportOptionsViewEffect.ShowImportSuccess(successfulParams.size) - ) - } - else -> postError(R.string.import_error) - } - - _viewState.postValue(currentViewState.copyState(isLoading = false)) - } - - fun exportParams(target: Uri, context: Context, backup: Boolean) = viewModelScope.launch { - _viewState.postValue(currentViewState.copyState(isLoading = true)) - - val postError: (Int) -> Unit = { - _viewEffect.postValue(ExportOptionsViewEffect.ShowExportError(it)) - } - val result = if (backup) { - backUpParamsWithFileDescriptor(target, context) - } else { - exportParamsWithFileDescriptor(target, context) - } - - result.exceptionOrNull()?.printStackTrace() - - when (result.exceptionOrNull()) { - is IOException -> postError(R.string.export_error_io) - - is NoParameterFoundException -> postError(R.string.export_error_no_param) - - is EmptyFileException -> postError(R.string.import_error_empty_file) - - null -> _viewEffect.postValue(ExportOptionsViewEffect.ShowExportSuccess) - - else -> postError(R.string.export_error) - } - - _viewState.postValue(currentViewState.copyState(isLoading = false)) - } - - private suspend fun exportParamsWithFileDescriptor( - target: Uri, - context: Context - ): Result = withContext(ioDispatcher) { - return@withContext runCatching { - context.contentResolver.openFileDescriptor(target, "w").use { - exportParamsUseCase(it!!.fileDescriptor) - } - } - } - - private suspend fun backUpParamsWithFileDescriptor( - target: Uri, - context: Context - ): Result = withContext(ioDispatcher) { - return@withContext runCatching { - context.contentResolver.openFileDescriptor(target, "w").use { - backupParamsUseCase(it!!.fileDescriptor) - } - } - } - - private val currentViewState: ViewState - get() = viewState.value ?: ViewState() -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt new file mode 100644 index 0000000..b01c59b --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt @@ -0,0 +1,90 @@ +package com.androidvip.sysctlgui.ui.main + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import com.androidvip.sysctlgui.core.navigation.UiRoute +import com.androidvip.sysctlgui.ui.user.UserParamsScreen +import com.androidvip.sysctlgui.ui.params.browse.ParamBrowseScreen +import com.androidvip.sysctlgui.ui.params.browse.ParamBrowseScreenContentPreview +import com.androidvip.sysctlgui.ui.params.edit.EditParamScreen +import com.androidvip.sysctlgui.ui.presets.ImportPresetScreen +import com.androidvip.sysctlgui.ui.presets.PresetsScreen +import com.androidvip.sysctlgui.ui.search.SearchScreen +import com.androidvip.sysctlgui.ui.settings.SettingsScreen + +@Composable +internal fun AppNavHost(innerPadding: PaddingValues, navController: NavHostController) { + NavHost( + modifier = Modifier.padding(innerPadding), + navController = navController, + startDestination = UiRoute.BrowseParams + ) { + composable { + if (LocalView.current.isInEditMode) { + ParamBrowseScreenContentPreview() + } else { + ParamBrowseScreen( + onParamSelected = { + navController.navigate(UiRoute.EditParam(paramName = it.name)) + } + ) + } + } + + composable { + EditParamScreen(onNavigateBack = { navController.popBackStack() }) + } + + composable { + PresetsScreen( + onNavigateBack = { navController.popBackStack() }, + onNavigateToImport = { navController.navigate(UiRoute.ImportPresets) } + ) + } + + composable { + ImportPresetScreen(onNavigateBack = { navController.popBackStack() }) + } + + composable { + UserParamsScreen( + filterPredicate = { it.isFavorite }, + onParamSelected = { + navController.navigate(UiRoute.EditParam(paramName = it.name)) + } + ) + } + + composable { + SearchScreen( + onParamSelected = { + navController.navigate(UiRoute.EditParam(paramName = it.name)) + }, + onNavigateBack = { navController.popBackStack() } + ) + } + + composable { + SettingsScreen( + onNavigateToUserParams = { + navController.navigate(UiRoute.UserParams) + } + ) + } + + composable { + UserParamsScreen( + filterPredicate = { true }, + onParamSelected = { + navController.navigate(UiRoute.EditParam(paramName = it.name)) + } + ) + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt index 840b03d..d00fece 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt @@ -1,78 +1,48 @@ package com.androidvip.sysctlgui.ui.main import android.app.NotificationManager -import android.content.Context import android.os.Build import android.os.Bundle import android.os.Handler -import android.view.MenuItem +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.isSystemInDarkTheme import androidx.core.os.postDelayed -import androidx.core.view.WindowCompat -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.ui.AppBarConfiguration -import androidx.navigation.ui.setupWithNavController -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.databinding.ActivityMain2Binding -import com.androidvip.sysctlgui.helpers.Actions -import com.androidvip.sysctlgui.ui.base.BaseAppCompatActivity - -class MainActivity : BaseAppCompatActivity() { - private lateinit var binding: ActivityMain2Binding +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.domain.repository.AppPrefs +import org.koin.android.ext.android.inject +class MainActivity : ComponentActivity() { + private val prefs: AppPrefs by inject() private val notificationPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { _ -> } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - WindowCompat.setDecorFitsSystemWindows(window, false) - - binding = ActivityMain2Binding.inflate(layoutInflater) - setContentView(binding.root) - setSupportActionBar(binding.toolbar) + enableEdgeToEdge() + + setContent { + SysctlGuiTheme( + darkTheme = prefs.forceDark || isSystemInDarkTheme(), + contrastLevel = prefs.contrastLevel, + dynamicColor = prefs.dynamicColors + ) { + MainScreen() + } + } Handler(mainLooper).postDelayed(1000) { checkNotificationPermission() } - setUpNavigation() navigateFromIntent() } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> finish() - - R.id.action_exit -> { - moveTaskToBack(true) - finish() - } - } - - return false // Let fragments have a chance to consume it - } - - override fun onSupportNavigateUp(): Boolean { - return navHost.navController.navigateUp() || super.onSupportNavigateUp() - } - - private fun setUpNavigation() = with(binding) { - val navController = navHost.navController - val defaultIds = setOf( - R.id.navigationBrowse, - R.id.navigationList, - R.id.navigationExport, - R.id.navigationSettings - ) - val appBarConfiguration = AppBarConfiguration(defaultIds) - - toolbar.setupWithNavController(navController, appBarConfiguration) - navView?.setupWithNavController(navController) - navRail?.setupWithNavController(navController) - } - private fun navigateFromIntent() { - val fragmentName = intent.getStringExtra(EXTRA_DESTINATION) ?: return + // TODO: handle intent + /*val fragmentName = intent.getStringExtra(EXTRA_DESTINATION) ?: return when (fragmentName) { Actions.BrowseParams.name -> R.id.navigationBrowse Actions.ListParams.name -> R.id.navigationList @@ -81,11 +51,11 @@ class MainActivity : BaseAppCompatActivity() { else -> null }?.let { id -> navHost.navController.navigate(id) - } + }*/ } private fun checkNotificationPermission() { - val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return if (prefs.askedForNotificationPermission || !prefs.runOnStartUp) return if (manager.areNotificationsEnabled()) return @@ -94,9 +64,6 @@ class MainActivity : BaseAppCompatActivity() { prefs.askedForNotificationPermission = true } - private val navHost: NavHostFragment - get() = supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment - companion object { internal const val EXTRA_DESTINATION = "destination" } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt new file mode 100644 index 0000000..8e42edb --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt @@ -0,0 +1,107 @@ +package com.androidvip.sysctlgui.ui.main + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.outlined.Build +import androidx.compose.material.icons.outlined.FavoriteBorder +import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material.icons.rounded.Build +import androidx.compose.material.icons.rounded.Settings +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewDynamicColors +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.navigation.NavDestination.Companion.hasRoute +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.core.navigation.TopLevelRoute +import com.androidvip.sysctlgui.core.navigation.UiRoute +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme + +@Composable +internal fun MainNavBar(navController: NavHostController = rememberNavController()) { + val browseParamsTitle = stringResource(R.string.browse) + val presetsTitle = stringResource(R.string.presets) + val favoritesTitle = stringResource(R.string.favorites) + val settingsTitle = stringResource(R.string.settings) + val topLevelRoutes = remember { + listOf( + TopLevelRoute( + name = browseParamsTitle, + route = UiRoute.BrowseParams, + selectedIcon = Icons.Filled.Home, + unselectedIcon = Icons.Outlined.Home + ), + TopLevelRoute( + name = presetsTitle, + route = UiRoute.Presets, + selectedIcon = Icons.Rounded.Build, + unselectedIcon = Icons.Outlined.Build + ), + TopLevelRoute( + name = favoritesTitle, + route = UiRoute.Favorites, + selectedIcon = Icons.Filled.Favorite, + unselectedIcon = Icons.Outlined.FavoriteBorder + ), + TopLevelRoute( + name = settingsTitle, + route = UiRoute.Settings, + selectedIcon = Icons.Rounded.Settings, + unselectedIcon = Icons.Outlined.Settings + ) + ) + } + + NavigationBar { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + + topLevelRoutes.forEach { route -> + val selected = currentDestination + ?.hierarchy + ?.any { it.hasRoute(route.route::class) } == true + + NavigationBarItem( + icon = { + Icon( + imageVector = if (selected) route.selectedIcon else route.unselectedIcon, + contentDescription = route.name, + ) + }, + label = { Text(route.name) }, + selected = selected, + onClick = { + navController.navigate(route.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + ) + } + } +} + +@Composable +@PreviewLightDark +@PreviewDynamicColors +private fun MainNavbarPreview() { + SysctlGuiTheme(dynamicColor = true) { + MainNavBar() + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt new file mode 100644 index 0000000..079de14 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt @@ -0,0 +1,105 @@ +package com.androidvip.sysctlgui.ui.main + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.tooling.preview.PreviewDynamicColors +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import com.androidvip.sysctlgui.core.navigation.UiRoute +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import org.koin.androidx.compose.koinViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MainScreen(viewModel: MainViewModel = koinViewModel()) { + val state by viewModel.uiState.collectAsStateWithLifecycle() + val navController = rememberNavController() + val snackbarHostState = remember { SnackbarHostState() } + + LaunchedEffect(Unit) { + viewModel.effect.collect { effect -> + if (effect is MainViewEffect.ShowSnackbar) { + val result = snackbarHostState.showSnackbar( + message = effect.message, + actionLabel = effect.actionLabel + ) + viewModel.onEvent(MainViewEvent.OnSnackbarResult(result)) + } + } + } + + MainScreenContent(state, navController, snackbarHostState) +} + +@Composable +private fun MainScreenContent( + state: MainViewState, + navController: NavHostController, + snackbarHostState: SnackbarHostState +) { + Scaffold( + topBar = { + AnimatedVisibility( + visible = state.showTopBar, + enter = expandVertically() + slideInVertically() + fadeIn(), + exit = shrinkVertically() + slideOutVertically() + fadeOut(), + label = "TopBar" + ) { + MainTopBar( + title = state.topBarTitle, + showSearch = state.showSearchAction, + showBack = state.showBackButton, + onSearchPressed = { navController.navigate(UiRoute.Search) }, + onBackPressed = { navController.popBackStack() } + ) + } + }, + bottomBar = { + AnimatedVisibility( + visible = state.showNavBar, + enter = expandVertically() + slideInVertically { it / 2 } + fadeIn(), + exit = shrinkVertically() + slideOutVertically { it / 2 } + fadeOut(), + label = "BottomBar" + ) { + MainNavBar(navController = navController) + } + }, + snackbarHost = { + SnackbarHost(snackbarHostState) + }, + content = { innerPadding -> + AppNavHost( + innerPadding = innerPadding, + navController = navController + ) + } + ) +} + +@Composable +@PreviewLightDark +@PreviewDynamicColors +private fun MainScreenPreview() { + SysctlGuiTheme(dynamicColor = true) { + MainScreenContent( + state = MainViewState(), + navController = rememberNavController(), + snackbarHostState = remember { SnackbarHostState() } + ) + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainTopBar.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainTopBar.kt new file mode 100644 index 0000000..4b214ce --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainTopBar.kt @@ -0,0 +1,75 @@ +package com.androidvip.sysctlgui.ui.main + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandHorizontally +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkHorizontally +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.ArrowBack +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewLightDark +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MainTopBar( + title: String = stringResource(R.string.app_name), + showBack: Boolean = false, + showSearch: Boolean = false, + onSearchPressed: () -> Unit, + onBackPressed: () -> Unit +) { + TopAppBar( + navigationIcon = { + AnimatedVisibility(visible = showBack) { + IconButton(onClick = onBackPressed) { + Icon( + imageVector = Icons.AutoMirrored.Rounded.ArrowBack, + contentDescription = "Back" + ) + } + } + }, + title = { + Text(title, maxLines = 1, overflow = TextOverflow.Ellipsis) + }, + actions = { + AnimatedVisibility( + visible = showSearch, + enter = expandHorizontally() + fadeIn(), + exit = shrinkHorizontally() + fadeOut() + ) { + IconButton(onClick = onSearchPressed) { + Icon( + imageVector = Icons.Rounded.Search, + contentDescription = stringResource(R.string.search) + ) + } + } + } + ) +} + +@Composable +@PreviewLightDark +private fun MainTopBarPreview() { + SysctlGuiTheme { + MainTopBar( + title = "SysctlGUI", + showBack = false, + showSearch = true, + onSearchPressed = {}, + onBackPressed = {} + ) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewEffect.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewEffect.kt deleted file mode 100644 index e7b9ab6..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewEffect.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.androidvip.sysctlgui.ui.main - -sealed interface MainViewEffect { - object NavigateToKernelList : MainViewEffect - object NavigateToKernelBrowser : MainViewEffect - object ExportParams : MainViewEffect - object NavigateToFavorites : MainViewEffect - object NavigateToSettings : MainViewEffect -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewModel.kt index 6f5c92a..cccd608 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewModel.kt @@ -1,41 +1,26 @@ package com.androidvip.sysctlgui.ui.main -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.SettingsItem - -class MainViewModel : ViewModel() { - private val _viewEffect = MutableLiveData() - val viewEffect: LiveData = _viewEffect - - fun getHomeItems(): List = listOf( - SettingsItem(R.string.show_variables, R.string.show_variables_sum, R.drawable.ic_edit_outline), - SettingsItem( - R.string.browse_variables, - R.string.browse_variables_sum, - R.drawable.ic_folder_outline - ), - SettingsItem( - R.string.export_options, - R.string.export_options_sum, - R.drawable.ic_file_import_outline - ), - SettingsItem( - R.string.show_favorites, - R.string.show_favorites_sum, - R.drawable.ic_favorite_unselected - ) - ) - - fun doWhenListPressed() = _viewEffect.postValue(MainViewEffect.NavigateToKernelList) - - fun doWhenBrowsePressed() = _viewEffect.postValue(MainViewEffect.NavigateToKernelBrowser) - - fun doWhenImportPressed() = _viewEffect.postValue(MainViewEffect.ExportParams) - - fun doWhenFavoritesPressed() = _viewEffect.postValue(MainViewEffect.NavigateToFavorites) - - fun doWhenSettingsPressed() = _viewEffect.postValue(MainViewEffect.NavigateToSettings) +import androidx.compose.material3.SnackbarResult +import com.androidvip.sysctlgui.utils.BaseViewModel + +class MainViewModel : BaseViewModel() { + + override fun createInitialState() = MainViewState() + + override fun onEvent(event: MainViewEvent) { + when (event) { + is MainViewEvent.OnSateChangeRequested -> { + setState { event.newState } + } + is MainViewEvent.ShowSnackbarRequested -> { + setEffect { MainViewEffect.ShowSnackbar(event.message, event.actionLabel) } + } + is MainViewEvent.OnSnackbarResult -> { + val snackbarResult = event.result + if (snackbarResult == SnackbarResult.ActionPerformed) { + setEffect { MainViewEffect.ActUponSckbarActionPerformed } + } + } + } + } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt new file mode 100644 index 0000000..da18a59 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt @@ -0,0 +1,26 @@ +package com.androidvip.sysctlgui.ui.main + +import androidx.compose.material3.SnackbarResult + +data class MainViewState( + val topBarTitle: String = "SysctlGUI", + val showTopBar: Boolean = true, + val showNavBar: Boolean = true, + val showBackButton: Boolean = false, + val showSearchAction: Boolean = true +) + +sealed interface MainViewEffect { + data class ShowSnackbar(val message: String, val actionLabel: String? = null) : MainViewEffect + data object ActUponSckbarActionPerformed : MainViewEffect +} + +sealed interface MainViewEvent { + data class OnSateChangeRequested(val newState: MainViewState) : MainViewEvent + data class ShowSnackbarRequested( + val message: String, + val actionLabel: String? = null + ) : MainViewEvent + + data class OnSnackbarResult(val result: SnackbarResult) : MainViewEvent +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/DocumentationBottomSheet.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/DocumentationBottomSheet.kt new file mode 100644 index 0000000..8ba0aab --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/DocumentationBottomSheet.kt @@ -0,0 +1,157 @@ +package com.androidvip.sysctlgui.ui.params + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.SheetState +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +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.text.AnnotatedString +import androidx.compose.ui.text.TextLinkStyles +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.fromHtml +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import androidx.core.text.HtmlCompat +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.domain.models.ParamDocumentation +import com.androidvip.sysctlgui.utils.browse +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import org.intellij.lang.annotations.Language +import kotlin.text.append + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun DocumentationBottomSheet( + documentation: ParamDocumentation, + sheetState: SheetState +) { + val coroutineScope = rememberCoroutineScope() + ModalBottomSheet( + onDismissRequest = { coroutineScope.launch { sheetState.hide() } }, + sheetState = sheetState, + ) { + DocumentationBottomSheetContent( + documentation = documentation, + sheetState = sheetState + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun DocumentationBottomSheetContent( + documentation: ParamDocumentation, + sheetState: SheetState, + coroutineScope: CoroutineScope = rememberCoroutineScope(), +) { + Column(modifier = Modifier.padding(24.dp)) { + val context = LocalContext.current + Text( + text = documentation.title, + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface + ) + + val documentationText = if (!documentation.documentationHtml.isNullOrEmpty()) { + AnnotatedString.fromHtml( + htmlString = documentation.documentationHtml.orEmpty(), + linkStyles = TextLinkStyles( + style = MaterialTheme.typography.bodyMedium.toSpanStyle().copy( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline, + fontWeight = FontWeight.Medium + ), + pressedStyle = MaterialTheme.typography.bodyMedium.toSpanStyle().copy( + color = MaterialTheme.colorScheme.tertiary, + textDecoration = TextDecoration.Underline, + fontWeight = FontWeight.Medium + ) + ) + ) + } else { + AnnotatedString(documentation.documentationText) + } + Text( + text = documentationText, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(top = 16.dp) + ) + + if (documentation.url != null) { + TextButton( + onClick = { + context.browse(documentation.url.orEmpty()) + coroutineScope.launch { sheetState.hide() } + }, + modifier = Modifier + .align(alignment = Alignment.End) + .padding(top = 16.dp) + ) { + Icon( + painter = painterResource(R.drawable.ic_open_in_browser), + contentDescription = null, + modifier = Modifier.padding(end = 8.dp) + ) + Text(text = "Read more") + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +@PreviewLightDark +private fun DocumentationBottomSheetPreview() { + + @Language("HTML") + val htmlDocs = """ +

    + When BPF JIT compiler is enabled, then compiled images are unknown + addresses to the kernel, meaning they neither show up in traces nor + in /proc/kallsyms. This enables export of these addresses, which can + be used for debugging/tracing. If bpf_jit_harden is enabled, this + feature is disabled. +

    +

    Values :

    +
      +
    • 0 - disable JIT kallsyms export (default value)
    • +
    • 1 - enable JIT kallsyms export for privileged users only
    • +
    + """.trimIndent() + + val documentation = ParamDocumentation( + title = "/proc/sys/fs", + url = "https://docs.kernel.org/admin-guide/sysctl/fs.html", + documentationText = """ + The files in this directory can be used to tune and monitor miscellaneous and general + things in the operation of the Linux kernel. It is advisable to read both + documentation and source before actually making adjustments. + """.trimIndent(), + documentationHtml = htmlDocs + ) + + SysctlGuiTheme(dynamicColor = true) { + val state = rememberModalBottomSheetState() + + Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { + DocumentationBottomSheetContent(documentation = documentation, state) + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/EmptyParamsWarning.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/EmptyParamsWarning.kt deleted file mode 100644 index b63bf04..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/EmptyParamsWarning.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.androidvip.sysctlgui.ui.params - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Warning -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -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 com.androidvip.sysctlgui.R - -@Composable -fun EmptyParamsWarning() { - Box(modifier = Modifier.fillMaxSize()) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(24.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.errorContainer - ) - ) { - Row( - modifier = Modifier.padding(24.dp), - horizontalArrangement = Arrangement.spacedBy( - 16.dp, - Alignment.CenterHorizontally - ) - ) { - Icon( - imageVector = Icons.Outlined.Warning, - contentDescription = stringResource(android.R.string.dialog_alert_title), - tint = MaterialTheme.colorScheme.onErrorContainer - ) - Column { - Text( - text = stringResource(id = R.string.error), - style = MaterialTheme.typography.bodyLarge.copy( - fontWeight = FontWeight.Medium - ), - color = MaterialTheme.colorScheme.onErrorContainer - ) - Text( - text = stringResource(id = R.string.no_parameters_found), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onErrorContainer - ) - } - } - } - } -} - -@Composable -@Preview -private fun EmptyParamsWarningPreview() { - Box(modifier = Modifier.background(Color.White)) { - EmptyParamsWarning() - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/OnParamItemClickedListener.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/OnParamItemClickedListener.kt deleted file mode 100644 index caa8b76..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/OnParamItemClickedListener.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.androidvip.sysctlgui.ui.params - -import android.view.View -import com.androidvip.sysctlgui.data.models.KernelParam - -fun interface OnParamItemClickedListener { - fun onParamItemClicked(param: KernelParam, itemLayout: View) -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/OnPopUpMenuItemSelectedListener.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/OnPopUpMenuItemSelectedListener.kt deleted file mode 100644 index 34aa6e5..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/OnPopUpMenuItemSelectedListener.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.androidvip.sysctlgui.ui.params - -import androidx.annotation.IdRes -import androidx.constraintlayout.widget.ConstraintLayout -import com.androidvip.sysctlgui.data.models.KernelParam - -interface OnPopUpMenuItemSelectedListener { - fun onPopUpMenuItemSelected( - kernelParam: KernelParam, - @IdRes itemId: Int, - removableLayout: ConstraintLayout - ) -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/BrowseParamsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/BrowseParamsViewModel.kt deleted file mode 100644 index d92bb99..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/BrowseParamsViewModel.kt +++ /dev/null @@ -1,161 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.browse - -import androidx.lifecycle.viewModelScope -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.mapper.DomainParamMapper -import com.androidvip.sysctlgui.domain.repository.AppPrefs -import com.androidvip.sysctlgui.domain.usecase.GetParamsFromFilesUseCase -import com.androidvip.sysctlgui.utils.BaseViewModel -import com.androidvip.sysctlgui.utils.Consts -import com.topjohnwu.superuser.io.SuFile -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.File - -class BrowseParamsViewModel( - private val getParamsFromFilesUseCase: GetParamsFromFilesUseCase, - appPrefs: AppPrefs, - private val dispatcher: CoroutineDispatcher = Dispatchers.IO -) : BaseViewModel() { - private var listFoldersFirst = true - private var searchExpression = "" - - init { - listFoldersFirst = appPrefs.listFoldersFirst - } - - override fun createInitialState(): ParamBrowserViewState = ParamBrowserViewState() - - override fun onEvent(event: ParamBrowserViewEvent) { - when (event) { - ParamBrowserViewEvent.RefreshRequested -> setPath(currentState.currentPath) - is ParamBrowserViewEvent.DirectoryChanged -> onDirectoryChanged(event.dir) - is ParamBrowserViewEvent.SearchExpressionChanged -> onSearchExpressionChanged(event.data) - is ParamBrowserViewEvent.ParamClicked -> setEffect { - ParamBrowserViewEffect.NavigateToParamDetails(DomainParamMapper.map(event.param)) - } - - ParamBrowserViewEvent.DocumentationMenuClicked -> setEffect { - ParamBrowserViewEffect.OpenDocumentationUrl(currentState.docUrl) - } - - ParamBrowserViewEvent.FavoritesMenuClicked -> setEffect { - ParamBrowserViewEffect.NavigateToFavorite - } - } - } - - private fun setPath(path: String) { - viewModelScope.launch { - loadBrowsableParamFiles(path) - } - } - - private fun onDirectoryChanged(newDir: File) { - val newPath = newDir.absolutePath - if (newPath.isEmpty() || !newPath.startsWith(Consts.PROC_SYS)) { - setEffect { ParamBrowserViewEffect.ShowToast(R.string.invalid_path) } - return - } - - setPath(newPath) - - when { - newPath.startsWith("/proc/sys/abi") -> setState { - copy( - docUrl = "https://www.kernel.org/doc/Documentation/sysctl/abi.txt", - showDocumentationMenu = true - ) - } - - newPath.startsWith("/proc/sys/fs") -> setState { - copy( - docUrl = "https://www.kernel.org/doc/Documentation/sysctl/fs.txt", - showDocumentationMenu = true - ) - } - - newPath.startsWith("/proc/sys/kernel") -> setState { - copy( - docUrl = "https://www.kernel.org/doc/Documentation/sysctl/kernel.txt", - showDocumentationMenu = true - ) - } - - newPath.startsWith("/proc/sys/net") -> setState { - copy( - docUrl = "https://www.kernel.org/doc/Documentation/sysctl/net.txt", - showDocumentationMenu = true - ) - } - - newPath.startsWith("/proc/sys/vm") -> setState { - copy( - docUrl = "https://www.kernel.org/doc/Documentation/sysctl/vm.txt", - showDocumentationMenu = true - ) - } - - else -> setState { copy(showDocumentationMenu = false) } - } - } - - private suspend fun getCurrentPathFiles(path: String) = withContext(dispatcher) { - runCatching { - val baseFile = File(path) - val file = if (baseFile.canRead()) baseFile else SuFile.open(path) - file.listFiles()?.toList() - }.getOrDefault(emptyList()) - } - - private suspend fun loadBrowsableParamFiles(path: String) { - setState { copy(isLoading = true) } - val files = getCurrentPathFiles(path).maybeDirectorySorted() - val params = getParamsFromFilesUseCase(files).map { - DomainParamMapper.map(it) - } - - setState { - copy( - currentPath = path, - isLoading = false, - data = params.filter { param -> byName(param.name, searchExpression) }, - totalData = params - ) - } - } - - private suspend fun List?.maybeDirectorySorted() = withContext(dispatcher) { - return@withContext this@maybeDirectorySorted?.run { - if (listFoldersFirst) { - sortedByDescending { it.isDirectory } - } else { - this - } - }?.toList().orEmpty() - } - - private fun onSearchExpressionChanged(expression: String) { - searchExpression = expression - - setState { - copy(data = this.totalData.filter { kernelParam -> - byName( - kernelParam.name, - searchExpression - ) - }) - } - } - - private fun byName(current: String, expected: String): Boolean { - if (expected.isEmpty()) { - return true - } - return current.lowercase() - .replace(".", "") - .contains(expected.lowercase()) - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/KernelParamBrowseFragment.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/KernelParamBrowseFragment.kt deleted file mode 100644 index 02f6406..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/KernelParamBrowseFragment.kt +++ /dev/null @@ -1,277 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.browse - -import android.annotation.SuppressLint -import android.app.Dialog -import android.os.Bundle -import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import android.view.Window -import android.webkit.WebChromeClient -import android.webkit.WebSettings -import android.webkit.WebView -import android.webkit.WebViewClient -import android.widget.ProgressBar -import androidx.activity.OnBackPressedCallback -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.pullrefresh.PullRefreshIndicator -import androidx.compose.material.pullrefresh.pullRefresh -import androidx.compose.material.pullrefresh.rememberPullRefreshState -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.KernelParam -import com.androidvip.sysctlgui.design.DesignIds -import com.androidvip.sysctlgui.design.DesignLayouts -import com.androidvip.sysctlgui.getColorRoles -import com.androidvip.sysctlgui.goAway -import com.androidvip.sysctlgui.show -import com.androidvip.sysctlgui.toast -import com.androidvip.sysctlgui.ui.base.BaseSearchFragment -import com.androidvip.sysctlgui.ui.params.EmptyParamsWarning -import com.androidvip.sysctlgui.ui.params.OnParamItemClickedListener -import com.androidvip.sysctlgui.ui.params.edit.EditKernelParamActivity -import com.androidvip.sysctlgui.utils.ComposeTheme -import com.androidvip.sysctlgui.utils.Consts -import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject -import java.io.File - -class KernelParamBrowseFragment : BaseSearchFragment(), OnParamItemClickedListener { - private var actionBarMenu: Menu? = null - private val viewModel: BrowseParamsViewModel by inject() - private val currentPath: String get() = viewModel.currentState.currentPath - private val canGoBack: Boolean get() = currentPath != Consts.PROC_SYS - - private val onBackPressedCallback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - if (canGoBack) { - onDirectoryChanged(File(currentPath).parentFile ?: File(Consts.PROC_SYS)) - } - } - } - - @OptIn(ExperimentalMaterialApi::class) - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - return ComposeView(requireContext()).apply { - setContent { - ComposeTheme { - val state by viewModel.uiState.collectAsStateWithLifecycle() - val refreshing = state.isLoading - val refreshState = rememberPullRefreshState( - refreshing = refreshing, - onRefresh = { refresh() } - ) - - actionBarMenu - ?.findItem(R.id.action_documentation) - ?.isVisible = state.showDocumentationMenu - - Box(Modifier.pullRefresh(refreshState)) { - if (state.showEmptyState) { - EmptyParamsWarning() - } else { - KernelParamsExplorer(state.data) - } - - PullRefreshIndicator( - modifier = Modifier.align(Alignment.TopCenter), - refreshing = refreshing, - state = refreshState, - backgroundColor = MaterialTheme.colorScheme.tertiaryContainer, - contentColor = MaterialTheme.colorScheme.onTertiaryContainer - ) - } - - SideEffect { onBackPressedCallback.isEnabled = canGoBack } - } - } - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - lifecycleScope.launch { - viewModel.onEvent(ParamBrowserViewEvent.DirectoryChanged(File(Consts.PROC_SYS))) - viewModel.effect.collect(::handleViewEffect) - } - - requireActivity().onBackPressedDispatcher.addCallback(onBackPressedCallback) - } - - override fun onStart() { - super.onStart() - refresh() - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.menu_browse_params, menu) - actionBarMenu = menu - - setUpSearchView(menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.action_documentation -> { - viewModel.onEvent(ParamBrowserViewEvent.DocumentationMenuClicked) - } - R.id.action_favorites -> { - viewModel.onEvent(ParamBrowserViewEvent.FavoritesMenuClicked) - } - else -> return false - } - - return true - } - - override fun onQueryTextChanged() { - viewModel.onEvent(ParamBrowserViewEvent.SearchExpressionChanged(searchExpression)) - } - - override fun onParamItemClicked(param: KernelParam, itemLayout: View) { - viewModel.onEvent(ParamBrowserViewEvent.ParamClicked(param)) - } - - private fun onDirectoryChanged(newDir: File) { - viewModel.onEvent(ParamBrowserViewEvent.DirectoryChanged(newDir)) - resetSearchExpression() - } - - private fun handleViewEffect(viewEffect: ParamBrowserViewEffect) { - when (viewEffect) { - is ParamBrowserViewEffect.NavigateToParamDetails -> { - navigateToParamDetails(viewEffect.param) - } - is ParamBrowserViewEffect.NavigateToFavorite -> { - findNavController().navigate(R.id.navigateFavoritesParams) - } - is ParamBrowserViewEffect.OpenDocumentationUrl -> openDocumentationUrl(viewEffect.url) - is ParamBrowserViewEffect.ShowToast -> toast(viewEffect.stringRes) - } - } - - private fun navigateToParamDetails(param: KernelParam) { - startActivity(EditKernelParamActivity.getIntent(requireContext(), param)) - } - - private fun refresh() { - viewModel.onEvent(ParamBrowserViewEvent.RefreshRequested) - } - - @SuppressLint("SetJavaScriptEnabled") - private fun openDocumentationUrl(url: String) { - if (!isAdded) return - - val dialog = Dialog(requireContext()).apply { - requestWindowFeature(Window.FEATURE_NO_TITLE) - setContentView(DesignLayouts.dialog_web) - setCancelable(true) - } - - val progressBar: ProgressBar = dialog.findViewById(DesignIds.webDialogProgress) - val swipeLayout: SwipeRefreshLayout = dialog.findViewById(DesignIds.webDialogSwipeLayout) - - val webView = dialog.findViewById(DesignIds.webDialogWebView).apply { - val colorRoles = getColorRoles() - settings.apply { - javaScriptEnabled = true - cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK - } - - loadUrl(url) - - webViewClient = object : WebViewClient() { - override fun onPageFinished(view: WebView, url: String) { - super.onPageFinished(view, url) - swipeLayout.isRefreshing = false - - val containerColorInt = colorRoles.accentContainer - val colorInt = colorRoles.onAccentContainer - - val containerColorHex = "#%06X".format(0xFFFFFF and containerColorInt) - val colorHex = "#%06X".format(0xFFFFFF and colorInt) - // Change webView background and text color to match the app theme - view.loadUrl( - """ - |javascript:( - |function() { - |document.querySelector('body').style.color='$colorHex'; - |document.querySelector('body').style.background='$containerColorHex'; - |} - |)() - """.trimMargin() - ) - } - } - - webChromeClient = object : WebChromeClient() { - override fun onProgressChanged(view: WebView, progress: Int) { - progressBar.progress = progress - if (progress == 100) { - progressBar.goAway() - swipeLayout.isRefreshing = false - } else { - progressBar.show() - } - } - } - } - - swipeLayout.apply { - val roles = getColorRoles() - setColorSchemeColors(roles.accent) - setProgressBackgroundColorSchemeColor(roles.accentContainer) - - setOnRefreshListener { webView.reload() } - } - - dialog.show() - } - - @Composable - private fun KernelParamsExplorer(params: List) { - LazyColumn { - itemsIndexed(params) { index, param -> - ParamBrowseItem( - onParamClick = { - viewModel.onEvent(ParamBrowserViewEvent.ParamClicked(param)) - }, - onDirectoryChanged = { - viewModel.onEvent(ParamBrowserViewEvent.DirectoryChanged(it)) - }, - param = param, - paramFile = File(param.path) - ) - if (index < params.lastIndex) { - HorizontalDivider( - thickness = 1.dp, - color = MaterialTheme.colorScheme.outlineVariant - ) - } - } - } - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseItem.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseItem.kt deleted file mode 100644 index 13e5349..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseItem.kt +++ /dev/null @@ -1,100 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.browse - -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -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.painterResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.KernelParam -import com.androidvip.sysctlgui.design.theme.md_theme_light_background -import java.io.File - -@Composable -fun ParamBrowseItem( - onParamClick: (KernelParam) -> Unit, - onDirectoryChanged: (File) -> Unit, - param: KernelParam, - paramFile: File -) { - val isDir = paramFile.isDirectory - val outlineColor = MaterialTheme.colorScheme.outlineVariant - val surfaceColor = MaterialTheme.colorScheme.surfaceVariant - val tintColor = if (isDir) { - MaterialTheme.colorScheme.onSurfaceVariant - } else { - MaterialTheme.colorScheme.onSurface - } - - Box( - modifier = Modifier - .clickable { - if (isDir) onDirectoryChanged(paramFile) else onParamClick(param) - } - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - Box(contentAlignment = Alignment.Center) { - Canvas(modifier = Modifier.size(42.dp), onDraw = { - drawCircle(color = if (isDir) surfaceColor else outlineColor) - }) - - val iconResource = if (isDir) { - R.drawable.ic_folder_outline - } else { - R.drawable.ic_file_outline - } - Icon( - painter = painterResource(id = iconResource), - tint = tintColor, - contentDescription = "" - ) - } - Text( - text = param.shortName, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onBackground, - style = if (isDir) { - MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Medium) - } else { - MaterialTheme.typography.bodyMedium - } - ) - } - } -} - -@Preview -@Composable -fun ParamItemPreview() { - val param = KernelParam(name = "test", value = "success") - Box(modifier = Modifier.background(md_theme_light_background)) { - ParamBrowseItem( - onParamClick = {}, - onDirectoryChanged = {}, - param = param, - paramFile = File("/") - ) - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt new file mode 100644 index 0000000..5ff042e --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt @@ -0,0 +1,313 @@ +package com.androidvip.sysctlgui.ui.params.browse + +import android.widget.Toast +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +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.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.models.ParamDocumentation +import com.androidvip.sysctlgui.models.UiKernelParam +import com.androidvip.sysctlgui.ui.main.MainViewEvent +import com.androidvip.sysctlgui.ui.main.MainViewModel +import com.androidvip.sysctlgui.ui.main.MainViewState +import com.androidvip.sysctlgui.ui.params.DocumentationBottomSheet +import com.androidvip.sysctlgui.utils.browse +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import org.koin.compose.viewmodel.koinViewModel +import java.io.File + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ParamBrowseScreen( + mainViewModel: MainViewModel = koinViewModel(), + viewModel: ParamBrowseViewModel = koinViewModel(), + onParamSelected: (KernelParam) -> Unit +) { + var documentation by remember { mutableStateOf(null) } + val documentationSheetState = rememberModalBottomSheetState() + val context = LocalContext.current + val state by viewModel.uiState.collectAsStateWithLifecycle() + + LaunchedEffect(state) { + mainViewModel.onEvent( + MainViewEvent.OnSateChangeRequested( + MainViewState( + showTopBar = true, + showNavBar = true, + showBackButton = state.backEnabled, + showSearchAction = true + ) + ) + ) + } + + LaunchedEffect(viewModel.effect) { + viewModel.effect.collect { effect -> + when (effect) { + is ParamBrowseViewEffect.EditKernelParam -> onParamSelected(effect.param) + is ParamBrowseViewEffect.OpenBrowser -> context.browse(effect.url) + is ParamBrowseViewEffect.ShowError -> Toast.makeText( + context, + effect.errorMessage, + Toast.LENGTH_SHORT + ).show() + } + } + } + + ParamBrowseScreenContent( + params = state.params, + currentPath = state.currentPath, + documentation = state.documentation, + onParamClicked = { + viewModel.onEvent(ParamBrowseViewEvent.ParamClicked(it)) + }, + onDocumentationClicked = { + viewModel.onEvent(ParamBrowseViewEvent.DocumentationClicked(it)) + }, + backEnabled = state.backEnabled, + onBackPressed = { + viewModel.onEvent(ParamBrowseViewEvent.BackRequested) + } + ) + + documentation?.let { + DocumentationBottomSheet( + documentation = it, + sheetState = documentationSheetState + ) + } +} + +@Composable +private fun ParamBrowseScreenContent( + params: List, + currentPath: String, + documentation: ParamDocumentation?, + onParamClicked: (UiKernelParam) -> Unit, + onDocumentationClicked: (ParamDocumentation) -> Unit, + backEnabled: Boolean = false, + onBackPressed: () -> Unit, +) { + val listState = rememberLazyListState() + var headerVisible by remember { mutableStateOf(backEnabled) } + + BackHandler(enabled = backEnabled, onBack = onBackPressed) + + LaunchedEffect(listState) { + var previousOffset = listState.firstVisibleItemScrollOffset + var previousIndex = listState.firstVisibleItemIndex + + snapshotFlow { listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset } + .map { (currentIndex, currentOffset) -> + when { + currentIndex > previousIndex -> false + currentIndex < previousIndex -> true + currentOffset > previousOffset -> false + currentOffset < previousOffset -> true + else -> null // No change or unable to determine (keep current state) + }.also { + previousIndex = currentIndex + previousOffset = currentOffset + } + } + .filter { it != null } + .distinctUntilChanged() + .collect { scrolledUp -> + headerVisible = scrolledUp ?: headerVisible + } + } + + val isAtTop by remember { + derivedStateOf { + listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0 + } + } + + val finalHeaderVisible = (headerVisible || isAtTop) && backEnabled + + Box(modifier = Modifier.fillMaxSize()) { + val spacerHeight by animateDpAsState(if (finalHeaderVisible) 56.dp else 0.dp) + LazyColumn( + state = listState, + modifier = Modifier.fillMaxSize() + ) { + item { Spacer(modifier = Modifier.height(spacerHeight)) } + + items( + count = params.size, + key = { index -> params[index].name } + ) { index -> + ParamFileRow( + modifier = Modifier.animateItem(), + param = params[index], + onParamClicked = onParamClicked, + ) + } + + if (documentation != null) { + item { Spacer(modifier = Modifier.height(56.dp)) } + } + } + + AnimatedVisibility( + visible = finalHeaderVisible, + enter = slideInVertically(initialOffsetY = { -it }) + fadeIn(), + exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut(), + modifier = Modifier.align(Alignment.TopCenter) + ) { + InfoItem( + text = currentPath, + icon = painterResource(R.drawable.ic_arrow_upward), + onClicked = onBackPressed + ) + } + + AnimatedVisibility( + visible = finalHeaderVisible && documentation != null, + enter = slideInVertically(initialOffsetY = { it }) + fadeIn(), + exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(), + modifier = Modifier.align(Alignment.BottomCenter) + ) { + InfoItem( + text = "Read documentation for \"${documentation?.title}\"", + textStyle = MaterialTheme.typography.titleSmall.copy( + textDecoration = TextDecoration.Underline, + color = MaterialTheme.colorScheme.primary + ), + icon = painterResource(R.drawable.ic_documentation), + onClicked = { onDocumentationClicked(documentation!!) } + ) + } + } +} + +@Composable +private fun InfoItem( + modifier: Modifier = Modifier, + text: String, + textStyle: TextStyle = MaterialTheme.typography.titleSmall.copy( + color = MaterialTheme.colorScheme.onSurfaceVariant + ), + icon: Painter, + onClicked: () -> Unit, +) { + val context = LocalContext.current + Row( + modifier = modifier + .fillMaxWidth() + .combinedClickable( + role = Role.Button, + onClick = onClicked, + onLongClick = { Toast.makeText(context, text, Toast.LENGTH_SHORT).show() }, + ) + .background(MaterialTheme.colorScheme.surfaceContainer) + .padding(horizontal = 16.dp, vertical = 12.dp) + .semantics(mergeDescendants = true) { this.contentDescription = text }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + painter = icon, + contentDescription = null, + tint = textStyle.color + ) + Text( + text = text, + style = textStyle, + maxLines = 2, + overflow = TextOverflow.MiddleEllipsis + ) + } +} + +@Composable +@PreviewLightDark +internal fun ParamBrowseScreenContentPreview() { + fun mapFilesToParams(files: Array?): List { + return files?.map { file -> + UiKernelParam( + name = file.name, + path = file.path, + value = "", + isFavorite = (0..5).random() % 2 == 0 + ) + } ?: emptyList() + } + + val root = File("/") + var currentPath by remember { mutableStateOf(root.path) } + var params by remember(currentPath) { + mutableStateOf(mapFilesToParams(File(currentPath).listFiles())) + } + + SysctlGuiTheme(dynamicColor = true) { + Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { + ParamBrowseScreenContent( + params = params, + currentPath = currentPath, + documentation = ParamDocumentation( + title = currentPath, + documentationText = "Documentation for $currentPath", + url = null + ), + onParamClicked = { + if (it.isDirectory) { + currentPath = it.path + params = mapFilesToParams(File(it.path).listFiles()) + } + }, + onDocumentationClicked = {}, + backEnabled = currentPath != root.path, + onBackPressed = { currentPath = File(currentPath).parent ?: root.path }, + ) + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseState.kt new file mode 100644 index 0000000..a141586 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseState.kt @@ -0,0 +1,23 @@ +package com.androidvip.sysctlgui.ui.params.browse + +import com.androidvip.sysctlgui.domain.models.ParamDocumentation +import com.androidvip.sysctlgui.models.UiKernelParam + +data class ParamBrowseState( + val params: List = emptyList(), + val currentPath: String = "", + val backEnabled: Boolean = false, + val documentation: ParamDocumentation? = null +) + +sealed interface ParamBrowseViewEffect { + data class OpenBrowser(val url: String) : ParamBrowseViewEffect + data class EditKernelParam(val param: UiKernelParam) : ParamBrowseViewEffect + data class ShowError(val errorMessage: String) : ParamBrowseViewEffect +} + +sealed interface ParamBrowseViewEvent { + data class ParamClicked(val param: UiKernelParam) : ParamBrowseViewEvent + data class DocumentationClicked(val docs: ParamDocumentation) : ParamBrowseViewEvent + object BackRequested : ParamBrowseViewEvent +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt new file mode 100644 index 0000000..f3b49d9 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt @@ -0,0 +1,115 @@ +package com.androidvip.sysctlgui.ui.params.browse + +import androidx.lifecycle.viewModelScope +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.repository.AppPrefs +import com.androidvip.sysctlgui.domain.usecase.GetParamDocumentationUseCase +import com.androidvip.sysctlgui.domain.usecase.GetParamsFromFilesUseCase +import com.androidvip.sysctlgui.helpers.UiKernelParamMapper +import com.androidvip.sysctlgui.models.UiKernelParam +import com.androidvip.sysctlgui.utils.BaseViewModel +import com.androidvip.sysctlgui.utils.Consts +import com.topjohnwu.superuser.nio.FileSystemManager +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File + +class ParamBrowseViewModel( + private val getParamsFromFiles: GetParamsFromFilesUseCase, + private val getParamDocumentation: GetParamDocumentationUseCase, + private val appPrefs: AppPrefs, + private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO +) : BaseViewModel() { + override fun createInitialState() = ParamBrowseState() + + init { + viewModelScope.launch { + val startingDirectory = File(Consts.PROC_SYS) + val params = getParams(startingDirectory) + + setState { + copy( + params = params, + currentPath = startingDirectory.absolutePath + ) + } + } + } + + override fun onEvent(event: ParamBrowseViewEvent) { + when (event) { + is ParamBrowseViewEvent.DocumentationClicked -> setEffect { + ParamBrowseViewEffect.OpenBrowser(event.docs.url.orEmpty()) + } + + ParamBrowseViewEvent.BackRequested -> onBackRequested() + is ParamBrowseViewEvent.ParamClicked -> onParamClicked(event.param) + } + } + + private fun fetchChildParams(parentParam: UiKernelParam) { + viewModelScope.launch { + runCatching { + val newParamsDeferred = async { getParams(File(parentParam.path)) } + val directoryDocumentationDeferred = async { getParamDocumentation(parentParam) } + + val newParams = newParamsDeferred.await() + val directoryDocumentation = directoryDocumentationDeferred.await() + + setState { + copy( + params = newParams, + currentPath = parentParam.path, + backEnabled = parentParam.path != Consts.PROC_SYS, + documentation = directoryDocumentation + ) + } + }.onFailure { + setEffect { ParamBrowseViewEffect.ShowError(it.message ?: "Unknown error") } + } + } + } + + private fun fetchChildParams(parentPath: String) { + val param = KernelParam.createFromPath(parentPath, "") + fetchChildParams(UiKernelParamMapper.map(param)) + } + + private fun onParamClicked(param: UiKernelParam) { + if (param.isDirectory) { + fetchChildParams(param) + } else { + setEffect { + ParamBrowseViewEffect.EditKernelParam(param) + } + } + } + + private fun onBackRequested() { + val currentPath = currentState.currentPath + if (currentPath == Consts.PROC_SYS) return + val parentFile = File(currentPath).parentFile ?: return + + fetchChildParams(parentFile.absolutePath) + } + + private suspend fun getParams(file: File): List = withContext(ioDispatcher) { + val fileList = if (file.canRead()) { + file.listFiles()?.toList() ?: emptyList() + } else { + val rootAwareFile = FileSystemManager.getLocal().getFile(file.absolutePath) + rootAwareFile.listFiles()?.toList() ?: emptyList() + } + + val params = getParamsFromFiles(fileList).map(UiKernelParamMapper::map) + + if (appPrefs.listFoldersFirst) { + params.sortedByDescending { it.isDirectory } + } else { + params + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowserViewEffect.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowserViewEffect.kt deleted file mode 100644 index b258e95..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowserViewEffect.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.browse - -import androidx.annotation.StringRes -import com.androidvip.sysctlgui.data.models.KernelParam - -sealed class ParamBrowserViewEffect { - object NavigateToFavorite : ParamBrowserViewEffect() - class NavigateToParamDetails(val param: KernelParam) : ParamBrowserViewEffect() - class OpenDocumentationUrl(val url: String) : ParamBrowserViewEffect() - class ShowToast(@StringRes val stringRes: Int) : ParamBrowserViewEffect() -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowserViewEvent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowserViewEvent.kt deleted file mode 100644 index ae5ea92..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowserViewEvent.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.browse - -import com.androidvip.sysctlgui.domain.models.DomainKernelParam -import java.io.File - -sealed interface ParamBrowserViewEvent { - object RefreshRequested : ParamBrowserViewEvent - class SearchExpressionChanged(val data: String) : ParamBrowserViewEvent - class ParamClicked(val param: DomainKernelParam) : ParamBrowserViewEvent - class DirectoryChanged(val dir: File) : ParamBrowserViewEvent - object DocumentationMenuClicked : ParamBrowserViewEvent - object FavoritesMenuClicked : ParamBrowserViewEvent -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowserViewState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowserViewState.kt deleted file mode 100644 index be18a2c..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowserViewState.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.browse - -import com.androidvip.sysctlgui.data.models.KernelParam -import com.androidvip.sysctlgui.utils.Consts - -data class ParamBrowserViewState( - var data: List = listOf(), - var totalData: List = listOf(), - var isLoading: Boolean = true, - var showEmptyState: Boolean = false, - var currentPath: String = Consts.PROC_SYS, - var showDocumentationMenu: Boolean = false, - var docUrl: String = "https://www.kernel.org/doc/Documentation" -) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamFileRow.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamFileRow.kt new file mode 100644 index 0000000..43f299c --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamFileRow.kt @@ -0,0 +1,159 @@ +package com.androidvip.sysctlgui.ui.params.browse + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight +import androidx.compose.material.icons.rounded.Favorite +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewDynamicColors +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.models.UiKernelParam + +@Composable +fun ParamFileRow( + modifier: Modifier = Modifier, + param: UiKernelParam, + onParamClicked: (UiKernelParam) -> Unit, + showFavoriteIcon: Boolean = true, +) { + Box(modifier = Modifier.clickable { onParamClicked(param) }) { + val rowDescription = if (param.isDirectory) { + "Directory: ${param.name}" + } else { + "Parameter: ${param.name}" + } + Row( + modifier = modifier + .semantics(mergeDescendants = true) { contentDescription = rowDescription } + .padding(horizontal = 16.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + ParamIcon(param = param) + + Column(modifier = Modifier.weight(1f)) { + Text( + text = param.lastNameSegment, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onBackground, + fontWeight = if (param.isDirectory) FontWeight.Bold else FontWeight.Normal + ) + + if (param.value.isNotBlank() && !param.isDirectory) { + Text( + text = param.value, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + + TrailingIcon(param = param, showFavoriteIcon = showFavoriteIcon) + } + } +} + + +@Composable +private fun ParamIcon(param: UiKernelParam) { + val primaryContainerColor = MaterialTheme.colorScheme.primaryContainer + val secondaryContainerColor = MaterialTheme.colorScheme.secondaryContainer + val containerColor by remember(param.isDirectory) { + derivedStateOf { + if (param.isDirectory) primaryContainerColor else secondaryContainerColor + } + } + + val onPrimaryContainerColor = MaterialTheme.colorScheme.onPrimaryContainer + val onSecondaryContainerColor = MaterialTheme.colorScheme.onSecondaryContainer + val iconColor by remember(param.isDirectory) { + derivedStateOf { + if (param.isDirectory) onPrimaryContainerColor else onSecondaryContainerColor + } + } + + val iconId = if (param.isDirectory) R.drawable.ic_folder else R.drawable.ic_file + + Box( + modifier = Modifier + .size(42.dp) + .clip(CircleShape) + .background(containerColor), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(iconId), + contentDescription = "Parameter icon", + modifier = Modifier.size(24.dp), + tint = iconColor + ) + } +} + +@Composable +private fun TrailingIcon(param: UiKernelParam, showFavoriteIcon: Boolean) { + if (param.isDirectory) { + Icon( + imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowRight, + contentDescription = "Navigate do directory", + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } else if (param.isFavorite && showFavoriteIcon) { + Icon( + imageVector = Icons.Rounded.Favorite, + contentDescription = "Favorite", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(18.dp) + ) + } +} + +@Composable +@PreviewLightDark +@PreviewDynamicColors +private fun ParamFileRowPreview() { + val param = UiKernelParam( + name = "vm.swappiness", + path = "/proc/sys/vm/swappiness", + value = "0" + ) + + SysctlGuiTheme(dynamicColor = true) { + Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { + ParamFileRow(param = param.copy(path = "C://"), onParamClicked = {}) + ParamFileRow(param = param.copy(path = "/home"), onParamClicked = {}) + ParamFileRow(param = param, onParamClicked = {}) + ParamFileRow(param = param.copy(isFavorite = true), onParamClicked = {}) + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamRow.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamRow.kt new file mode 100644 index 0000000..da3c96b --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamRow.kt @@ -0,0 +1,106 @@ +package com.androidvip.sysctlgui.ui.params.browse + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Favorite +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.models.UiKernelParam + +@Composable +fun ParamRow( + modifier: Modifier = Modifier, + param: UiKernelParam, + onParamClicked: (UiKernelParam) -> Unit, + showFullName: Boolean = false +) { + val rowDescription = "Parameter: ${param.name}" + val rowState = if (param.isFavorite) "Marked as favorite" else "" + + Row( + modifier = modifier + .heightIn(min = 64.dp) + .clickable { onParamClicked(param) }, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .semantics(mergeDescendants = showFullName) { + this.contentDescription = rowDescription + this.stateDescription = rowState + } + .padding(16.dp) + .weight(1f), + verticalArrangement = Arrangement.spacedBy(2.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = if (showFullName) param.name else param.lastNameSegment, + modifier = Modifier.fillMaxWidth(), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onBackground, + fontWeight = FontWeight.Medium + ) + if (param.value.isNotBlank()) { + Text( + text = param.value, + modifier = Modifier.fillMaxWidth(), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.bodyMedium, + ) + } + } + + if (param.isFavorite) { + Icon( + imageVector = Icons.Rounded.Favorite, + contentDescription = null, + tint = MaterialTheme.colorScheme.secondary, + modifier = Modifier + .padding(end = 16.dp) + .size(18.dp) + ) + } + } +} + +@Composable +@PreviewLightDark +private fun ParamRowPreview() { + val param = UiKernelParam( + name = "vm.swappiness", + path = "/proc/sys/vm/swappiness", + value = "0" + ) + + SysctlGuiTheme(contrastLevel = 1) { + Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { + ParamRow(param = param, onParamClicked = {}, showFullName = true) + ParamRow(param = param.copy(isFavorite = true), onParamClicked = {},) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/ActionToggleButton.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/ActionToggleButton.kt new file mode 100644 index 0000000..3bf22ef --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/ActionToggleButton.kt @@ -0,0 +1,188 @@ +package com.androidvip.sysctlgui.ui.params.edit + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.FloatingActionButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.PreviewDynamicColors +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme + +@Composable +internal fun ActionToggleButton( + modifier: Modifier = Modifier, + isActive: Boolean, + iconOnActive: Painter, + iconOnInactive: Painter, + contentDescription: String? = null, + onToggle: (Boolean) -> Unit, +) { + val containerColor by animateColorAsState( + targetValue = if (isActive) { + MaterialTheme.colorScheme.secondary + } else { + MaterialTheme.colorScheme.background + }, + label = "FabContainerColor" + ) + + val defaultElevation by animateDpAsState( + targetValue = if (isActive) 4.dp else 2.dp, + label = "FabElevation" + ) + + FloatingActionButton( + modifier = modifier, + onClick = { onToggle(!isActive) }, + containerColor = containerColor, + elevation = FloatingActionButtonDefaults.elevation( + defaultElevation = defaultElevation, + pressedElevation = defaultElevation * 2 + ), + shape = CircleShape, + ) { + AnimatedContent( + targetState = isActive, + label = "ActionToggleButtonAnimation", + transitionSpec = { + val enterTransition = scaleIn( + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ), + initialScale = 1.25f + ) + fadeIn(animationSpec = tween(durationMillis = 200)) + + val exitTransition = scaleOut( + animationSpec = tween(durationMillis = 150), + targetScale = 1.25f + ) + fadeOut(animationSpec = tween(durationMillis = 100)) + + enterTransition togetherWith exitTransition + } + ) { isCurrentlyActive -> + val iconTint = if (isCurrentlyActive) { + MaterialTheme.colorScheme.onSecondary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + } + + Icon( + painter = if (isCurrentlyActive) iconOnActive else iconOnInactive, + contentDescription = "Toggle $contentDescription", + tint = iconTint + ) + } + } +} + +@Composable +internal fun FavoriteButton( + modifier: Modifier = Modifier, + isFavorite: Boolean, + onFavoriteClick: (Boolean) -> Unit, +) { + ActionToggleButton( + modifier = modifier, + isActive = isFavorite, + iconOnActive = painterResource(R.drawable.ic_favorite), + iconOnInactive = painterResource(R.drawable.ic_favorite_outlined), + contentDescription = "favorite", + onToggle = onFavoriteClick + ) +} +@Composable +internal fun TaskerButton( + modifier: Modifier = Modifier, + isTaskerParam: Boolean, + onToggle: (Boolean) -> Unit, +) { + ActionToggleButton( + modifier = modifier, + isActive = isTaskerParam, + iconOnActive = painterResource(R.drawable.ic_tasker), + iconOnInactive = painterResource(R.drawable.ic_tasker_outlined), + contentDescription = "tasker param", + onToggle = onToggle + ) +} + +@PreviewLightDark +@Composable +private fun FavoriteButtonStatesPreview() { + SysctlGuiTheme { + Surface(color = MaterialTheme.colorScheme.background) { + Row( + modifier = Modifier.padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + FavoriteButton( + isFavorite = false, + onFavoriteClick = {} + ) + FavoriteButton( + isFavorite = true, + onFavoriteClick = {} + ) + TaskerButton( + isTaskerParam = false, + onToggle = {} + ) + TaskerButton( + isTaskerParam = true, + onToggle = {} + ) + } + } + } +} + +@Composable +@PreviewDynamicColors +private fun FavoriteButtonInteractivePreview() { + SysctlGuiTheme(dynamicColor = true) { + Surface { + var isFavorite by remember { mutableStateOf(false) } + + Row( + modifier = Modifier.padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + FavoriteButton( + isFavorite = isFavorite, + onFavoriteClick = { isFavorite = !isFavorite }, + ) + TaskerButton( + isTaskerParam = isFavorite, + onToggle = { isFavorite = !isFavorite } + ) + } + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditKernelParamActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditKernelParamActivity.kt deleted file mode 100644 index 459e4e1..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditKernelParamActivity.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.edit - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.annotation.StringRes -import androidx.appcompat.app.AlertDialog -import androidx.lifecycle.lifecycleScope -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.KernelParam -import com.androidvip.sysctlgui.toast -import com.androidvip.sysctlgui.utils.ComposeTheme -import kotlinx.coroutines.launch -import org.koin.androidx.viewmodel.ext.android.viewModel - -class EditKernelParamActivity : ComponentActivity() { - private val viewModel by viewModel() - private val isEditingSavedParam: Boolean - get() = intent.getBooleanExtra(EXTRA_EDIT_SAVED_PARAM, false) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - ComposeTheme { - EditParamScreen(viewModel = viewModel) - } - } - - lifecycleScope.launch { - viewModel.effect.collect(::handleViewEffect) - } - - handleIntent(intent) - } - - private fun handleIntent(intent: Intent) { - val param = intent.getParcelableExtra(EXTRA_PARAM) as? KernelParam - if (param != null) { - viewModel.onEvent(EditParamViewEvent.ReceivedParam(param, this)) - } else { - finishWithInvalidParamError() - } - } - - private fun finishWithInvalidParamError() { - toast(R.string.unexpected_error) - finish() - } - - private fun handleViewEffect(effect: EditParamViewEffect) { - when (effect) { - EditParamViewEffect.NavigateBack -> onBackPressedDispatcher.onBackPressed() - EditParamViewEffect.ShowTaskerListSelection -> { - selectTaskerListAsDialog { listId -> - viewModel.onEvent(EditParamViewEvent.TaskerListSelected(listId)) - } - } - - is EditParamViewEffect.ShowApplyError -> doAfterParamNotApplied(effect.messageRes) - is EditParamViewEffect.ShowApplySuccess -> doAfterParamApplied() - } - } - - private fun selectTaskerListAsDialog(block: (Int) -> Unit) { - AlertDialog.Builder(this) - .setTitle(R.string.select_tasker_list) - .setNegativeButton(android.R.string.cancel) { _, _ -> } - .setSingleChoiceItems(R.array.tasker_lists, -1) { dialog, which -> - block(which) - dialog.dismiss() - }.also { - if (!isFinishing) { - it.show() - } - } - } - - private fun doAfterParamApplied() { - if (isEditingSavedParam) { - setResult(Activity.RESULT_OK) - toast(R.string.done) - finish() - } - } - - private fun doAfterParamNotApplied(@StringRes messageRes: Int) { - toast(messageRes) - if (isEditingSavedParam) { - setResult(Activity.RESULT_CANCELED) - finish() - } - } - - companion object { - const val EXTRA_EDIT_SAVED_PARAM = "edit_saved_param" - const val EXTRA_PARAM = "param" - - fun getIntent(context: Context, param: KernelParam): Intent { - return Intent(context, EditKernelParamActivity::class.java).apply { - putExtra(EXTRA_PARAM, param) - } - } - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt index 30b6e34..c9ee615 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt @@ -1,446 +1,592 @@ package com.androidvip.sysctlgui.ui.params.edit -import androidx.annotation.DrawableRes +import android.content.ClipData +import android.widget.Toast +import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.SizeTransform +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.ArrowBack -import androidx.compose.material.icons.outlined.Check -import androidx.compose.material.icons.outlined.FavoriteBorder -import androidx.compose.material.icons.outlined.Refresh -import androidx.compose.material.icons.outlined.Warning +import androidx.compose.material.icons.rounded.Done +import androidx.compose.material.icons.rounded.Edit +import androidx.compose.material.icons.rounded.Warning +import androidx.compose.material3.AssistChip import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.FloatingActionButtonDefaults +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.LargeFloatingActionButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SmallFloatingActionButton -import androidx.compose.material3.SnackbarDuration -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.platform.ClipEntry +import androidx.compose.ui.platform.LocalClipboard +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.fromHtml import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewDynamicColors +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp +import androidx.core.view.HapticFeedbackConstantsCompat +import androidx.core.view.ViewCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.KernelParam +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.domain.enums.CommitMode +import com.androidvip.sysctlgui.domain.models.ParamDocumentation +import com.androidvip.sysctlgui.models.UiKernelParam +import com.androidvip.sysctlgui.ui.components.ErrorContainer +import com.androidvip.sysctlgui.ui.components.SingleChoiceDialog +import com.androidvip.sysctlgui.ui.main.MainViewEffect +import com.androidvip.sysctlgui.ui.main.MainViewEvent +import com.androidvip.sysctlgui.ui.main.MainViewModel +import com.androidvip.sysctlgui.ui.main.MainViewState +import com.androidvip.sysctlgui.utils.Consts +import com.androidvip.sysctlgui.utils.browse +import com.androidvip.sysctlgui.utils.performHapticFeedbackForToggle +import kotlinx.coroutines.launch +import org.intellij.lang.annotations.Language +import org.koin.androidx.compose.koinViewModel -@OptIn(ExperimentalMaterial3Api::class) @Composable -fun EditParamScreen(viewModel: EditParamViewModel) { - val state by viewModel.uiState.collectAsStateWithLifecycle() - val snackbarHostState = remember { SnackbarHostState() } - val listState = rememberLazyListState() - val expandedFabState = remember { - derivedStateOf { - listState.firstVisibleItemIndex == 0 - } +fun EditParamScreen( + viewModel: EditParamViewModel = koinViewModel(), + mainViewModel: MainViewModel = koinViewModel(), + onNavigateBack: () -> Unit +) { + val context = LocalContext.current + val state = viewModel.uiState.collectAsStateWithLifecycle() + val taskerListOptions = listOf("Primary", "Secondary") + var showSelectTaskerListDialog by rememberSaveable { mutableStateOf(true) } + var selectedOptionIndex by rememberSaveable { + mutableIntStateOf(Consts.LIST_NUMBER_PRIMARY_TASKER) } + var errorMessage by rememberSaveable { mutableStateOf("") } + var showError by rememberSaveable { mutableStateOf(false) } - Scaffold( - topBar = { - TopAppBar( - title = { Text(text = stringResource(id = R.string.edit_params)) }, - navigationIcon = { - IconButton(onClick = { viewModel.onEvent(EditParamViewEvent.BackPressed) }) { - Icon( - imageVector = Icons.Outlined.ArrowBack, - contentDescription = stringResource(id = R.string.restore_param), - tint = MaterialTheme.colorScheme.onPrimaryContainer - ) - } - } - ) - }, - snackbarHost = { SnackbarHost(snackbarHostState) }, - floatingActionButton = { - FloatingActionButtonColumn( - onReset = { viewModel.onEvent(EditParamViewEvent.ResetPressed) }, - onApply = { viewModel.onEvent(EditParamViewEvent.ApplyPressed) }, - hasApplied = state.hasApplied, - expanded = expandedFabState.value - ) - } - ) { contentPadding -> - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(contentPadding), - state = listState - ) { - item { ParamTexts(param = state.param) } - item { - ParamValues( - param = state.param, - appliedValue = state.restoreValue, - keyboardType = state.keyboardType, - singleLine = state.singleLine, - onValueChange = { - viewModel.onEvent(EditParamViewEvent.ParamValueInputChanged(it)) - } - ) - } - item { - ParamActions( - onFavoriteClicked = { - viewModel.onEvent(EditParamViewEvent.FavoritePressed(state.param.favorite)) - }, - onTaskerClicked = { viewModel.onEvent(EditParamViewEvent.TaskerPressed) }, - param = state.param, - taskerAvailable = state.taskerAvailable + LaunchedEffect(Unit) { + mainViewModel.onEvent( + MainViewEvent.OnSateChangeRequested( + MainViewState( + topBarTitle = "Edit kernel parameter", + showTopBar = true, + showNavBar = false, + showBackButton = true, + showSearchAction = false ) + ) + ) + + mainViewModel.effect.collect { effect -> + if (effect is MainViewEffect.ActUponSckbarActionPerformed) { + viewModel.onEvent(EditParamViewEvent.UndoRequested) } - item { ParamDocs(info = state.paramInfo) } } } - val successMessage = stringResource(id = R.string.done) - val undoMessage = stringResource(id = R.string.undo) - LaunchedEffect(key1 = Unit) { + LaunchedEffect(viewModel.effect) { viewModel.effect.collect { effect -> when (effect) { + EditParamViewEffect.GoBack -> onNavigateBack() + + is EditParamViewEffect.ShowError -> { + errorMessage = effect.message + showError = true + } + + is EditParamViewEffect.OpenBrowser -> context.browse(effect.url) + is EditParamViewEffect.ShowApplySuccess -> { - val result = snackbarHostState.showSnackbar( - message = successMessage, - actionLabel = undoMessage, - duration = SnackbarDuration.Short + mainViewModel.onEvent( + MainViewEvent.ShowSnackbarRequested( + message = "Value applied successfully", + actionLabel = "Undo" + ) ) - - if (result == SnackbarResult.ActionPerformed) { - viewModel.onEvent(EditParamViewEvent.ResetPressed) - } } - else -> Unit } } } + + EditParamContent( + state = state.value, + showError = showError, + errorMessage = errorMessage, + onValueApply = { + viewModel.onEvent(EditParamViewEvent.ApplyPressed(it)) + }, + onTaskerClicked = { + viewModel.onEvent(EditParamViewEvent.TaskerTogglePressed(it, selectedOptionIndex)) + }, + onDocsReadMorePressed = { + viewModel.onEvent(EditParamViewEvent.DocumentationReadMoreClicked) + }, + onFavoriteToggle = { + viewModel.onEvent(EditParamViewEvent.FavoriteTogglePressed(it)) + }, + onErrorAnimationEnd = { showError = false }, + taskerListNameResolver = { listId -> taskerListOptions.getOrNull(listId).orEmpty() } + ) + + SingleChoiceDialog( + showDialog = showSelectTaskerListDialog, + title = "Choose a Tasker list", + options = taskerListOptions, + initialSelectedOptionIndex = selectedOptionIndex, + onDismissRequest = { showSelectTaskerListDialog = false }, + onOptionSelected = { + selectedOptionIndex = it + viewModel.onEvent(EditParamViewEvent.TaskerTogglePressed(true, it)) + } + ) } @Composable -private fun FloatingActionButtonColumn( - onReset: () -> Unit, - onApply: () -> Unit, - hasApplied: Boolean, - expanded: Boolean +private fun EditParamContent( + state: EditParamViewState, + showError: Boolean, + errorMessage: String, + onDocsReadMorePressed: () -> Unit, + onValueApply: (String) -> Unit, + onFavoriteToggle: (Boolean) -> Unit, + onTaskerClicked: (Boolean) -> Unit, + onErrorAnimationEnd: () -> Unit, + taskerListNameResolver: (Int) -> String = { "List #$it" }, ) { + val param = state.kernelParam + val view = LocalView.current + val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() + val clipboardManager = LocalClipboard.current + val scrollState = rememberScrollState() + + val copyParamContentToClipboard = { + val clipData = ClipData.newPlainText( + "Kernel Parameter", + "${param.lastNameSegment}=${param.value} (${param.path})" + ) + val clipEntry = ClipEntry(clipData) + coroutineScope.launch { + clipboardManager.setClipEntry(clipEntry) + } + Toast.makeText(context, "Copied to clipboard", Toast.LENGTH_SHORT).show() + } + Column( - verticalArrangement = Arrangement.spacedBy(12.dp), - horizontalAlignment = Alignment.End, - modifier = Modifier.padding(bottom = 8.dp) + modifier = Modifier + .fillMaxSize() + .verticalScroll(scrollState) + .background(MaterialTheme.colorScheme.surfaceContainer) ) { - AnimatedVisibility(hasApplied) { - SmallFloatingActionButton( - onClick = onReset, - containerColor = MaterialTheme.colorScheme.tertiaryContainer, - contentColor = MaterialTheme.colorScheme.onTertiaryContainer + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background) + ) { + Text( + text = param.lastNameSegment, + style = MaterialTheme.typography.displayLarge, + modifier = Modifier + .combinedClickable( + enabled = true, + onClick = { + Toast.makeText(context, "Long press to copy", Toast.LENGTH_SHORT).show() + }, + onLongClick = copyParamContentToClipboard + ) + .padding(start = 16.dp, end = 16.dp, top = 64.dp), + maxLines = 3, + color = MaterialTheme.colorScheme.onBackground, + overflow = TextOverflow.Ellipsis + ) + + Row( + modifier = Modifier.padding( + horizontal = 16.dp, + vertical = if (param.isTaskerParam) 0.dp else 24.dp + ), + verticalAlignment = Alignment.CenterVertically ) { - Icon( - imageVector = Icons.Outlined.Refresh, - contentDescription = stringResource(id = R.string.restore_param), - tint = MaterialTheme.colorScheme.onTertiaryContainer + Column(modifier = Modifier.weight(1f)) { + Text( + text = param.name, + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(vertical = 8.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Text( + text = param.path, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + + Spacer(modifier = Modifier.width(8.dp)) + + if (state.taskerAvailable) { + TaskerButton( + isTaskerParam = param.isTaskerParam, + onToggle = { newState -> + performHapticFeedbackForToggle(newState, view) + onTaskerClicked(newState) + }, + modifier = Modifier.scale(0.85f) + ) + } + + FavoriteButton( + isFavorite = param.isFavorite, + onFavoriteClick = { newState -> + performHapticFeedbackForToggle(newState, view) + onFavoriteToggle(newState) + }, + modifier = Modifier.scale(0.85f) ) } - } - ExtendedFloatingActionButton( - text = { - Text( - text = stringResource(id = R.string.apply_param), - color = MaterialTheme.colorScheme.onSecondaryContainer, - fontWeight = FontWeight.Medium - ) - }, - icon = { - Icon( - imageVector = Icons.Outlined.Check, - contentDescription = stringResource(id = R.string.apply_param), - tint = MaterialTheme.colorScheme.onSecondaryContainer + if (param.isTaskerParam && state.taskerAvailable) { + val listName = taskerListNameResolver(param.taskerList) + AssistChip( + onClick = { onTaskerClicked(true) }, + modifier = Modifier.padding(16.dp), + label = { Text(text = "Tasker list: $listName") }, + leadingIcon = { + Icon( + painter = painterResource(R.drawable.ic_tasker), + contentDescription = "Tasker list", + tint = MaterialTheme.colorScheme.tertiary + ) + } ) - }, - onClick = onApply, - expanded = expanded, - containerColor = MaterialTheme.colorScheme.secondaryContainer + } + } + + ParamValueContent( + modifier = Modifier.padding(16.dp), + param = param, + keyboardType = state.keyboardType, + onValueApply = onValueApply + ) + + AnimatedVisibility( + visible = showError && errorMessage.isNotEmpty(), + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + ) { + ErrorContainer(message = errorMessage, onAnimationEnd = onErrorAnimationEnd) + } + + ParamDocs( + modifier = Modifier.padding(16.dp), + documentation = state.documentation, + onReadMorePressed = onDocsReadMorePressed ) } } @Composable -private fun ParamTexts(param: KernelParam) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp) +private fun ParamValueContent( + modifier: Modifier = Modifier, + param: UiKernelParam, + keyboardType: KeyboardType, + onValueApply: (String) -> Unit +) { + var isEditing by remember { mutableStateOf(false) } + var editedValue by remember(param.value) { mutableStateOf(param.value) } + val view = LocalView.current + + HorizontalDivider() + + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically ) { - Column(modifier = Modifier.padding(16.dp)) { + Column(modifier = Modifier.weight(1f)) { Text( - text = stringResource(id = R.string.param), - style = MaterialTheme.typography.headlineMedium, - color = MaterialTheme.colorScheme.onSurface + text = "Parameter value", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onBackground ) - ParamRow(iconRes = R.drawable.ic_config, text = param.configName) - ParamRow(iconRes = R.drawable.ic_name, text = param.shortName) - ParamRow(iconRes = R.drawable.ic_folder_outline, text = param.path) + EditableParamValue( + isEditing = isEditing, + paramValue = param.value, + editedValue = editedValue, + keyboardType = keyboardType, + onEditorValueChange = { editedValue = it }, + modifier = Modifier + .padding(top = 8.dp) + .fillMaxWidth() + ) + } + + IconButton( + onClick = { + if (isEditing) { + onValueApply(editedValue) + ViewCompat.performHapticFeedback(view, HapticFeedbackConstantsCompat.CONFIRM) + } + isEditing = !isEditing + } + ) { + AnimatedContent( + targetState = isEditing, + label = "EditButtonAnimation", + ) { editingActive -> + if (editingActive) { + Icon( + imageVector = Icons.Rounded.Done, + contentDescription = "Apply", + tint = MaterialTheme.colorScheme.primary + ) + } else { + Icon( + imageVector = Icons.Rounded.Edit, + contentDescription = "Edit", + tint = MaterialTheme.colorScheme.primary + ) + } + } } } } @Composable -private fun ParamRow(@DrawableRes iconRes: Int, text: String) { - Row( - modifier = Modifier.padding(top = 16.dp, start = 16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(24.dp) - ) { - Icon( - painter = painterResource(id = iconRes), - contentDescription = "", - tint = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.size(24.dp) - ) - Text( - text = text, - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onSurface - ) +fun EditableParamValue( + isEditing: Boolean, + paramValue: String, + editedValue: String, + keyboardType: KeyboardType = KeyboardType.Text, + onEditorValueChange: (String) -> Unit, + modifier: Modifier = Modifier +) { + Box(modifier = modifier) { + AnimatedContent( + targetState = isEditing, + label = "EditableValueAnimation", + transitionSpec = { + if (targetState) { + slideInVertically { it } + fadeIn() togetherWith + slideOutVertically { -it } + fadeOut() + } else { + slideInVertically { -it } + fadeIn() togetherWith + slideOutVertically { it } + fadeOut() + }.using( + SizeTransform(clip = true) + ) + } + ) { editingActive -> + if (editingActive) { + OutlinedTextField( + value = editedValue, + onValueChange = onEditorValueChange, + label = { Text("New value") }, + singleLine = true, + keyboardOptions = KeyboardOptions(keyboardType = keyboardType), + modifier = Modifier.fillMaxWidth() + ) + } else { + Text( + text = paramValue, + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.fillMaxWidth() + ) + } + } } } @Composable -private fun ParamValues( - param: KernelParam, - onValueChange: (String) -> Unit, - appliedValue: String, - keyboardType: KeyboardType, - singleLine: Boolean +private fun ParamDocs( + modifier: Modifier = Modifier, + documentation: ParamDocumentation?, + onReadMorePressed: () -> Unit, ) { - var typedValue by rememberSaveable { mutableStateOf(param.value) } - - Column(modifier = Modifier.padding(16.dp)) { - Text( - text = stringResource(id = R.string.value), - style = MaterialTheme.typography.headlineMedium, - color = MaterialTheme.colorScheme.onBackground - ) + HorizontalDivider() + Column(modifier = modifier) { Text( - modifier = Modifier.padding(top = 16.dp), - text = stringResource(id = R.string.current_value), + text = "Documentation", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onBackground ) - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp), - textStyle = MaterialTheme.typography.bodyLarge.copy( - color = MaterialTheme.colorScheme.onBackground - ), - keyboardOptions = KeyboardOptions( - keyboardType = keyboardType, - imeAction = ImeAction.Done - ), - maxLines = 3, - singleLine = singleLine, - value = typedValue, - onValueChange = { typedValue = it; onValueChange(it) } - ) - Text( - modifier = Modifier.padding(top = 16.dp), - text = stringResource(id = R.string.last_applied_value), - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onBackground - ) - SelectionContainer { - Text( - text = appliedValue, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onBackground - ) - } - } -} + if (documentation != null) { + val documentationText = if (!documentation.documentationHtml.isNullOrEmpty()) { + AnnotatedString.fromHtml( + htmlString = documentation.documentationHtml.orEmpty(), + linkStyles = TextLinkStyles( + style = MaterialTheme.typography.bodyMedium.toSpanStyle().copy( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline, + fontWeight = FontWeight.Medium + ), + pressedStyle = MaterialTheme.typography.bodyMedium.toSpanStyle().copy( + color = MaterialTheme.colorScheme.tertiary, + textDecoration = TextDecoration.Underline, + fontWeight = FontWeight.Medium + ) + ) + ) + } else { + AnnotatedString(documentation.documentationText) + } -@Composable -private fun ParamActions( - onFavoriteClicked: () -> Unit, - onTaskerClicked: () -> Unit, - param: KernelParam, - taskerAvailable: Boolean -) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(24.dp, Alignment.CenterHorizontally) - ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - LargeFloatingActionButton( - modifier = Modifier.size(74.dp), - onClick = onFavoriteClicked, - containerColor = if (param.favorite) { - MaterialTheme.colorScheme.errorContainer - } else { - MaterialTheme.colorScheme.outlineVariant - }, - contentColor = if (param.favorite) { - MaterialTheme.colorScheme.onErrorContainer - } else { - MaterialTheme.colorScheme.outline - }, - elevation = FloatingActionButtonDefaults.elevation(defaultElevation = 0.dp) - ) { - Icon( - imageVector = Icons.Outlined.FavoriteBorder, - contentDescription = stringResource(id = R.string.set_favorite) + SelectionContainer { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + text = documentationText, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } - Text( + TextButton( + onClick = onReadMorePressed, modifier = Modifier - .widthIn(max = 82.dp) - .padding(top = 2.dp), - text = if (param.favorite) { - stringResource(id = R.string.remove_from_favorites) - } else { - stringResource(id = R.string.set_favorite) - }, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onBackground - ) - } - - if (taskerAvailable) { - Column( - horizontalAlignment = Alignment.CenterHorizontally + .padding(vertical = 8.dp) + .align(Alignment.End) ) { - LargeFloatingActionButton( - modifier = Modifier.size(74.dp), - onClick = onTaskerClicked, - containerColor = if (param.taskerParam) { - MaterialTheme.colorScheme.primaryContainer - } else { - MaterialTheme.colorScheme.outlineVariant - }, - contentColor = if (param.taskerParam) { - MaterialTheme.colorScheme.onPrimaryContainer - } else { - MaterialTheme.colorScheme.outline - }, - elevation = FloatingActionButtonDefaults.elevation(defaultElevation = 0.dp) + Text(text = "Read more") + } + } else { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(24.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer + ) + ) { + Row( + modifier = Modifier.padding(24.dp), + horizontalArrangement = Arrangement.spacedBy( + 16.dp, + Alignment.CenterHorizontally + ) ) { Icon( - painter = painterResource(id = R.drawable.ic_action_tasker), - contentDescription = stringResource(id = R.string.set_favorite), - tint = MaterialTheme.colorScheme.onPrimaryContainer + imageVector = Icons.Rounded.Warning, + contentDescription = stringResource(android.R.string.dialog_alert_title), + tint = MaterialTheme.colorScheme.onErrorContainer + ) + Text( + text = "No documentation available", + style = MaterialTheme.typography.bodyLarge.copy(), + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onErrorContainer ) } - Text( - modifier = Modifier - .widthIn(max = 82.dp) - .padding(top = 2.dp), - text = if (param.taskerParam) { - stringResource(id = R.string.remove_from_tasker_list) - } else { - stringResource(id = R.string.add_to_tasker_list) - }, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onBackground - ) } } } } @Composable -private fun ParamDocs(info: String?) { - Text( - modifier = Modifier.padding(top = 16.dp, bottom = 0.dp, start = 16.dp, end = 16.dp), - text = stringResource(id = R.string.information), - style = MaterialTheme.typography.titleMedium, - color = MaterialTheme.colorScheme.onBackground - ) +@PreviewLightDark +@PreviewDynamicColors +private fun EditParamContentPreview() { - if (info != null) { - SelectionContainer { - Text( - modifier = Modifier.padding(top = 4.dp, bottom = 16.dp, start = 16.dp, end = 16.dp), - text = info, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onBackground + @Language("html") + val htmlDocs = """ +

    Correctable memory errors are very common on servers. + Soft-offline is kernel’s solution for memory pages having + (excessive) corrected memory errors.

    + +

    For different types_of page, soft-offline has different behaviors / costs.

    +
      +
    • For a raw error page, soft-offline migrates the in-use page’s content to a new raw page.
    • +
    • For a page that is part of a transparent hugepage, soft-offline splits the transparent hugepage into raw pages, then migrates only the raw error page. As a result, user is transparently backed by 1 less hugepage, impacting memory access performance.
    • +
    • For a page that is part of a HugeTLB hugepage, soft-offline first migrates the entire HugeTLB hugepage, during which a free hugepage will be consumed as migration target. Then the original hugepage is dissolved into raw pages without compensation, reducing the capacity of the HugeTLB pool by 1.
    • +
    • It is user’s call to choose between reliability (staying away from fragile physical memory) vs performance / capacity implications in transparent and HugeTLB cases.
    • +
    + """.trimIndent() + .replace( + "", + "" + ) + .replace("", "") + + var showError by remember { mutableStateOf(true) } + + SysctlGuiTheme(dynamicColor = true) { + Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { + + val state = EditParamViewState( + kernelParam = UiKernelParam( + name = "vm.enable_soft_offline", + path = "/proc/sys/vm/enable_soft_offline", + value = "1", + taskerList = 1, + isTaskerParam = false, + isFavorite = false + ), + taskerAvailable = true, + keyboardType = KeyboardType.Number, + documentation = ParamDocumentation( + title = "vm.enable_soft_offline", + documentationText = "", + documentationHtml = htmlDocs, + url = "url" + ), ) - } - } else { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(24.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.errorContainer + EditParamContent( + state = state, + showError = showError, + errorMessage = "Sysctl command for 'wm.swappiness' executed, " + + "but output did not confirm the change. Output: 'Access denied'. " + + "Try using '${CommitMode.ECHO}' mode.", + onValueApply = {}, + onTaskerClicked = {}, + onDocsReadMorePressed = {}, + onFavoriteToggle = {}, + onErrorAnimationEnd = { showError = false } ) - ) { - Row( - modifier = Modifier.padding(24.dp), - horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally) - ) { - Icon( - imageVector = Icons.Outlined.Warning, - contentDescription = stringResource(android.R.string.dialog_alert_title), - tint = MaterialTheme.colorScheme.onErrorContainer - ) - Text( - text = stringResource(id = R.string.no_info_available), - style = MaterialTheme.typography.bodyLarge.copy( - fontWeight = FontWeight.Medium - ), - color = MaterialTheme.colorScheme.onErrorContainer - ) - } } } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewEffect.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewEffect.kt deleted file mode 100644 index 9e1c327..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewEffect.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.edit - -import androidx.annotation.StringRes - -sealed interface EditParamViewEffect { - class ShowApplyError(@StringRes val messageRes: Int) : EditParamViewEffect - object ShowApplySuccess : EditParamViewEffect - object NavigateBack : EditParamViewEffect - object ShowTaskerListSelection : EditParamViewEffect -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewEvent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewEvent.kt deleted file mode 100644 index b289d50..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewEvent.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.edit - -import android.content.Context -import com.androidvip.sysctlgui.data.models.KernelParam - -sealed interface EditParamViewEvent { - class ReceivedParam(val param: KernelParam, val context: Context) : EditParamViewEvent - object BackPressed : EditParamViewEvent - class FavoritePressed(val favorite: Boolean) : EditParamViewEvent - object TaskerPressed : EditParamViewEvent - object ApplyPressed : EditParamViewEvent - object ResetPressed : EditParamViewEvent - class TaskerListSelected(val listId: Int) : EditParamViewEvent - class ParamValueInputChanged(val newValue: String) : EditParamViewEvent -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt index 44e5713..d8cb6ea 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt @@ -1,202 +1,141 @@ package com.androidvip.sysctlgui.ui.params.edit -import android.annotation.SuppressLint -import android.content.Context -import android.content.pm.PackageManager -import android.os.Build +import android.util.Log import androidx.compose.ui.text.input.KeyboardType +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.KernelParam -import com.androidvip.sysctlgui.domain.exceptions.ApplyValueException -import com.androidvip.sysctlgui.domain.exceptions.CommitModeException import com.androidvip.sysctlgui.domain.repository.AppPrefs -import com.androidvip.sysctlgui.domain.usecase.ApplyParamsUseCase -import com.androidvip.sysctlgui.domain.usecase.UpdateUserParamUseCase -import com.androidvip.sysctlgui.readLines +import com.androidvip.sysctlgui.domain.usecase.ApplyParamUseCase +import com.androidvip.sysctlgui.domain.usecase.GetParamDocumentationUseCase +import com.androidvip.sysctlgui.domain.usecase.GetRuntimeParamUseCase +import com.androidvip.sysctlgui.domain.usecase.GetUserParamByNameUseCase +import com.androidvip.sysctlgui.domain.usecase.IsTaskerInstalledUseCase +import com.androidvip.sysctlgui.domain.usecase.UpsertUserParamUseCase +import com.androidvip.sysctlgui.helpers.UiKernelParamMapper +import com.androidvip.sysctlgui.models.UiKernelParam import com.androidvip.sysctlgui.utils.BaseViewModel -import java.io.InputStream import kotlinx.coroutines.launch class EditParamViewModel( - private val prefs: AppPrefs, - private val applyParams: ApplyParamsUseCase, - private val updateUserParam: UpdateUserParamUseCase + savedStateHandle: SavedStateHandle, + private val applyParam: ApplyParamUseCase, + private val getDocumentation: GetParamDocumentationUseCase, + private val upsertUserParam: UpsertUserParamUseCase, + private val getRuntimeParam: GetRuntimeParamUseCase, + private val getUserParam: GetUserParamByNameUseCase, + private val isTaskerInstalled: IsTaskerInstalledUseCase, + private val appPrefs: AppPrefs ) : BaseViewModel() { - override fun createInitialState(): EditParamViewState = EditParamViewState() + private val paramName: String? = savedStateHandle.get(PARAM_NAME_KEY) + private var previousKernelParamValue: String? = null - override fun onEvent(event: EditParamViewEvent) { - when (event) { - EditParamViewEvent.ApplyPressed -> { - applyParam(currentState.param.copy(value = currentState.typedValue)) - } - EditParamViewEvent.BackPressed -> { - setEffect { EditParamViewEffect.NavigateBack } - } - is EditParamViewEvent.FavoritePressed -> { - updateParam(currentState.param.copy(favorite = !currentState.param.favorite)) - } - is EditParamViewEvent.TaskerListSelected -> { - updateParam(currentState.param.copy(taskerList = event.listId, taskerParam = true)) - } - is EditParamViewEvent.ParamValueInputChanged -> { - setState { copy(typedValue = event.newValue) } - } - is EditParamViewEvent.ReceivedParam -> { - setInitialState(event.param, event.context) - } - EditParamViewEvent.ResetPressed -> { - applyParam(currentState.param.copy(value = currentState.restoreValue)) - } - EditParamViewEvent.TaskerPressed -> { - setEffect { EditParamViewEffect.ShowTaskerListSelection } + init { + viewModelScope.launch { + if (paramName.isNullOrEmpty()) return@launch setEffect { EditParamViewEffect.GoBack } + + val param = runCatching { getUserParam(paramName) }.getOrNull() + ?: getRuntimeParam(paramName) + ?: return@launch setEffect { EditParamViewEffect.GoBack } + + val documentation = getDocumentation(param) + + setState { + copy( + kernelParam = UiKernelParamMapper.map(param), + taskerAvailable = isTaskerInstalled(), + keyboardType = guessKeyboardType(param.value), + documentation = documentation, + ) } } } - private fun setInitialState(param: KernelParam, context: Context) { - val keyboardType = getKeyboardTypeForValue(param.value) - val singleLine = keyboardType != KeyboardType.Text || - param.value.length <= PARAM_LENGTH_INPUT_THRESHOLD + override fun createInitialState() = EditParamViewState() - setState { - copy( - param = param, - restoreValue = param.value, - typedValue = param.value, - paramInfo = findParamInfo(param, context), - taskerAvailable = isTaskerInstalled(context), - keyboardType = keyboardType, - singleLine = singleLine - ) + override fun onEvent(event: EditParamViewEvent) { + when (event) { + is EditParamViewEvent.ApplyPressed -> applyKernelParam(event.newValue) + is EditParamViewEvent.UndoRequested -> { + previousKernelParamValue?.let { applyKernelParam(it) } + } + is EditParamViewEvent.DocumentationReadMoreClicked -> onDocumentationReadMoreClicked() + is EditParamViewEvent.FavoriteTogglePressed -> onFavoriteTogglePressed(event.newState) + is EditParamViewEvent.TaskerTogglePressed -> onTaskerTogglePressed(event.newState, event.listId) } } - private fun applyParam(param: KernelParam) { + private fun applyKernelParam(newValue: String) { + val oldParam = currentState.kernelParam viewModelScope.launch { + val newParam = oldParam.copy(value = newValue) runCatching { - applyParams(param) - updateUserParam(param) - }.onFailure { - val messageRes = when (it) { - is ApplyValueException -> R.string.apply_value_error - is CommitModeException -> R.string.commit_value_error - else -> R.string.error - } - setEffect { EditParamViewEffect.ShowApplyError(messageRes) } + applyParam(newParam) + upsertUserParam(newParam) }.onSuccess { - setEffect { EditParamViewEffect.ShowApplySuccess } - setState { - copy(param = param, hasApplied = param.value != currentState.restoreValue) + setState { copy(kernelParam = newParam) } + setEffect { EditParamViewEffect.ShowApplySuccess(oldParam.value) } + previousKernelParamValue = oldParam.value + }.onFailure { + Log.e("EditParamViewModel", "Failed to apply param", it) + setEffect { + EditParamViewEffect.ShowError(it.message.orEmpty()) } } } } - private fun updateParam(param: KernelParam) { + private fun onFavoriteTogglePressed(newState: Boolean) { viewModelScope.launch { + val newParam = currentState.kernelParam.copy(isFavorite = newState) runCatching { - updateUserParam(param) - }.onFailure { - setEffect { EditParamViewEffect.ShowApplyError(R.string.error) } + upsertUserParam(newParam) }.onSuccess { - setState { copy(param = param) } + setState { copy(kernelParam = newParam) } + }.onFailure { + Log.e("EditParamViewModel", "Failed to update favorite status", it) + setEffect { + EditParamViewEffect.ShowError("Failed to update favorite status") + } } } } - private fun getKeyboardTypeForValue(paramValue: String): KeyboardType { - if (!prefs.guessInputType) return KeyboardType.Text - - val intValue = paramValue.toIntOrNull() - if (intValue != null) return KeyboardType.Number - - val decimalValue = paramValue.toDoubleOrNull() - if (decimalValue != null) return KeyboardType.Decimal - - return KeyboardType.Text - } - - @SuppressLint("DiscouragedApi") - private fun findParamInfo(param: KernelParam, context: Context): String? = with(context) { - val paramName = param.shortName - val resId = resources.getIdentifier( - paramName.replace("-", "_"), - "string", - packageName - ) - val stringRes = runCatching { getString(resId) }.getOrNull() - - // Prefer the documented string resource - if (stringRes != null) return stringRes - - if (!param.path.startsWith("/")) return null - - val subdirs = param.path.split("/") - if (subdirs.isEmpty() || subdirs.size < SUBDIR_THRESHOLD) return null - - // Finding param info within subdir whole documentation string - - val rawInputStream: InputStream? = when (subdirs[3]) { // /proc/sys/[?] - "abi" -> resources.openRawResource(R.raw.abi) - "fs" -> resources.openRawResource(R.raw.fs) - "kernel" -> resources.openRawResource(R.raw.kernel) - "net" -> resources.openRawResource(R.raw.net) - "vm" -> resources.openRawResource(R.raw.vm) - else -> null - } - - val documentation = buildString { - rawInputStream.readLines { - append(it) - append("\n") + private fun onTaskerTogglePressed(newState: Boolean, listId: Int) { + viewModelScope.launch { + val newParam = currentState.kernelParam.copy( + isTaskerParam = newState, + taskerList = listId + ) + runCatching { + upsertUserParam(newParam) + }.onSuccess { + setState { copy(kernelParam = newParam) } + }.onFailure { + Log.e("EditParamViewModel", "Failed to update tasker status", it) + setEffect { + EditParamViewEffect.ShowError("Failed to update tasker status") + } } } - if (documentation.isEmpty()) return null - - /* - Trying to match: - - =============== - - paramName - - the <== - actual <== - documentation <== - - =============== - */ - val info: String? = runCatching { - documentation - .split("=+".toRegex()) - .last { it.contains("$paramName\n") } - .split("$paramName\n") - .last() - }.getOrNull() + } - return info.takeIf { it.isNullOrEmpty().not() } + private fun onDocumentationReadMoreClicked() { + currentState.documentation?.url?.let { documentationUrl -> + setEffect { EditParamViewEffect.OpenBrowser(documentationUrl) } + } } - private fun isTaskerInstalled(context: Context): Boolean { - val packageManager = context.packageManager + private fun guessKeyboardType(paramValue: String): KeyboardType { + if (!appPrefs.guessInputType) return KeyboardType.Text - return runCatching { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - packageManager.getPackageInfo( - TASKER_PACKAGE_NAME, - PackageManager.PackageInfoFlags.of(0L) - ) - } else { - packageManager.getPackageInfo(TASKER_PACKAGE_NAME, 0) - } - true - }.getOrDefault(false) + return when { + paramValue.toIntOrNull() != null -> KeyboardType.Number + paramValue.toDoubleOrNull() != null -> KeyboardType.Decimal + else -> KeyboardType.Text + } } companion object { - private const val PARAM_LENGTH_INPUT_THRESHOLD = 12 - private const val SUBDIR_THRESHOLD = 4 - private const val TASKER_PACKAGE_NAME = "net.dinglisch.android.taskerm" + private const val PARAM_NAME_KEY = "paramName" } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewState.kt index 7f7105c..549e388 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewState.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewState.kt @@ -1,15 +1,27 @@ package com.androidvip.sysctlgui.ui.params.edit import androidx.compose.ui.text.input.KeyboardType -import com.androidvip.sysctlgui.data.models.KernelParam +import com.androidvip.sysctlgui.domain.models.ParamDocumentation +import com.androidvip.sysctlgui.models.UiKernelParam data class EditParamViewState( - val param: KernelParam = KernelParam(), - val restoreValue: String = "", // Backup, - val typedValue: String = "", - val hasApplied: Boolean = false, - val paramInfo: String? = null, + val kernelParam: UiKernelParam = UiKernelParam(), val taskerAvailable: Boolean = false, val keyboardType: KeyboardType = KeyboardType.Text, - val singleLine: Boolean = true + val documentation: ParamDocumentation? = null, ) + +sealed interface EditParamViewEffect { + data class OpenBrowser(val url: String) : EditParamViewEffect + data class ShowApplySuccess(val previousValue: String) : EditParamViewEffect + data class ShowError(val message: String) : EditParamViewEffect + data object GoBack : EditParamViewEffect +} + +sealed interface EditParamViewEvent { + data class ApplyPressed(val newValue: String) : EditParamViewEvent + data object UndoRequested : EditParamViewEvent + data class FavoriteTogglePressed(val newState: Boolean) : EditParamViewEvent + data class TaskerTogglePressed(val newState: Boolean, val listId: Int) : EditParamViewEvent + data object DocumentationReadMoreClicked : EditParamViewEvent +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/KernelParamListFragment.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/KernelParamListFragment.kt deleted file mode 100644 index 62fe69d..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/KernelParamListFragment.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.list - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.pullrefresh.PullRefreshIndicator -import androidx.compose.material.pullrefresh.pullRefresh -import androidx.compose.material.pullrefresh.rememberPullRefreshState -import androidx.compose.material3.Divider -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.unit.dp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.KernelParam -import com.androidvip.sysctlgui.ui.base.BaseSearchFragment -import com.androidvip.sysctlgui.ui.params.EmptyParamsWarning -import com.androidvip.sysctlgui.ui.params.edit.EditKernelParamActivity -import com.androidvip.sysctlgui.utils.ComposeTheme -import kotlinx.coroutines.launch -import org.koin.androidx.viewmodel.ext.android.viewModel - -class KernelParamListFragment : BaseSearchFragment() { - private val viewModel: ListParamsViewModel by viewModel() - - @OptIn(ExperimentalMaterialApi::class) - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - return ComposeView(requireContext()).apply { - setContent { - ComposeTheme { - val state by viewModel.uiState.collectAsStateWithLifecycle() - val refreshing = state.isLoading - val refreshState = rememberPullRefreshState( - refreshing = refreshing, - onRefresh = { refreshList() } - ) - - Box(Modifier.pullRefresh(refreshState)) { - if (state.showEmptyState) { - EmptyParamsWarning() - } else { - KernelParamsList(state.data) - } - - PullRefreshIndicator( - modifier = Modifier.align(Alignment.TopCenter), - refreshing = refreshing, - state = refreshState, - backgroundColor = MaterialTheme.colorScheme.tertiaryContainer, - contentColor = MaterialTheme.colorScheme.onTertiaryContainer - ) - } - } - } - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - lifecycleScope.launch { - viewModel.effect.collect(::processEffect) - } - } - - override fun onStart() { - super.onStart() - refreshList() - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.menu_main_search, menu) - setUpSearchView(menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.action_favorites -> findNavController().navigate(R.id.navigateFavoritesParams) - else -> return false - } - - return true - } - - override fun onQueryTextChanged() { - viewModel.onEvent(ParamViewEvent.SearchExpressionChanged(searchExpression)) - } - - private fun onParamItemClicked(param: KernelParam) { - startActivity(EditKernelParamActivity.getIntent(requireContext(), param)) - } - - private fun refreshList() { - viewModel.onEvent(ParamViewEvent.RefreshRequested) - } - - private fun processEffect(effect: ParamViewEffect) { - when (effect) { - is ParamViewEffect.NavigateToParamDetails -> onParamItemClicked(effect.param) - } - } - - @Composable - private fun KernelParamsList(params: List) { - LazyColumn { - itemsIndexed(params) { index, param -> - ParamItem( - onParamClick = { viewModel.onEvent(ParamViewEvent.ParamClicked(param)) }, - param = param - ) - if (index < params.lastIndex) { - Divider(color = MaterialTheme.colorScheme.outlineVariant, thickness = 1.dp) - } - } - } - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ListParamsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ListParamsViewModel.kt deleted file mode 100644 index 59273ff..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ListParamsViewModel.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.list - -import androidx.lifecycle.viewModelScope -import com.androidvip.sysctlgui.data.mapper.DomainParamMapper -import com.androidvip.sysctlgui.domain.usecase.GetRuntimeParamsUseCase -import com.androidvip.sysctlgui.utils.BaseViewModel -import kotlinx.coroutines.launch - -class ListParamsViewModel( - private val getParamsUseCase: GetRuntimeParamsUseCase -) : BaseViewModel() { - private var searchExpression = "" - - private fun requestKernelParams() { - viewModelScope.launch { - setState { copy(isLoading = true) } - val params = getParamsUseCase() - .map(DomainParamMapper::map) - .filter { param -> - if (searchExpression.isNotEmpty()) { - param.name.lowercase() - .replace(".", "") - .contains(searchExpression.lowercase()) - } else { - true - } - } - setState { copy(isLoading = false, data = params, showEmptyState = params.isEmpty()) } - } - } - - override fun createInitialState(): ParamViewState = ParamViewState() - - override fun onEvent(event: ParamViewEvent) { - when (event) { - is ParamViewEvent.ParamClicked -> setEffect { - ParamViewEffect.NavigateToParamDetails(DomainParamMapper.map(event.param)) - } - is ParamViewEvent.SearchExpressionChanged -> { - searchExpression = event.data - requestKernelParams() - } - ParamViewEvent.RefreshRequested -> requestKernelParams() - else -> Unit - } - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamItem.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamItem.kt deleted file mode 100644 index 340b65f..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamItem.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.list - -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.androidvip.sysctlgui.data.models.KernelParam -import com.androidvip.sysctlgui.design.theme.md_theme_light_background - -@Composable -fun ParamItem(onParamClick: (KernelParam) -> Unit, param: KernelParam) { - Column( - modifier = Modifier - .fillMaxWidth() - .clickable { onParamClick(param) } - ) { - Text( - modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp), - text = param.shortName, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.secondary - ) - Spacer(modifier = Modifier.height(2.dp)) - Text( - modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp), - text = param.value, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onBackground - ) - } -} - -@Preview -@Composable -fun ParamItemPreview() { - val param = KernelParam(name = "test", value = "success") - Box(modifier = Modifier.background(md_theme_light_background)) { - ParamItem(onParamClick = {}, param = param) - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamViewEffect.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamViewEffect.kt deleted file mode 100644 index 51a92db..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamViewEffect.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.list - -import com.androidvip.sysctlgui.data.models.KernelParam - -sealed interface ParamViewEffect { - class NavigateToParamDetails(val param: KernelParam) : ParamViewEffect -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamViewEvent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamViewEvent.kt deleted file mode 100644 index f60b160..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamViewEvent.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.list - -import com.androidvip.sysctlgui.domain.models.DomainKernelParam - -sealed interface ParamViewEvent { - object RefreshRequested : ParamViewEvent - class SearchExpressionChanged(val data: String) : ParamViewEvent - class ParamClicked(val param: DomainKernelParam) : ParamViewEvent -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamViewState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamViewState.kt deleted file mode 100644 index 865215c..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/list/ParamViewState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.list - -import com.androidvip.sysctlgui.data.models.KernelParam - -data class ParamViewState( - var data: List = listOf(), - var isLoading: Boolean = true, - var showEmptyState: Boolean = false -) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/BaseManageParamsActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/BaseManageParamsActivity.kt deleted file mode 100644 index 4f23947..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/BaseManageParamsActivity.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.user - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.runtime.getValue -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.androidvip.sysctlgui.data.models.KernelParam -import com.androidvip.sysctlgui.ui.params.edit.EditKernelParamActivity -import com.androidvip.sysctlgui.utils.ComposeTheme -import org.koin.androidx.viewmodel.ext.android.viewModel - -abstract class BaseManageParamsActivity : ComponentActivity() { - private val viewModel: UserParamsViewModel by viewModel() - abstract val filterPredicate: (KernelParam) -> Boolean - abstract val title: String - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - ComposeTheme { - val state by viewModel.uiState.collectAsStateWithLifecycle() - UserParamsScreen( - topBarTitle = title, - params = state.params, - searchViewVisible = state.searchViewVisible, - onQueryChanged = { - viewModel.onEvent(UserParamsViewEvent.SearchQueryChanged(it)) - }, - onSearch = { viewModel.onEvent(UserParamsViewEvent.SearchPressed) }, - onSearchPressed = { viewModel.onEvent(UserParamsViewEvent.SearchViewPressed) }, - onSearchClose = { viewModel.onEvent(UserParamsViewEvent.CloseSearchPressed) }, - onParamClicked = { startActivity(EditKernelParamActivity.getIntent(this, it)) }, - onDelete = { viewModel.onEvent(UserParamsViewEvent.DeleteSwipe(it)) }, - onBackPressed = { onBackPressedDispatcher.onBackPressed() } - ) - } - } - } - - override fun onStart() { - super.onStart() - viewModel.setBaseFilterPredicate(filterPredicate) - viewModel.onEvent(UserParamsViewEvent.ParamsRequested) - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/ManageFavoritesParamsActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/ManageFavoritesParamsActivity.kt deleted file mode 100644 index 8e2b1be..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/ManageFavoritesParamsActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.user - -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.KernelParam - -class ManageFavoritesParamsActivity : BaseManageParamsActivity() { - override val title: String - get() = getString(R.string.tasker_list_plugin_favorites) - - override val filterPredicate: (KernelParam) -> Boolean - get() = { it.favorite } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/ManageOnStartUpParamsActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/ManageOnStartUpParamsActivity.kt deleted file mode 100644 index 9b6a726..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/ManageOnStartUpParamsActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.user - -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.KernelParam - -class ManageOnStartUpParamsActivity : BaseManageParamsActivity() { - override val title: String - get() = getString(R.string.manage_parameters) - - override val filterPredicate: (KernelParam) -> Boolean - get() = { true } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsScreen.kt deleted file mode 100644 index 5f5abf0..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsScreen.kt +++ /dev/null @@ -1,242 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.user - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.outlined.ArrowBack -import androidx.compose.material.icons.outlined.Close -import androidx.compose.material.icons.outlined.Search -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SearchBar -import androidx.compose.material3.SwipeToDismissBox -import androidx.compose.material3.SwipeToDismissBoxValue -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.rememberSwipeToDismissBoxState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.KernelParam -import com.androidvip.sysctlgui.design.theme.md_theme_light_background -import com.androidvip.sysctlgui.ui.params.EmptyParamsWarning -import com.androidvip.sysctlgui.ui.params.list.ParamItem - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun UserParamsScreen( - topBarTitle: String, - params: List, - searchViewVisible: Boolean, - onQueryChanged: (String) -> Unit, - onSearch: (String) -> Unit, - onSearchPressed: () -> Unit, - onSearchClose: () -> Unit, - onDelete: (KernelParam) -> Unit, - onParamClicked: (KernelParam) -> Unit, - onBackPressed: () -> Unit -) { - val listState = rememberLazyListState() - - Scaffold( - topBar = { - if (searchViewVisible) { - ParamSearch( - onSearch = onSearch, - onClose = onSearchClose, - onQueryChanged = onQueryChanged - ) - } else { - TopAppBar( - title = { Text(text = topBarTitle) }, - navigationIcon = { - IconButton(onClick = onBackPressed) { - Icon( - imageVector = Icons.AutoMirrored.Outlined.ArrowBack, - contentDescription = stringResource(id = R.string.restore_param), - tint = MaterialTheme.colorScheme.onPrimaryContainer - ) - } - }, - actions = { - IconButton(onClick = onSearchPressed) { - Icon( - imageVector = Icons.Outlined.Search, - contentDescription = stringResource(id = R.string.search), - tint = MaterialTheme.colorScheme.onSurface - ) - } - } - ) - } - } - ) { contentPadding -> - if (params.isEmpty()) { - Box(modifier = Modifier.padding(top = 64.dp)) { EmptyParamsWarning() } - } else { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(contentPadding), - state = listState - ) { - items( - items = params, - key = { param -> param.id }, - itemContent = { param -> - SwipeToDismissContent( - onParamClick = onParamClicked, - onDelete = onDelete, - param = param - ) - } - ) - } - } - } -} - -@Composable -@OptIn(ExperimentalMaterial3Api::class) -private fun SwipeToDismissContent( - onParamClick: (KernelParam) -> Unit, - onDelete: (KernelParam) -> Unit, - param: KernelParam -) { - val currentParam by rememberUpdatedState(newValue = param) - val dismissState = rememberSwipeToDismissBoxState( - confirmValueChange = { - fun getResultFromValueChange(): Boolean { - if (it == SwipeToDismissBoxValue.EndToStart) { - onDelete(currentParam) - return true - } - return false - } - getResultFromValueChange() - } - ) - - SwipeToDismissBox( - state = dismissState, - enableDismissFromEndToStart = true, - backgroundContent = { - Box( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.error), - contentAlignment = Alignment.CenterEnd - ) { - Icon( - modifier = Modifier.padding(end = 16.dp), - painter = painterResource(id = R.drawable.ic_delete_sweep), - contentDescription = "", - tint = MaterialTheme.colorScheme.onError - ) - } - } - ) { - Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { - ParamItem( - onParamClick = onParamClick, - param = param - ) - HorizontalDivider( - thickness = 1.dp, - color = MaterialTheme.colorScheme.outlineVariant - ) - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun ParamSearch(onSearch: (String) -> Unit, onClose: () -> Unit, onQueryChanged: (String) -> Unit) { - var searchText by remember { mutableStateOf("") } - - SearchBar( - modifier = Modifier.fillMaxWidth(), - query = searchText, - onQueryChange = { searchText = it; onQueryChanged(it) }, - onSearch = onSearch, - active = false, - shape = RoundedCornerShape(0.dp), - onActiveChange = { }, - leadingIcon = { - Icon( - imageVector = Icons.Outlined.Search, - contentDescription = stringResource(id = R.string.search), - tint = MaterialTheme.colorScheme.onPrimaryContainer - ) - }, - trailingIcon = { - IconButton(onClick = onClose) { - Icon( - imageVector = Icons.Outlined.Close, - contentDescription = stringResource(id = android.R.string.cancel), - tint = MaterialTheme.colorScheme.onPrimaryContainer - ) - } - }, - placeholder = { - Text( - text = stringResource(id = R.string.search), - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - } - ) { - } -} - -@Preview -@Composable -private fun UserParamsScreenPreview() { - val params = buildList { - repeat(15) { n -> - add( - KernelParam( - id = n, - favorite = n % 3 == 0, - name = buildString { (0..n).forEach { append((it * 4).toChar()) } }, - value = "${n * 31}" - ) - ) - } - } - Box(modifier = Modifier.background(md_theme_light_background)) { - UserParamsScreen( - topBarTitle = "Favorites", - params = params, - searchViewVisible = false, - onQueryChanged = {}, - onSearch = {}, - onSearchPressed = {}, - onParamClicked = {}, - onDelete = {}, - onSearchClose = {}, - onBackPressed = {} - ) - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewEvent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewEvent.kt deleted file mode 100644 index 2d43672..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewEvent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.user - -import com.androidvip.sysctlgui.data.models.KernelParam - -sealed interface UserParamsViewEvent { - object ParamsRequested : UserParamsViewEvent - object SearchViewPressed : UserParamsViewEvent - object SearchPressed : UserParamsViewEvent - object CloseSearchPressed : UserParamsViewEvent - class DeleteSwipe(val param: KernelParam) : UserParamsViewEvent - class SearchQueryChanged(val query: String) : UserParamsViewEvent -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewModel.kt deleted file mode 100644 index 8ea0e27..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewModel.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.user - -import androidx.lifecycle.viewModelScope -import com.androidvip.sysctlgui.data.mapper.DomainParamMapper -import com.androidvip.sysctlgui.data.models.KernelParam -import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase -import com.androidvip.sysctlgui.domain.usecase.RemoveUserParamUseCase -import com.androidvip.sysctlgui.domain.usecase.UpdateUserParamUseCase -import com.androidvip.sysctlgui.utils.BaseViewModel -import kotlinx.coroutines.launch - -typealias ParamFilterPredicate = (KernelParam) -> Boolean - -class UserParamsViewModel( - private val getParamsUseCase: GetUserParamsUseCase, - private val removeParamUseCase: RemoveUserParamUseCase, - private val updateParamUseCase: UpdateUserParamUseCase -) : BaseViewModel() { - private var baseFilterPredicate: ParamFilterPredicate = { true } - private var currentFilterPredicate: ParamFilterPredicate = { true } - - override fun createInitialState(): UserParamsViewState = UserParamsViewState() - - override fun onEvent(event: UserParamsViewEvent) { - when (event) { - UserParamsViewEvent.ParamsRequested -> getParams() - UserParamsViewEvent.SearchPressed -> getParams() - UserParamsViewEvent.CloseSearchPressed -> { - setState { copy(searchViewVisible = false) } - } - UserParamsViewEvent.SearchViewPressed -> { - setState { copy(searchViewVisible = true) } - } - is UserParamsViewEvent.DeleteSwipe -> { - if (event.param.favorite) { - event.param.favorite = false - update(event.param) - } else { - delete(event.param) - } - } - is UserParamsViewEvent.SearchQueryChanged -> { - currentFilterPredicate = { - it.name - .replace(".", "") - .contains(event.query, ignoreCase = true) && - baseFilterPredicate(it) - } - } - } - } - private fun getParams() { - viewModelScope.launch { - val params = getParamsUseCase() - .map { DomainParamMapper.map(it) } - .filter(currentFilterPredicate) - - setState { copy(params = params) } - } - } - - fun setBaseFilterPredicate(predicate: ParamFilterPredicate) { - baseFilterPredicate = predicate - currentFilterPredicate = baseFilterPredicate - } - - private fun delete(kernelParam: KernelParam) { - viewModelScope.launch { - removeParamUseCase.execute(kernelParam) - getParams() - } - } - - private fun update(kernelParam: KernelParam) { - viewModelScope.launch { - updateParamUseCase(kernelParam) - getParams() - } - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewState.kt deleted file mode 100644 index d3bcc4f..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/user/UserParamsViewState.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.androidvip.sysctlgui.ui.params.user - -import com.androidvip.sysctlgui.data.models.KernelParam - -data class UserParamsViewState( - val searchViewVisible: Boolean = false, - val params: List = emptyList(), -) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/ImportPresetScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/ImportPresetScreen.kt new file mode 100644 index 0000000..7f78ae5 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/ImportPresetScreen.kt @@ -0,0 +1,357 @@ +package com.androidvip.sysctlgui.ui.presets + +import android.widget.Toast +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +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.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.CheckCircle +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +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.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.PreviewDynamicColors +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.ui.main.MainViewEvent +import com.androidvip.sysctlgui.ui.main.MainViewModel +import com.androidvip.sysctlgui.ui.main.MainViewState +import org.koin.androidx.compose.koinViewModel + +private const val SUCCESS_ANIMATION_DURATION = 4000 + +@OptIn(ExperimentalAnimationApi::class) +@Composable +fun ImportPresetScreen( + viewModel: PresetsViewModel = koinViewModel(), + mainViewModel: MainViewModel = koinViewModel(), + onNavigateBack: () -> Unit +) { + val context = LocalContext.current + val state by viewModel.uiState.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + mainViewModel.onEvent( + MainViewEvent.OnSateChangeRequested( + MainViewState( + topBarTitle = "Applying preset", + showTopBar = true, + showNavBar = false, + showBackButton = true, + showSearchAction = false + ) + ) + ) + } + + LaunchedEffect(viewModel.effect) { + viewModel.effect.collect { effect -> + when (effect) { + is PresetsViewEffect.ShowError -> { + Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show() + } + is PresetsViewEffect.ShowToast -> { + Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show() + } + PresetsViewEffect.GoBack -> onNavigateBack() + else -> {} + } + } + } + + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + AnimatedContent( + targetState = state.incomingPresetsScreenState, + transitionSpec = { + val isLoadingInitialState = initialState == IncomingPresetsScreenState.Loading + val isSuccessTargetState = targetState == IncomingPresetsScreenState.Success + if (isLoadingInitialState && isSuccessTargetState) { + val enterTransition = fadeIn() + scaleIn(initialScale = 0.8f) + val exitTransition = fadeOut() + scaleOut(targetScale = 0.9f) + enterTransition togetherWith exitTransition + } else { + fadeIn() togetherWith fadeOut() + } + } + ) { targetState -> + when (targetState) { + IncomingPresetsScreenState.Idle -> { + IncomingPresetsContent( + paramsToImport = state.paramsToImport, + onImportPressed = { + viewModel.onEvent(PresetsViewEvent.ConfirmImportPressed) + }, + onCancelPressed = { + viewModel.onEvent(PresetsViewEvent.CancelImportPressed) + } + ) + } + + IncomingPresetsScreenState.Loading -> { + LoadingIndicator() + } + + IncomingPresetsScreenState.Success -> { + SuccessIndicator( + onAnimationEnd = { + viewModel.onEvent(PresetsViewEvent.CancelImportPressed) + } + ) + } + } + } + } +} + +@Composable +private fun IncomingPresetsContent( + paramsToImport: List, + onImportPressed: () -> Unit, + onCancelPressed: () -> Unit +) { + Column(modifier = Modifier.fillMaxSize()) { + Text( + text = "${paramsToImport.size} parameters found", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onBackground, + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceContainerHigh) + .padding(16.dp) + ) + + HorizontalDivider() + + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + itemsIndexed( + items = paramsToImport, + key = { index, item -> item.name } + ) { index, item -> + Row( + modifier = Modifier + .height(IntrinsicSize.Min) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .width(36.dp) + .fillMaxHeight() + .background(MaterialTheme.colorScheme.surfaceContainerHigh) + ) { + Text( + text = "${index + 1}", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.End, + modifier = Modifier + .fillMaxWidth() + .padding(4.dp) + ) + } + + val text = buildAnnotatedString { + withStyle( + SpanStyle( + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary + ) + ) { + append(item.name) + } + append("=") + withStyle( + SpanStyle( + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.tertiary + ) + ) { + append(item.value) + } + } + Text( + text = text, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Medium, + modifier = Modifier + .weight(1f) + .padding(4.dp) + ) + } + } + } + + HorizontalDivider() + + Row( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceContainer) + .padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally) + ) { + OutlinedButton( + modifier = Modifier.weight(1f), + onClick = onCancelPressed + ) { + Text(text = "Cancel") + } + + OutlinedButton( + modifier = Modifier.weight(1f), + onClick = onImportPressed, + enabled = paramsToImport.isNotEmpty(), + colors = ButtonDefaults.outlinedButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Text(text = "Import") + } + } + } +} + +@Composable +private fun LoadingIndicator() { + Column( + modifier = Modifier + .fillMaxSize() + .padding(32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically) + ) { + CircularProgressIndicator() + Text( + text = "Loading preset...", + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.Center + ) + } +} + +@Composable +private fun SuccessIndicator(onAnimationEnd: () -> Unit) { + var animationStarted by remember { mutableStateOf(false) } + val progressTarget = if (animationStarted) 0f else 1f + + val progress by animateFloatAsState( + targetValue = progressTarget, + animationSpec = tween(durationMillis = SUCCESS_ANIMATION_DURATION, easing = LinearEasing), + finishedListener = { value -> + if (value == 0f) { + onAnimationEnd() + } + } + ) + + LaunchedEffect(Unit) { animationStarted = true } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically) + ) { + Icon( + imageVector = Icons.Rounded.CheckCircle, + contentDescription = "Success", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(128.dp) + ) + + Text( + text = "Presets successfully imported", + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.Center + ) + + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.primary, + trackColor = MaterialTheme.colorScheme.surfaceVariant, + progress = { progress } + ) + } +} + +@Composable +@PreviewLightDark +@PreviewDynamicColors +private fun IncomingPresetsScreenPreview() { + SysctlGuiTheme(dynamicColor = true) { + Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { + IncomingPresetsContent( + paramsToImport = buildList { + repeat(16) { + add( + KernelParam( + name = "vm.swappiness.$it", + value = "value$it", + path = "" + ) + ) + } + }, + onImportPressed = {}, + onCancelPressed = {}, + ) + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt new file mode 100644 index 0000000..3b3b98a --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt @@ -0,0 +1,215 @@ +package com.androidvip.sysctlgui.ui.presets + +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Card +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +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.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.tooling.preview.PreviewDynamicColors +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.ui.components.ErrorContainer +import com.androidvip.sysctlgui.ui.main.MainViewEvent +import com.androidvip.sysctlgui.ui.main.MainViewModel +import com.androidvip.sysctlgui.ui.main.MainViewState +import org.koin.compose.viewmodel.koinViewModel + +@Composable +fun PresetsScreen( + viewModel: PresetsViewModel = koinViewModel(), + mainViewModel: MainViewModel = koinViewModel(), + onNavigateBack: () -> Unit, + onNavigateToImport: () -> Unit, +) { + val context = LocalContext.current + val state by viewModel.uiState.collectAsStateWithLifecycle() + val pickFileLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.OpenDocument(), + onResult = { uri -> + viewModel.onEvent(PresetsViewEvent.PresetFilePicked(uri)) + } + ) + val createFileLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.CreateDocument("text/plain"), + onResult = { uri -> + viewModel.onEvent(PresetsViewEvent.BackUpFileCreated(uri)) + } + ) + var showError by remember { mutableStateOf(false) } + var errorMessage by remember { mutableStateOf("") } + + LaunchedEffect(Unit) { + mainViewModel.onEvent( + MainViewEvent.OnSateChangeRequested( + MainViewState( + showTopBar = true, + showNavBar = true, + showBackButton = false, + showSearchAction = false + ) + ) + ) + } + + LaunchedEffect(viewModel.effect) { + viewModel.effect.collect { effect -> + when (effect) { + is PresetsViewEffect.ShowError -> { + errorMessage = effect.message + showError = true + } + PresetsViewEffect.ShowImportScreen -> onNavigateToImport() + is PresetsViewEffect.ShowToast -> { + Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show() + } + PresetsViewEffect.GoBack -> onNavigateBack() + } + } + } + + AnimatedVisibility(visible = state.loading) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } + + PresetsScreenContent( + onImportPressed = { pickFileLauncher.launch(arrayOf("*/*")) }, + onExportPressed = { + val defaultFileName = "backup.conf" + createFileLauncher.launch(defaultFileName) + }, + onErrorAnimationEnd = { showError = false }, + showError = showError, + errorMessage = errorMessage + ) +} + +@Composable +private fun PresetsScreenContent( + onImportPressed: () -> Unit, + onExportPressed: () -> Unit, + onErrorAnimationEnd: () -> Unit, + showError: Boolean, + errorMessage: String +) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + ImportCards( + onClick = onImportPressed, + title = "Import", + description = "Import presets from a file", + iconRes = R.drawable.ic_import + ) + ImportCards( + onClick = onExportPressed, + title = "Export", + description = "Export presets to a file", + iconRes = R.drawable.ic_export + ) + + Spacer(modifier = Modifier.weight(1f)) + + AnimatedVisibility( + visible = showError && errorMessage.isNotEmpty(), + enter = slideInVertically { it / 2 } + fadeIn(), + exit = slideOutVertically{ it / 2 } + fadeOut(), + modifier = Modifier.padding(bottom = 16.dp) + ) { + ErrorContainer(message = errorMessage, onAnimationEnd = onErrorAnimationEnd) + } + } +} + +@Composable +private fun ImportCards(onClick: () -> Unit, title: String, description: String, iconRes: Int) { + Card(onClick = onClick, modifier = Modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .padding(16.dp) + .semantics(mergeDescendants = true) { + contentDescription = "$title - $description" + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Icon( + modifier = Modifier.size(40.dp), + painter = painterResource(id = iconRes), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + Column { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface + ) + Text( + text = description, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } +} + + + +@Composable +@PreviewLightDark +@PreviewDynamicColors +private fun PresetsScreenPreview() { + SysctlGuiTheme(dynamicColor = true) { + Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { + PresetsScreenContent( + onImportPressed = {}, + onExportPressed = {}, + onErrorAnimationEnd = {}, + showError = true, + errorMessage = "Error message" + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsViewModel.kt new file mode 100644 index 0000000..2ef1f1f --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsViewModel.kt @@ -0,0 +1,109 @@ +package com.androidvip.sysctlgui.ui.presets + +import android.net.Uri +import android.util.Log +import androidx.lifecycle.viewModelScope +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.data.utils.PresetsFileProcessor +import com.androidvip.sysctlgui.domain.StringProvider +import com.androidvip.sysctlgui.domain.exceptions.EmptyFileException +import com.androidvip.sysctlgui.domain.exceptions.MalformedLineException +import com.androidvip.sysctlgui.domain.exceptions.NoValidParamException +import com.androidvip.sysctlgui.domain.usecase.AddUserParamsUseCase +import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase +import com.androidvip.sysctlgui.utils.BaseViewModel +import kotlinx.coroutines.launch +import java.io.IOException + +class PresetsViewModel( + private val getUserParams: GetUserParamsUseCase, + private val addUserParams: AddUserParamsUseCase, + private val presetsFileProcessor: PresetsFileProcessor, + private val stringProvider: StringProvider +) : BaseViewModel() { + override fun createInitialState() = PresetsViewState() + + override fun onEvent(event: PresetsViewEvent) { + when (event) { + PresetsViewEvent.CancelImportPressed -> setEffect { PresetsViewEffect.GoBack } + PresetsViewEvent.ConfirmImportPressed -> confirmImport() + is PresetsViewEvent.PresetFilePicked -> handleImportPreset(event.uri) + is PresetsViewEvent.BackUpFileCreated -> handleBackup(event.uri) + } + } + + private fun confirmImport() { + viewModelScope.launch { + setState { copy(incomingPresetsScreenState = IncomingPresetsScreenState.Loading) } + val paramsToImport = uiState.value.paramsToImport + runCatching { + addUserParams(paramsToImport) + }.onSuccess { + setState { + copy( + paramsToImport = emptyList(), + incomingPresetsScreenState = IncomingPresetsScreenState.Success + ) + } + }.onFailure { + setState { copy(incomingPresetsScreenState = IncomingPresetsScreenState.Idle) } + setEffect { PresetsViewEffect.ShowError("Failed to import params") } + } + } + } + + private fun handleImportPreset(uri: Uri?) { + if (uri == null) { + setEffect { PresetsViewEffect.ShowError(stringProvider.getString(R.string.preset_error_file_picking)) } + return + } + + viewModelScope.launch { + try { + val params = presetsFileProcessor.getKernelParamsFromUri(uri) + setState { + copy( + paramsToImport = params, + incomingPresetsScreenState = IncomingPresetsScreenState.Idle + ) + } + setEffect { PresetsViewEffect.ShowImportScreen } + } catch (_: EmptyFileException) { + setEffect { PresetsViewEffect.ShowError(stringProvider.getString(R.string.import_error_empty_file)) } + } catch (_: MalformedLineException) { + setEffect { PresetsViewEffect.ShowError(stringProvider.getString(R.string.import_error_malformed_line)) } + } catch (_: NoValidParamException) { + setEffect { PresetsViewEffect.ShowError(stringProvider.getString(R.string.export_error_no_param)) } + } catch (_: IOException) { + setEffect { PresetsViewEffect.ShowError(stringProvider.getString(R.string.export_error_io)) } + } catch (e: Exception) { + Log.e("PresetsViewModel", "Error importing file", e) + setEffect { PresetsViewEffect.ShowError(stringProvider.getString(R.string.import_error)) } + } + } + } + + private fun handleBackup(uri: Uri?) { + if (uri == null) { + setEffect { PresetsViewEffect.ShowError(stringProvider.getString(R.string.preset_error_file_creation)) } + return + } + + viewModelScope.launch { + try { + val userParams = getUserParams() + + runCatching { + presetsFileProcessor.backupParamsToUri(uri, userParams) + }.onSuccess { + setEffect { PresetsViewEffect.ShowToast("Export complete") } + }.onFailure { + setEffect { PresetsViewEffect.ShowError(stringProvider.getString(R.string.preset_error_processing_file)) } + } + } catch (e: Exception) { + Log.e("PresetsViewModel", "Error saving file", e) + setEffect { PresetsViewEffect.ShowError(stringProvider.getString(R.string.preset_error_opening_file)) } + } + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsViewState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsViewState.kt new file mode 100644 index 0000000..80f4a7a --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsViewState.kt @@ -0,0 +1,30 @@ +package com.androidvip.sysctlgui.ui.presets + +import android.net.Uri +import com.androidvip.sysctlgui.domain.models.KernelParam + +data class PresetsViewState( + val paramsToImport: List = emptyList(), + val loading: Boolean = false, + val incomingPresetsScreenState: IncomingPresetsScreenState = IncomingPresetsScreenState.Idle +) + +enum class IncomingPresetsScreenState { + Idle, + Loading, + Success +} + +sealed interface PresetsViewEvent { + data class PresetFilePicked(val uri: Uri?) : PresetsViewEvent + data class BackUpFileCreated(val uri: Uri?) : PresetsViewEvent + data object ConfirmImportPressed : PresetsViewEvent + data object CancelImportPressed : PresetsViewEvent +} + +sealed interface PresetsViewEffect { + data class ShowError(val message: String) : PresetsViewEffect + data class ShowToast(val message: String) : PresetsViewEffect + data object ShowImportScreen : PresetsViewEffect + data object GoBack : PresetsViewEffect +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt new file mode 100644 index 0000000..b6ab33a --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt @@ -0,0 +1,462 @@ +package com.androidvip.sysctlgui.ui.search + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandHorizontally +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkHorizontally +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.ArrowBack +import androidx.compose.material.icons.rounded.Clear +import androidx.compose.material.icons.rounded.Search +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.ListItemDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SearchBar +import androidx.compose.material3.SearchBarDefaults +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.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.models.SearchHint +import com.androidvip.sysctlgui.models.UiKernelParam +import com.androidvip.sysctlgui.ui.main.MainViewEvent +import com.androidvip.sysctlgui.ui.main.MainViewModel +import com.androidvip.sysctlgui.ui.main.MainViewState +import com.androidvip.sysctlgui.ui.params.browse.ParamRow +import org.koin.compose.viewmodel.koinViewModel + +@Composable +fun SearchScreen( + viewModel: SearchViewModel = koinViewModel(), + mainViewModel: MainViewModel = koinViewModel(), + onParamSelected: (UiKernelParam) -> Unit, + onNavigateBack: () -> Unit +) { + val state by viewModel.uiState.collectAsStateWithLifecycle() + var searchQuery by remember { mutableStateOf("") } + var searchActive by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + mainViewModel.onEvent( + MainViewEvent.OnSateChangeRequested( + MainViewState( + showTopBar = false, + showNavBar = true, + ) + ) + ) + } + + LaunchedEffect(viewModel.effect) { + viewModel.effect.collect { effect -> + when (effect) { + is SearchViewEffect.EditKernelParam -> onParamSelected(effect.param) + SearchViewEffect.NavigateBack -> onNavigateBack() + } + } + } + + SearchScreenContent( + searchQuery = searchQuery, + onSearchQueryChange = { + searchQuery = it + viewModel.onEvent(SearchViewEvent.SearchQueryChange(it)) + }, + searchActive = searchActive, + onSearchActiveChange = { searchActive = it }, + searchHints = state.searchHints, + searchResults = state.searchResults, + onNavigateBack = { viewModel.onEvent(SearchViewEvent.BackClicked) }, + onHistoryItemRemoveClicked = { + viewModel.onEvent(SearchViewEvent.HistoryItemRemoveClicked(it)) + }, + onSearch = { query -> + searchActive = false + viewModel.onEvent(SearchViewEvent.SearchRequested(query)) + } + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun SearchScreenContent( + searchQuery: String, + onSearchQueryChange: (String) -> Unit, + searchActive: Boolean, + onSearchActiveChange: (Boolean) -> Unit, + searchHints: List, + searchResults: List, + onHistoryItemRemoveClicked: (SearchHint) -> Unit, + onNavigateBack: () -> Unit, + onSearch: (String) -> Unit +) { + val focusManager = LocalFocusManager.current + val searchBarHorizontalPadding by animateDpAsState( + targetValue = if (searchActive) 0.dp else 16.dp, + label = "SearchBarHorizontalPadding", + animationSpec = tween(durationMillis = 300) + ) + val searchBarTopPadding by animateDpAsState( + targetValue = if (searchActive) 0.dp else 8.dp, + label = "SearchBarTopPadding", + animationSpec = tween(durationMillis = 300) + ) + + Scaffold( + topBar = { + val onActiveChange: (Boolean) -> Unit = { isActive -> + if (searchActive != isActive) { + onSearchActiveChange(isActive) + } + if (!isActive && searchQuery.isNotEmpty()) { + onSearchQueryChange("") + } + } + val searchBarColors = SearchBarDefaults.colors() + SearchBar( + inputField = { + SearchBarDefaults.InputField( + query = searchQuery, + onQueryChange = onSearchQueryChange, + onSearch = onSearch, + expanded = searchActive, + onExpandedChange = onActiveChange, + placeholder = { Text("Search kernel parameters") }, + leadingIcon = { + AnimatedVisibility( + visible = searchActive, + enter = expandVertically() + fadeIn(), + exit = shrinkVertically() + fadeOut() + ) { + IconButton(onClick = { + focusManager.clearFocus() + onSearchActiveChange(false) + onSearchQueryChange("") + if (searchQuery.isEmpty()) { + onNavigateBack() + } + }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + contentDescription = "Back" + ) + } + } + + + AnimatedVisibility( + visible = !searchActive, + enter = expandVertically( + animationSpec = tween(delayMillis = 200) + ) + fadeIn( + animationSpec = tween(delayMillis = 200) + ), + exit = shrinkVertically( + animationSpec = tween(durationMillis = 0) + ) + fadeOut( + animationSpec = tween(durationMillis = 0) + ) + ) { + Icon(Icons.Rounded.Search, contentDescription = "Search Icon") + } + }, + trailingIcon = { + AnimatedVisibility( + visible = searchQuery.isNotEmpty() && searchActive, + enter = expandHorizontally(expandFrom = Alignment.Start) + fadeIn(), + exit = shrinkHorizontally(shrinkTowards = Alignment.Start) + fadeOut() + ) { + IconButton(onClick = { + onSearchQueryChange("") + }) { + Icon(Icons.Rounded.Clear, contentDescription = "Clear search") + } + } + }, + colors = searchBarColors.inputFieldColors, + ) + }, + expanded = searchActive, + onExpandedChange = onActiveChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = searchBarHorizontalPadding) + .padding(top = searchBarTopPadding), + shape = SearchBarDefaults.inputFieldShape, + colors = searchBarColors, + tonalElevation = SearchBarDefaults.TonalElevation, + shadowElevation = SearchBarDefaults.ShadowElevation, + windowInsets = SearchBarDefaults.windowInsets, + ) { + SearchViewContent( + searchHints = searchHints, + onHistoryItemRemoveClicked = onHistoryItemRemoveClicked, + onSearchQueryChange = onSearchQueryChange, + onSearch = onSearch, + onSearchActiveChange = onSearchActiveChange, + searchQuery = searchQuery + ) + } + } + ) { innerPadding -> + Box(modifier = Modifier.padding(innerPadding)) { + if (!searchActive) { + SearchResultsContent(searchResults, searchQuery) + } + + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } + } +} + +@Composable +private fun SearchViewContent( + searchHints: List, + onHistoryItemRemoveClicked: (SearchHint) -> Unit, + onSearchQueryChange: (String) -> Unit, + onSearch: (String) -> Unit, + onSearchActiveChange: (Boolean) -> Unit, + searchQuery: String +) { + val historyHints = searchHints.filter { it.isFromHistory } + val suggestionHints = searchHints.filter { !it.isFromHistory } + + LazyColumn(modifier = Modifier.fillMaxWidth()) { + if (historyHints.isNotEmpty()) { + item(key = "history_header") { + Text( + text = "Recent Searches", + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .animateItem() + .padding(horizontal = 16.dp, vertical = 12.dp) + ) + } + items(historyHints, key = { it.hint }) { hintItem -> + ListItem( + colors = ListItemDefaults.colors( + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh + ), + headlineContent = { Text(hintItem.hint) }, + leadingContent = { + Icon( + painter = painterResource(R.drawable.ic_history), + contentDescription = "History item" + ) + }, + trailingContent = { + IconButton( + onClick = { onHistoryItemRemoveClicked(hintItem) }, + modifier = Modifier.offset(16.dp) + ) { + Icon( + Icons.Rounded.Clear, + contentDescription = "Clear history item" + ) + } + }, + modifier = Modifier + .clickable { + onSearchQueryChange(hintItem.hint) + onSearch(hintItem.hint) + onSearchActiveChange(false) + } + .animateItem() + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) + } + if (suggestionHints.isNotEmpty()) { + item { HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) } + } + } + + if (suggestionHints.isNotEmpty()) { + item { + Text( + text = "Suggestions", + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp) + ) + } + items(suggestionHints, key = { it.hint }) { hintItem -> + ListItem( + colors = ListItemDefaults.colors( + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh + ), + headlineContent = { Text(hintItem.hint) }, + modifier = Modifier + .clickable { + onSearchQueryChange(hintItem.hint) + onSearch(hintItem.hint) + onSearchActiveChange(false) + } + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) + } + } + + if (searchHints.isEmpty() && searchQuery.isBlank()) { + item { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + Text("No suggestions available.") + } + } + } + } +} + +@Composable +private fun SearchResultsContent(searchResults: List, searchQuery: String) { + if (searchResults.isNotEmpty()) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(top = 8.dp) + ) { + itemsIndexed(searchResults) { index, param -> + ParamRow( + modifier = Modifier.animateItem(), + param = param, + showFullName = true, + onParamClicked = {} + ) + + if (index < searchResults.lastIndex) { + HorizontalDivider() + } + } + } + } else if (searchQuery.isNotEmpty()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(24.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "No results found for \"$searchQuery\"", + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + } + } else { + Box( + modifier = Modifier + .fillMaxSize() + .padding(24.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "Enter a query to search for kernel parameters.", + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + } + } +} + +@Composable +@Preview +private fun SearchScreenPreview() { + SysctlGuiTheme { + var searchQuery by remember { mutableStateOf("") } + var searchActive by remember { mutableStateOf(false) } + var searchHints by remember { + mutableStateOf( + listOf( + SearchHint("vm.swappiness", isFromHistory = false), + SearchHint("net.ipv4.tcp_congestion_control", isFromHistory = false), + SearchHint("kernel.panic", isFromHistory = true), + SearchHint("fs.file-max", isFromHistory = true) + ) + ) + } + var searchResults by remember { + mutableStateOf( + listOf( + UiKernelParam( + name = "vm.swappiness", + path = "/proc/sys/vm/swappiness", + isFavorite = false + ), + UiKernelParam( + name = "vm.overcommit_memory", + path = "/proc/sys/vm/overcommit_memory", + value = "1", + isFavorite = true + ), + UiKernelParam( + name = "net.ipv4.tcp_congestion_control", + path = "/proc/sys/net/ipv4/tcp_congestion_control", + value = "cubic" + ) + ) + ) + } + + SearchScreenContent( + searchQuery = searchQuery, + onSearchQueryChange = { searchQuery = it }, + searchActive = searchActive, + onSearchActiveChange = { searchActive = it }, + searchHints = searchHints, + searchResults = searchResults, + onNavigateBack = {}, + onHistoryItemRemoveClicked = { searchHints = searchHints - it }, + onSearch = { + searchResults = searchResults.filter { + it.name.contains(searchQuery, ignoreCase = true) + } + } + ) + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewModel.kt new file mode 100644 index 0000000..b024a89 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewModel.kt @@ -0,0 +1,140 @@ +package com.androidvip.sysctlgui.ui.search + +import androidx.lifecycle.viewModelScope +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.repository.AppPrefs +import com.androidvip.sysctlgui.domain.usecase.GetRuntimeParamsUseCase +import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase +import com.androidvip.sysctlgui.helpers.UiKernelParamMapper +import com.androidvip.sysctlgui.models.SearchHint +import com.androidvip.sysctlgui.models.UiKernelParam +import com.androidvip.sysctlgui.utils.BaseViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class SearchViewModel( + private val getUserParams: GetUserParamsUseCase, + private val getRuntimeParams: GetRuntimeParamsUseCase, + private val appPrefs: AppPrefs +) : BaseViewModel() { + private var preSearchJob: Job? = null + private val searchableParams = mutableListOf() + private val searchHistory = mutableSetOf() + + init { + viewModelScope.launch { + setState { copy(loading = true) } + + val fetchedHistory = appPrefs.searchHistory + val fetchedParams = fetchParams() + + searchHistory.addAll(fetchedHistory) + searchableParams.addAll(fetchedParams) + + val uiSearchHints = fetchedHistory.map { SearchHint(hint = it, isFromHistory = true) } + + setState { + copy( + loading = false, + searchHints = uiSearchHints + ) + } + } + } + + override fun createInitialState() = SearchViewState() + + private suspend fun fetchParams(): List { + val userParams = getUserParams() + return getRuntimeParams(userParams).map(UiKernelParamMapper::map) + } + + override fun onEvent(event: SearchViewEvent) { + when (event) { + SearchViewEvent.BackClicked -> setEffect { SearchViewEffect.NavigateBack } + is SearchViewEvent.HistoryItemRemoveClicked -> handleRemoveFromHistory(event.hint) + is SearchViewEvent.SearchRequested -> handleSearch(event.query) + is SearchViewEvent.SearchQueryChange -> handleSearchQueryChange(event.query) + is SearchViewEvent.ParamClicked -> setEffect { + SearchViewEffect.EditKernelParam(event.param) + } + } + } + + private fun handleSearchQueryChange(query: String) { + preSearchJob?.cancel() + if (query.isEmpty()) { + setState { copy(searchResults = emptyList()) } + } else if (query.length >= MIN_PRE_SEARCH_QUERY_LENGTH) { + preSearchJob = viewModelScope.launch { + delay(300L) // Debounce delay + handlePreSearch(query) + } + } + } + + private fun handleSearch(query: String) { + viewModelScope.launch { + setState { copy(loading = true) } + searchHistory.add(query) + appPrefs.addSearchToHistory(query) + + val hints = withContext(Dispatchers.IO) { + val historyHints = searchHistory + .take(MAX_SEARCH_HISTORY) + .map { SearchHint(hint = it, isFromHistory = true) } + + val paramHints = searchableParams + .filter { it.name.contains(query, ignoreCase = true) } + .take(MAX_SEARCH_HINTS) + .map { SearchHint(it.name) } + + historyHints + paramHints + } + + setState { + copy(loading = false, searchHints = hints) + } + } + } + + private fun handlePreSearch(query: String) { + viewModelScope.launch { + val hints = withContext(Dispatchers.IO) { + searchableParams + .filter { it.name.contains(query, ignoreCase = true) } + .map(UiKernelParamMapper::map) + } + + setState { + copy(loading = false, searchResults = hints) + } + } + } + + private fun handleRemoveFromHistory(searchHint: SearchHint) { + viewModelScope.launch { + withContext(Dispatchers.IO) { + appPrefs.removeSearchFromHistory(searchHint.hint) + searchHistory.remove(searchHint.hint) + } + + setState { + copy( + searchHints = searchHistory + .take(MAX_SEARCH_HISTORY) + .map { SearchHint(hint = it, isFromHistory = true) } + ) + } + } + } + + companion object { + private const val MIN_PRE_SEARCH_QUERY_LENGTH = 4 + private const val MAX_SEARCH_HISTORY = 3 + private const val MAX_SEARCH_HINTS = 5 + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewState.kt new file mode 100644 index 0000000..eea820a --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewState.kt @@ -0,0 +1,23 @@ +package com.androidvip.sysctlgui.ui.search + +import com.androidvip.sysctlgui.models.SearchHint +import com.androidvip.sysctlgui.models.UiKernelParam + +data class SearchViewState( + val loading: Boolean = true, + val searchHints: List = emptyList(), + val searchResults: List = emptyList() +) + +sealed interface SearchViewEvent { + data object BackClicked : SearchViewEvent + data class SearchQueryChange(val query: String) : SearchViewEvent + data class HistoryItemRemoveClicked(val hint: SearchHint) : SearchViewEvent + data class SearchRequested(val query: String) : SearchViewEvent + data class ParamClicked(val param: UiKernelParam) : SearchViewEvent +} + +sealed interface SearchViewEffect { + data object NavigateBack : SearchViewEffect + data class EditKernelParam(val param: UiKernelParam) : SearchViewEffect +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsFragment.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsFragment.kt deleted file mode 100644 index afb695a..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsFragment.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.androidvip.sysctlgui.ui.settings - -import android.app.NotificationManager -import android.content.Context -import android.os.Build -import android.os.Bundle -import androidx.activity.result.contract.ActivityResultContracts -import androidx.lifecycle.lifecycleScope -import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat -import androidx.preference.SwitchPreferenceCompat -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.utils.RootUtils -import com.androidvip.sysctlgui.domain.repository.AppPrefs -import com.androidvip.sysctlgui.helpers.StartUpServiceToggle -import com.androidvip.sysctlgui.utils.Consts -import com.google.android.material.color.DynamicColors -import kotlinx.coroutines.launch -import org.koin.android.ext.android.inject - -class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener { - private val prefs: AppPrefs by inject() - private val rootUtils: RootUtils by inject() - - private val notificationPermissionLauncher = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { _ -> } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - setPreferencesFromResource(R.xml.preferences, rootKey) - - val currCommitMode = prefs.commitMode - val commitModePref = findPreference(Consts.Prefs.COMMIT_MODE) - commitModePref?.summary = if (currCommitMode == "sysctl") { - "Use sysctl -w" - } else { - "Use echo 'value' > /proc/sys/…" - } - - val startupDelay = prefs.startUpDelay - val startupDelayPref = findPreference(Consts.Prefs.START_UP_DELAY) - - startupDelayPref?.summary = if (startupDelay > 0) { - getString(R.string.startup_delay_sum, startupDelay) - } else { - getString(R.string.startup_delay_disabled) - } - - val useBusyboxPref = findPreference(Consts.Prefs.USE_BUSYBOX) as SwitchPreferenceCompat? - lifecycleScope.launch { - if (rootUtils.isBusyboxAvailable()) { - useBusyboxPref?.isEnabled = true - } else { - useBusyboxPref?.isChecked = false - useBusyboxPref?.isEnabled = false - } - } - - val dynamicColorsPref = findPreference(Consts.Prefs.DYNAMIC_COLORS) as SwitchPreferenceCompat? - dynamicColorsPref?.isEnabled = DynamicColors.isDynamicColorAvailable() - - commitModePref?.onPreferenceChangeListener = this - startupDelayPref?.onPreferenceChangeListener = this - dynamicColorsPref?.onPreferenceChangeListener = this - findPreference(Consts.Prefs.RUN_ON_START_UP)?.onPreferenceChangeListener = this - findPreference(Consts.Prefs.FORCE_DARK_THEME)?.onPreferenceChangeListener = this - } - - override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { - when (preference.key) { - Consts.Prefs.RUN_ON_START_UP -> { - StartUpServiceToggle.toggleStartUpService(requireContext(), newValue == true) - askForNotificationPermission() - } - - Consts.Prefs.COMMIT_MODE -> { - preference.summary = if (newValue == "sysctl") { - "Use sysctl -w" - } else { - "Use echo 'value' > /proc/sys/…" - } - } - - Consts.Prefs.START_UP_DELAY -> { - val selectedValue = (newValue as? Int) ?: 0 - - preference.summary = if (selectedValue > 0) { - getString(R.string.startup_delay_sum, selectedValue) - } else { - getString(R.string.startup_delay_disabled) - } - } - - Consts.Prefs.FORCE_DARK_THEME -> { - requireActivity().recreate() - } - - Consts.Prefs.DYNAMIC_COLORS -> { - if (newValue == true) { - DynamicColors.applyToActivitiesIfAvailable(requireActivity().application) - } - requireActivity().recreate() - } - } - - return true - } - - private fun askForNotificationPermission() { - val manager = requireContext().getSystemService(Context.NOTIFICATION_SERVICE) - as NotificationManager - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (!manager.areNotificationsEnabled()) { - notificationPermissionLauncher.launch( - android.Manifest.permission.POST_NOTIFICATIONS - ) - } - } - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt new file mode 100644 index 0000000..534a37e --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt @@ -0,0 +1,233 @@ +package com.androidvip.sysctlgui.ui.settings + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Build +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.MaterialTheme +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.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.androidvip.sysctlgui.data.Prefs +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.domain.enums.CommitMode +import com.androidvip.sysctlgui.domain.enums.SettingItemType +import com.androidvip.sysctlgui.domain.models.AppSetting +import com.androidvip.sysctlgui.ui.main.MainViewEvent +import com.androidvip.sysctlgui.ui.main.MainViewModel +import com.androidvip.sysctlgui.ui.main.MainViewState +import com.androidvip.sysctlgui.ui.settings.components.HeaderComponent +import com.androidvip.sysctlgui.ui.settings.components.SliderSettingComponent +import com.androidvip.sysctlgui.ui.settings.components.SwitchSettingComponent +import com.androidvip.sysctlgui.ui.settings.components.TextSettingComponent +import com.androidvip.sysctlgui.ui.settings.model.SettingsViewEffect +import com.androidvip.sysctlgui.ui.settings.model.SettingsViewEvent +import org.koin.androidx.compose.koinViewModel + +internal const val DISABLED_ALPHA = 0.38f + +@Composable +internal fun SettingsScreen( + mainViewModel: MainViewModel = koinViewModel(), + viewModel: SettingsViewModel = koinViewModel(), + onNavigateToUserParams: () -> Unit +) { + val state = viewModel.uiState.collectAsStateWithLifecycle() + val context = LocalContext.current + var hasNotificationPermission by remember { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + mutableStateOf( + ContextCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + ) + } else { + mutableStateOf(true) + } + } + + val permissionLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestPermission(), + onResult = { isGranted -> + hasNotificationPermission = isGranted + } + ) + + + LaunchedEffect(Unit) { + mainViewModel.onEvent( + MainViewEvent.OnSateChangeRequested( + MainViewState( + showTopBar = true, + showNavBar = true, + ) + ) + ) + } + + LaunchedEffect(Unit) { + viewModel.effect.collect { effect -> + when (effect) { + SettingsViewEffect.RequestNotificationPermission -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (!hasNotificationPermission) { + permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } + } + } + } + } + } + + SettingsScreenContent( + settings = state.value.settings, + onNavigateToUserParams = onNavigateToUserParams, + onValueChanged = { appSetting, newValue -> + viewModel.onEvent(SettingsViewEvent.SettingValueChanged(appSetting, newValue)) + } + ) +} + +@Composable +private fun SettingsScreenContent( + settings: List> = emptyList(), + onNavigateToUserParams: () -> Unit, + onValueChanged: (AppSetting<*>, Any) -> Unit +) { + val groupedSettings = settings.groupBy { it.category } + LazyColumn(modifier = Modifier.fillMaxWidth()) { + groupedSettings.forEach { (category, settings) -> + item { + Text( + text = category, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding( + top = 8.dp, + bottom = 8.dp, + start = 56.dp, + end = 16.dp, + ) + ) + } + items(settings.size) { + val appSetting = settings[it] + when (appSetting.type) { + SettingItemType.Text -> { + HeaderComponent( + modifier = Modifier.fillMaxWidth(), + appSetting = appSetting, + onClick = onNavigateToUserParams + ) + } + SettingItemType.List -> { + TextSettingComponent( + modifier = Modifier.fillMaxWidth(), + appSetting = appSetting, + onValueChange = { newValue -> + onValueChanged(appSetting, newValue) + } + ) + } + SettingItemType.Switch -> { + SwitchSettingComponent( + modifier = Modifier.fillMaxWidth(), + appSetting = appSetting, + onValueChange = { newValue -> + onValueChanged(appSetting, newValue) + } + ) + } + SettingItemType.Slider -> { + SliderSettingComponent( + modifier = Modifier.fillMaxWidth(), + appSetting = appSetting, + onValueChange = { newValue -> + onValueChanged(appSetting, newValue) + } + ) + } + } + } + } + } +} + +@Composable +@PreviewLightDark +internal fun SettingsScreenPreview() { + SysctlGuiTheme { + val settings = listOf( + /////////// GENERAL SETTINGS //////////// + AppSetting( + key = Prefs.ListFoldersFirst.key, + value = true, + category = "General", + title = "List folders first", + description = "List folders first when using the kernel parameter browser option", + type = SettingItemType.Switch, + ), + + /////////// THEME SETTINGS //////////// + + AppSetting( + key = Prefs.ContrastLevel.key, + value = 1, + category = "Theme", + title = "Contrast level", + description = "Contrast level for the theme colors", + type = SettingItemType.Slider, + values = listOf(1, 2, 3), + ), + + /////////// COMMIT SETTINGS //////////// + + AppSetting( + key = Prefs.CommitMode.key, + value = "sysctl", + category = "Operations", + title = "Commit mode", + description = "Command used when applying the parameter value", + type = SettingItemType.List, + values = listOf( + CommitMode.SYSCTL.name.lowercase(), + CommitMode.ECHO.name.lowercase(), + ) + ), + + /////////// STARTUP SETTINGS //////////// + AppSetting( + key = "", + value = Unit, + category = "Startup", + title = "Manage parameters", + description = "Manage the parameters that will be applied at startup", + type = SettingItemType.Text, + ) + ) + Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { + SettingsScreenContent( + settings = settings, + onNavigateToUserParams = {}, + onValueChanged = { _, _ -> } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsViewModel.kt new file mode 100644 index 0000000..0828835 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsViewModel.kt @@ -0,0 +1,68 @@ +package com.androidvip.sysctlgui.ui.settings + +import android.content.SharedPreferences +import androidx.core.content.edit +import androidx.lifecycle.viewModelScope +import com.androidvip.sysctlgui.data.Prefs +import com.androidvip.sysctlgui.domain.enums.SettingItemType +import com.androidvip.sysctlgui.domain.usecase.GetAppSettingsUseCase +import com.androidvip.sysctlgui.ui.settings.model.SettingsViewEffect +import com.androidvip.sysctlgui.ui.settings.model.SettingsViewEvent +import com.androidvip.sysctlgui.ui.settings.model.SettingsViewState +import com.androidvip.sysctlgui.utils.BaseViewModel +import kotlinx.coroutines.launch + +class SettingsViewModel( + private val sharedPreferences: SharedPreferences, + private val getSettings: GetAppSettingsUseCase +) : BaseViewModel() { + + init { + viewModelScope.launch { + val settings = getSettings() + setState { copy(settings = settings) } + } + } + + override fun createInitialState() = SettingsViewState() + + override fun onEvent(event: SettingsViewEvent) { + when (event) { + is SettingsViewEvent.SettingValueChanged<*> -> { + if (event.appSetting.type == SettingItemType.Text) return + if (event.appSetting.key.isEmpty()) return + + when (event.newValue) { + is Boolean -> sharedPreferences.edit { + putBoolean(event.appSetting.key, event.newValue) + } + + is Int -> sharedPreferences.edit { + putInt(event.appSetting.key, event.newValue) + } + + is Long -> sharedPreferences.edit { + putLong(event.appSetting.key, event.newValue) + } + + is Float -> sharedPreferences.edit { + putFloat(event.appSetting.key, event.newValue) + } + + is String -> sharedPreferences.edit { + putString(event.appSetting.key, event.newValue) + } + + is List<*> -> sharedPreferences.edit { + val stringValues = event.newValue.filterIsInstance().toSet() + putStringSet(event.appSetting.key, stringValues) + } + } + + if (event.appSetting.key == Prefs.RunOnStartup.key) { + setEffect { SettingsViewEffect.RequestNotificationPermission } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/HeaderComponent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/HeaderComponent.kt new file mode 100644 index 0000000..2c7c59a --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/HeaderComponent.kt @@ -0,0 +1,46 @@ +package com.androidvip.sysctlgui.ui.settings.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.androidvip.sysctlgui.domain.models.AppSetting + +@Composable +fun HeaderComponent( + modifier: Modifier = Modifier, + appSetting: AppSetting<*>, + onClick: () -> Unit, + icon: @Composable (() -> Unit)? = null +) { + Box(modifier = modifier.clickable(onClick = onClick)) { + Row( + modifier = Modifier.padding(all = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(space = 16.dp) + ) { + + Box( + modifier = Modifier + .align(Alignment.CenterVertically) + .size(24.dp) + ) { + icon?.invoke() + } + + SettingsComponentColumn( + title = appSetting.title, + description = appSetting.description, + enabled = appSetting.enabled, + modifier = Modifier.fillMaxWidth() + ) + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/SettingsComponentColumn.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/SettingsComponentColumn.kt new file mode 100644 index 0000000..29e877a --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/SettingsComponentColumn.kt @@ -0,0 +1,45 @@ +package com.androidvip.sysctlgui.ui.settings.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.androidvip.sysctlgui.ui.settings.DISABLED_ALPHA + +@Composable +internal fun SettingsComponentColumn( + modifier: Modifier = Modifier, + title: String, + description: String? = null, + enabled: Boolean = true, + bottomContent: @Composable (ColumnScope.() -> Unit)? = null +) { + Column( + verticalArrangement = Arrangement.Center, + modifier = modifier + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onBackground + .copy(alpha = if (enabled) 1f else DISABLED_ALPHA) + ) + description?.let { + Text( + text = it, + modifier = Modifier.padding(top = 2.dp), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onBackground + .copy(alpha = if (enabled) 0.8f else DISABLED_ALPHA) + ) + } + bottomContent?.let { + bottomContent() + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/SliderSettingComponent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/SliderSettingComponent.kt new file mode 100644 index 0000000..c2e563b --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/SliderSettingComponent.kt @@ -0,0 +1,89 @@ +package com.androidvip.sysctlgui.ui.settings.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.domain.enums.SettingItemType +import com.androidvip.sysctlgui.domain.models.AppSetting + +@Composable +fun SliderSettingComponent( + modifier: Modifier = Modifier, + appSetting: AppSetting<*>, + onValueChange: (Int) -> Unit, + icon: @Composable (() -> Unit)? = null +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier.padding(all = 16.dp), + horizontalArrangement = Arrangement.spacedBy(space = 16.dp) + ) { + Box( + modifier = Modifier + .align(Alignment.CenterVertically) + .size(24.dp) + ) { + icon?.invoke() + } + + val values = appSetting.values?.filterIsInstance() ?: emptyList() + val minValue = values.min().toFloat() + val maxValue = values.max().toFloat() + var value by remember { + mutableFloatStateOf((appSetting.value as? Int)?.toFloat() ?: minValue) + } + + SettingsComponentColumn( + title = appSetting.title, + description = appSetting.description + " (${value.toInt()})", + enabled = appSetting.enabled, + modifier = Modifier.fillMaxWidth() + ) { + Slider( + modifier = Modifier.padding(top = 4.dp), + value = value, + enabled = appSetting.enabled, + onValueChange = { value = it.toInt().toFloat(); onValueChange(it.toInt()) }, + valueRange = minValue..maxValue, + ) + } + } +} + +@Composable +@PreviewLightDark +fun SliderSettingComponentPreview() { + SysctlGuiTheme(dynamicColor = true) { + Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { + SliderSettingComponent( + appSetting = AppSetting( + key = "key", + title = "Title", + description = "Description", + value = 0, + category = "", + type = SettingItemType.Slider, + values = (0..10).toList(), + ), + onValueChange = {} + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/SwitchSettingComponent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/SwitchSettingComponent.kt new file mode 100644 index 0000000..09a1369 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/SwitchSettingComponent.kt @@ -0,0 +1,91 @@ +package com.androidvip.sysctlgui.ui.settings.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.domain.enums.SettingItemType +import com.androidvip.sysctlgui.domain.models.AppSetting + +@Composable +fun SwitchSettingComponent( + modifier: Modifier = Modifier, + appSetting: AppSetting<*>, + onValueChange: (newValue: Boolean) -> Unit, + icon: @Composable (() -> Unit)? = null +) { + var checked by remember { mutableStateOf(appSetting.value as? Boolean) } + + Row( + modifier = modifier + .padding(all = 16.dp) + .clickable(enabled = appSetting.enabled) { + onValueChange(!(checked ?: false)) + checked = !(checked ?: false) + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(space = 16.dp) + ) { + Box( + modifier = Modifier + .align(Alignment.CenterVertically) + .size(24.dp) + ) { + icon?.invoke() + } + + SettingsComponentColumn( + title = appSetting.title, + description = appSetting.description, + enabled = appSetting.enabled, + modifier = Modifier + .weight(1f) + .padding(end = 16.dp) + ) + + Switch( + checked = checked == true, + onCheckedChange = { + onValueChange(it) + checked = it + }, + enabled = appSetting.enabled, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } +} + +@Composable +@PreviewLightDark +fun SwitchSettingComponentPreview() { + SysctlGuiTheme(dynamicColor = true) { + Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { + SwitchSettingComponent( + appSetting = AppSetting( + key = "key", + title = "Title", + description = "Description", + value = true, + category = "", + type = SettingItemType.Switch + ), + onValueChange = {} + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/TextSettingComponent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/TextSettingComponent.kt new file mode 100644 index 0000000..f8b1071 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/TextSettingComponent.kt @@ -0,0 +1,111 @@ +package com.androidvip.sysctlgui.ui.settings.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.domain.enums.SettingItemType +import com.androidvip.sysctlgui.domain.models.AppSetting + +@Composable +fun TextSettingComponent( + modifier: Modifier = Modifier, + appSetting: AppSetting<*>, + onValueChange: (String) -> Unit, + icon: @Composable (() -> Unit)? = null +) { + var expanded by remember { mutableStateOf(false) } + + Box( + modifier = modifier + .clickable(enabled = appSetting.enabled && appSetting.type == SettingItemType.List) { + expanded = true + } + ) { + Row( + modifier = Modifier.padding(all = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(space = 16.dp) + ) { + + Box( + modifier = Modifier + .align(Alignment.CenterVertically) + .size(24.dp) + ) { + icon?.invoke() + } + + SettingsComponentColumn( + title = appSetting.title, + description = appSetting.description, + enabled = appSetting.enabled, + modifier = Modifier.fillMaxWidth() + ) + } + } + + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + appSetting.values?.forEach { item -> + DropdownMenuItem( + text = { + Text( + text = item as String, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface + ) + }, + onClick = { + onValueChange(item as String) + expanded = false + } + ) + } + } +} + +@Composable +@PreviewLightDark +private fun TextSettingComponentPreview() { + SysctlGuiTheme(dynamicColor = true) { + Box(modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background)) { + TextSettingComponent( + appSetting = AppSetting( + key = "key", + title = "Title", + description = "Description", + value = "sysctl", + category = "", + values = listOf("sysctl", "echo"), + type = SettingItemType.List + ), + onValueChange = {} + ) + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/model/SettingsViewEvent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/model/SettingsViewEvent.kt new file mode 100644 index 0000000..21a61b8 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/model/SettingsViewEvent.kt @@ -0,0 +1,15 @@ +package com.androidvip.sysctlgui.ui.settings.model + +import com.androidvip.sysctlgui.domain.models.AppSetting + +sealed interface SettingsViewEvent { + class SettingValueChanged(val appSetting: AppSetting, val newValue: Any) : SettingsViewEvent +} + +sealed interface SettingsViewEffect { + object RequestNotificationPermission : SettingsViewEffect +} + +data class SettingsViewState( + val settings: List> = emptyList() +) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartActivity.kt index 308f14b..5fe650c 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartActivity.kt @@ -4,49 +4,44 @@ import android.content.Intent import android.os.Build import android.os.Bundle import android.widget.Toast +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.lifecycleScope import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.models.KernelParam import com.androidvip.sysctlgui.data.utils.RootUtils import com.androidvip.sysctlgui.databinding.ActivitySplashBinding -import com.androidvip.sysctlgui.domain.usecase.PerformDatabaseMigrationUseCase +import com.androidvip.sysctlgui.domain.enums.Actions +import com.androidvip.sysctlgui.domain.repository.AppPrefs import com.androidvip.sysctlgui.goAway -import com.androidvip.sysctlgui.helpers.Actions import com.androidvip.sysctlgui.toast -import com.androidvip.sysctlgui.ui.base.BaseAppCompatActivity import com.androidvip.sysctlgui.ui.main.MainActivity -import com.androidvip.sysctlgui.ui.params.edit.EditKernelParamActivity -import com.topjohnwu.superuser.Shell -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.koin.android.ext.android.inject -class StartActivity : BaseAppCompatActivity() { +class StartActivity : AppCompatActivity() { private lateinit var binding: ActivitySplashBinding private val rootUtils: RootUtils by inject() - private val performDatabaseMigrationUseCase: PerformDatabaseMigrationUseCase by inject() - private val dispatcher: CoroutineDispatcher by lazy { Dispatchers.Default } + private val prefs: AppPrefs by inject() override fun onCreate(savedInstanceState: Bundle?) { val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) + enableEdgeToEdge() splashScreen.setKeepOnScreenCondition { true } binding = ActivitySplashBinding.inflate(layoutInflater) setContentView(binding.root) lifecycleScope.launch { + rootUtils.getRootShell() val isRootAccessGiven = checkRootAccess() binding.splashStatusText.setText(R.string.splash_status_checking_busybox) val isBusyBoxAvailable = checkBusyBox() binding.splashStatusText.setText(R.string.splash_status_checking_migration) - checkForDatabaseMigration() if (isRootAccessGiven) { if (!isBusyBoxAvailable) { @@ -67,23 +62,14 @@ class StartActivity : BaseAppCompatActivity() { } } - private suspend fun checkRootAccess() = withContext(dispatcher) { + private suspend fun checkRootAccess(): Boolean { delay(500) - Shell.rootAccess() + return rootUtils.isRootAvailable() } - private suspend fun checkBusyBox() = rootUtils.isBusyboxAvailable().also { + private suspend fun checkBusyBox(): Boolean { delay(500) - } - - private suspend fun checkForDatabaseMigration() { - delay(500) - if (!prefs.migrationCompleted) { - binding.splashStatusText.setText(R.string.splash_status_performing_migration) - - val result = runCatching { performDatabaseMigrationUseCase() } - prefs.migrationCompleted = result.isSuccess - } + return rootUtils.isBusyboxAvailable() } private fun navigate() { @@ -100,7 +86,9 @@ class StartActivity : BaseAppCompatActivity() { } Actions.EditParam.name -> { - Intent(this, EditKernelParamActivity::class.java).apply { + Intent(this, MainActivity::class.java) + // TODO: handle edit param intent + /*Intent(this, EditKernelParamActivity::class.java).apply { putExtra( EditKernelParamActivity.EXTRA_PARAM, intent.extras!!.getParcelable( @@ -114,7 +102,7 @@ class StartActivity : BaseAppCompatActivity() { false ) ) - } + }*/ } else -> { diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartErrorActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartErrorActivity.kt index 6c9b9ea..5602352 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartErrorActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartErrorActivity.kt @@ -4,12 +4,12 @@ import android.content.Intent import android.graphics.drawable.AnimatedVectorDrawable import android.os.Bundle import android.os.Handler +import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.databinding.ActivityStartErrorBinding -import com.androidvip.sysctlgui.ui.base.BaseAppCompatActivity -class StartErrorActivity : BaseAppCompatActivity() { +class StartErrorActivity : AppCompatActivity() { private lateinit var binding: ActivityStartErrorBinding override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/tasker/TaskerPluginActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/tasker/TaskerPluginActivity.kt index 65b0e85..c3b59d5 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/tasker/TaskerPluginActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/tasker/TaskerPluginActivity.kt @@ -1,6 +1,5 @@ package com.androidvip.sysctlgui.ui.tasker -import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.MenuItem @@ -8,11 +7,10 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf import com.androidvip.sysctlgui.databinding.ActivityTaskerPluginBinding import com.androidvip.sysctlgui.receivers.TaskerReceiver -import com.androidvip.sysctlgui.ui.base.BaseAppCompatActivity import kotlin.contracts.ExperimentalContracts @ExperimentalContracts -class TaskerPluginActivity : BaseAppCompatActivity() { +class TaskerPluginActivity : AppCompatActivity() { private lateinit var binding: ActivityTaskerPluginBinding override fun onCreate(savedInstanceState: Bundle?) { @@ -32,7 +30,7 @@ class TaskerPluginActivity : BaseAppCompatActivity() { putExtra(TaskerReceiver.EXTRA_BUNDLE, resultBundle) } - setResult(Activity.RESULT_OK, resultIntent) + setResult(RESULT_OK, resultIntent) finish() } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt new file mode 100644 index 0000000..8043c5d --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt @@ -0,0 +1,262 @@ +package com.androidvip.sysctlgui.ui.user + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SwipeToDismissBox +import androidx.compose.material3.SwipeToDismissBoxValue +import androidx.compose.material3.Text +import androidx.compose.material3.rememberSwipeToDismissBoxState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.models.UiKernelParam +import com.androidvip.sysctlgui.ui.main.MainViewEffect +import com.androidvip.sysctlgui.ui.main.MainViewEvent +import com.androidvip.sysctlgui.ui.main.MainViewModel +import com.androidvip.sysctlgui.ui.main.MainViewState +import com.androidvip.sysctlgui.ui.params.browse.ParamFileRow +import kotlinx.coroutines.delay +import org.koin.compose.viewmodel.koinViewModel +import kotlin.time.Duration.Companion.milliseconds + +const val ANIMATION_DURATION = 300 + +@Composable +fun UserParamsScreen( + mainViewModel: MainViewModel = koinViewModel(), + viewModel: UserParamsViewModel = koinViewModel(), + filterPredicate: (UiKernelParam) -> Boolean = { true }, + onParamSelected: (KernelParam) -> Unit, +) { + val context = LocalContext.current + val state by viewModel.uiState.collectAsStateWithLifecycle() + + LaunchedEffect(Unit) { + viewModel.onEvent(UserParamsViewEvent.ScreenLoaded(filterPredicate)) + + mainViewModel.onEvent( + MainViewEvent.OnSateChangeRequested( + MainViewState( + showTopBar = true, + showNavBar = true, + showBackButton = false, + showSearchAction = true + ) + ) + ) + + mainViewModel.effect.collect { effect -> + if (effect is MainViewEffect.ActUponSckbarActionPerformed) { + viewModel.onEvent(UserParamsViewEvent.ParamRestoreRequested) + } + } + } + + LaunchedEffect(viewModel.effect) { + viewModel.effect.collect { effect -> + when (effect) { + is UserParamsViewEffect.ShowParamDetails -> { + onParamSelected(effect.param) + } + + is UserParamsViewEffect.ShowUndoSnackBar -> { + mainViewModel.onEvent( + MainViewEvent.ShowSnackbarRequested( + message = context.getString(R.string.favorite_param_deleted_format, effect.param.name), + actionLabel = context.getString(R.string.undo) + ) + ) + } + } + } + } + + FavoritesScreenContent( + favoriteParams = state.userParams, + loading = state.loading, + onParamClicked = { + viewModel.onEvent(UserParamsViewEvent.ParamClicked(it)) + }, + onParamDeleteRequested = { + viewModel.onEvent(UserParamsViewEvent.ParamDeleteRequested(it)) + } + ) +} + +@Composable +private fun FavoritesScreenContent( + favoriteParams: List, + loading: Boolean, + onParamClicked: (UiKernelParam) -> Unit, + onParamDeleteRequested: (UiKernelParam) -> Unit +) { + val listState = rememberLazyListState() + + AnimatedVisibility(visible = loading, enter = fadeIn(), exit = fadeOut()) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } + + if (favoriteParams.isEmpty() && !loading) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(32.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = painterResource(R.drawable.ic_heart_broken), + contentDescription = "Empty", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(128.dp) + ) + // TODO: Empty state image + Text( + text = "No favorites added", + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + } + return + } + + LazyColumn( + state = listState, + modifier = Modifier.fillMaxSize() + ) { + items( + count = favoriteParams.size, + key = { index -> favoriteParams[index].name } + ) { index -> + val param = favoriteParams[index] + var showParam by remember { mutableStateOf(true) } + val dismissState = rememberSwipeToDismissBoxState() + + LaunchedEffect(showParam, param) { + if (!showParam) { + delay(ANIMATION_DURATION.milliseconds) + onParamDeleteRequested(param) + } + } + + AnimatedVisibility( + visible = showParam, + exit = shrinkVertically( + animationSpec = tween(durationMillis = ANIMATION_DURATION), + shrinkTowards = Alignment.Top + ) + fadeOut(animationSpec = tween(durationMillis = ANIMATION_DURATION)) + ) { + val swipeBackgroundColor = MaterialTheme.colorScheme.errorContainer + val swipeContentColor = MaterialTheme.colorScheme.onErrorContainer + + SwipeToDismissBox( + state = dismissState, + enableDismissFromStartToEnd = false, + enableDismissFromEndToStart = true, + onDismiss = { showParam = false }, + backgroundContent = { + val color = when (dismissState.dismissDirection) { + SwipeToDismissBoxValue.EndToStart -> swipeBackgroundColor + else -> Color.Transparent + } + Box( + modifier = Modifier + .fillMaxSize() + .background(color) + .padding(horizontal = 16.dp), + contentAlignment = Alignment.CenterEnd + ) { + Icon( + painter = painterResource(R.drawable.ic_delete_sweep), + contentDescription = "Delete", + tint = swipeContentColor + ) + } + } + ) { + ParamFileRow( + modifier = Modifier.background(MaterialTheme.colorScheme.background), + param = param, + showFavoriteIcon = false, + onParamClicked = onParamClicked + ) + } + } + + if (index < favoriteParams.lastIndex && showParam) { + HorizontalDivider() + } + } + } +} + +@Composable +@Preview +private fun FavoriteScreenContentPreview() { + val params = listOf( + UiKernelParam( + name = "vm.swappiness", + path = "/proc/sys/vm/swappiness", + value = "100", + isFavorite = true + ), + UiKernelParam( + name = "vm.overcommit_memory", + path = "/proc/sys/vm/overcommit_memory", + value = "1", + isFavorite = true + ), + UiKernelParam( + name = "vm.overcommit_ratio", + path = "/proc/sys/vm/overcommit_ratio", + value = "2", + isFavorite = true + ) + ) + SysctlGuiTheme(dynamicColor = true) { + Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { + FavoritesScreenContent( + favoriteParams = params, + loading = false, + onParamClicked = {}, + onParamDeleteRequested = {} + ) + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsViewModel.kt new file mode 100644 index 0000000..cff23a5 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsViewModel.kt @@ -0,0 +1,69 @@ +package com.androidvip.sysctlgui.ui.user + +import androidx.lifecycle.viewModelScope +import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase +import com.androidvip.sysctlgui.domain.usecase.RemoveUserParamUseCase +import com.androidvip.sysctlgui.domain.usecase.UpsertUserParamUseCase +import com.androidvip.sysctlgui.helpers.UiKernelParamMapper +import com.androidvip.sysctlgui.models.UiKernelParam +import com.androidvip.sysctlgui.utils.BaseViewModel +import kotlinx.coroutines.launch + +class UserParamsViewModel( + private val getUserParams: GetUserParamsUseCase, + private val removeParam: RemoveUserParamUseCase, + private val upsertParam: UpsertUserParamUseCase, +) : BaseViewModel() { + override fun createInitialState() = UserParamsViewState() + + private var mostRecentlyRemovedParam: UiKernelParam? = null + + override fun onEvent(event: UserParamsViewEvent) { + when (event) { + is UserParamsViewEvent.ScreenLoaded -> loadParams(event.filterPredicate) + + is UserParamsViewEvent.ParamClicked -> setEffect { + UserParamsViewEffect.ShowParamDetails(event.param) + } + + is UserParamsViewEvent.ParamDeleteRequested -> removeParam(event.param) + + is UserParamsViewEvent.ParamRestoreRequested -> { + mostRecentlyRemovedParam?.let { reAddParam(it) } + } + } + } + + private fun loadParams(predicate: (UiKernelParam) -> Boolean) { + viewModelScope.launch { + val params = getUserParams() + .map(UiKernelParamMapper::map) + .filter(predicate) + setState { copy(userParams = params) } + } + } + + private fun removeParam(param: UiKernelParam) { + viewModelScope.launch { + runCatching { + removeParam.invoke(param) + }.onSuccess { + setState { copy(userParams = userParams - param) } + setEffect { UserParamsViewEffect.ShowUndoSnackBar(param) } + mostRecentlyRemovedParam = param + } + } + } + + private fun reAddParam(param: UiKernelParam) { + viewModelScope.launch { + runCatching { + val newId = upsertParam(param) + require(newId > 0) + }.onSuccess { + setState { copy(userParams = userParams + param) } + mostRecentlyRemovedParam = null + } + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsViewState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsViewState.kt new file mode 100644 index 0000000..c460907 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsViewState.kt @@ -0,0 +1,20 @@ +package com.androidvip.sysctlgui.ui.user + +import com.androidvip.sysctlgui.models.UiKernelParam + +data class UserParamsViewState( + val userParams: List = emptyList(), + val loading: Boolean = true +) + +sealed interface UserParamsViewEvent { + data class ScreenLoaded(val filterPredicate: (UiKernelParam) -> Boolean) : UserParamsViewEvent + data class ParamClicked(val param: UiKernelParam) : UserParamsViewEvent + data class ParamDeleteRequested(val param: UiKernelParam) : UserParamsViewEvent + data object ParamRestoreRequested : UserParamsViewEvent +} + +sealed interface UserParamsViewEffect { + data class ShowParamDetails(val param: UiKernelParam) : UserParamsViewEffect + data class ShowUndoSnackBar(val param: UiKernelParam) : UserParamsViewEffect +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/utils/DataBindingUtils.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/utils/DataBindingUtils.kt deleted file mode 100644 index 289ec2a..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/utils/DataBindingUtils.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.androidvip.sysctlgui.utils - -import androidx.annotation.DrawableRes -import androidx.appcompat.widget.AppCompatImageView -import androidx.databinding.BindingAdapter - -@BindingAdapter("binding:srcCompatRes") -fun AppCompatImageView.setImageResourceCompat(@DrawableRes res: Int) { - setImageResource(res) -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/utils/KernelParamUtils.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/utils/KernelParamUtils.kt deleted file mode 100644 index ef59fdb..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/utils/KernelParamUtils.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.androidvip.sysctlgui.utils - -import android.content.Context -import android.net.Uri -import com.androidvip.sysctlgui.domain.models.DomainKernelParam -import com.google.gson.Gson -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.io.FileOutputStream - -// TODO: move to repository -object KernelParamUtils { - - suspend fun writeParamsToUri( - context: Context, - params: List, - uri: Uri - ) = withContext(Dispatchers.IO) { - return@withContext runCatching { - context.contentResolver.openFileDescriptor(uri, "w")?.use { - FileOutputStream(it.fileDescriptor).use { fileOutputStream -> - fileOutputStream.write(Gson().toJson(params).toByteArray()) - } - } - true - }.getOrElse { - it.printStackTrace() - false - } - } - -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/utils/ThemeExt.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/utils/ThemeExt.kt deleted file mode 100644 index f05ffeb..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/utils/ThemeExt.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.androidvip.sysctlgui.utils - -import android.app.Activity -import androidx.compose.runtime.Composable -import androidx.fragment.app.Fragment -import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme -import com.androidvip.sysctlgui.domain.repository.AppPrefs -import org.koin.android.ext.android.get - -@Composable -fun Activity.ComposeTheme(content: @Composable () -> Unit) { - val prefs: AppPrefs = get() - SysctlGuiTheme( - forceDark = prefs.forceDark, - dynamicColor = prefs.dynamicColors, - content = content - ) -} - -@Composable -fun Fragment.ComposeTheme(content: @Composable () -> Unit) { - val prefs: AppPrefs = get() - SysctlGuiTheme( - forceDark = prefs.forceDark, - dynamicColor = prefs.dynamicColors, - content = content - ) -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoriteWidgetParamUpdater.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoriteWidgetParamUpdater.kt index 82a3ac6..c2a67f0 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoriteWidgetParamUpdater.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoriteWidgetParamUpdater.kt @@ -5,7 +5,6 @@ import android.appwidget.AppWidgetManager import android.content.ComponentName import android.content.Context import android.content.Intent -import android.os.Build import com.androidvip.sysctlgui.data.repository.ParamsRepositoryImpl class FavoriteWidgetParamUpdater(private val context: Context) : @@ -19,11 +18,7 @@ class FavoriteWidgetParamUpdater(private val context: Context) : if (idArray.isEmpty()) return - val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - } else { - PendingIntent.FLAG_UPDATE_CURRENT - } + val flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT idArray.forEach { intentUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(it)) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidget.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidget.kt index a02911d..afec2c9 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidget.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidget.kt @@ -6,19 +6,19 @@ import android.appwidget.AppWidgetProvider import android.content.Context import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_NEW_TASK -import android.net.Uri -import android.os.Build import android.widget.RemoteViews +import androidx.core.net.toUri import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.mapper.DomainParamMapper +import com.androidvip.sysctlgui.domain.enums.Actions import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase -import com.androidvip.sysctlgui.helpers.Actions -import com.androidvip.sysctlgui.ui.params.edit.EditKernelParamActivity +import com.androidvip.sysctlgui.helpers.UiKernelParamMapper +import com.androidvip.sysctlgui.models.UiKernelParam import com.androidvip.sysctlgui.ui.start.StartActivity import com.androidvip.sysctlgui.widgets.FavoritesWidget.Companion.EDIT_PARAM_EXTRA import kotlinx.coroutines.runBlocking import org.koin.core.component.KoinComponent import org.koin.core.component.inject +import kotlin.collections.map class FavoritesWidget : AppWidgetProvider(), KoinComponent { private val getUserParamsUseCase: GetUserParamsUseCase by inject() @@ -41,11 +41,10 @@ class FavoritesWidget : AppWidgetProvider(), KoinComponent { } runBlocking { - val params = getUserParamsUseCase().filter { - it.favorite - }.map { - DomainParamMapper.map(it) - }.toMutableList() + val params = getUserParamsUseCase() + .filter { it.isFavorite } + .map(UiKernelParamMapper::map) + .toMutableList() if (params.isEmpty()) return@runBlocking @@ -53,8 +52,9 @@ class FavoritesWidget : AppWidgetProvider(), KoinComponent { Intent(context, StartActivity::class.java).apply { flags = FLAG_ACTIVITY_NEW_TASK action = Actions.EditParam.name - putExtra(EditKernelParamActivity.EXTRA_PARAM, param) - putExtra(EditKernelParamActivity.EXTRA_EDIT_SAVED_PARAM, true) + // TODO + /*putExtra(EditKernelParamActivity.EXTRA_PARAM, param) + putExtra(EditKernelParamActivity.EXTRA_EDIT_SAVED_PARAM, true)*/ context.startActivity(this) } } @@ -79,7 +79,7 @@ internal fun updateAppWidget( ) { val intent = Intent(context, FavoritesWidgetService::class.java).apply { putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) - data = Uri.parse(this.toUri(Intent.URI_INTENT_SCHEME)) + data = toUri(Intent.URI_INTENT_SCHEME).toUri() } val views = RemoteViews( @@ -96,13 +96,9 @@ internal fun updateAppWidget( ).run { action = EDIT_PARAM_EXTRA putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) - data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME)) + data = toUri(Intent.URI_INTENT_SCHEME).toUri() - val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - } else { - PendingIntent.FLAG_UPDATE_CURRENT - } + val flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.getBroadcast(context, 0, this, flags) } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidgetService.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidgetService.kt index 9b7b82d..2876d99 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidgetService.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidgetService.kt @@ -6,8 +6,7 @@ import android.content.Intent import android.widget.RemoteViews import android.widget.RemoteViewsService import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.data.mapper.DomainParamMapper -import com.androidvip.sysctlgui.data.models.KernelParam +import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase import com.androidvip.sysctlgui.widgets.FavoritesWidget.Companion.EXTRA_ITEM import kotlinx.coroutines.runBlocking @@ -36,9 +35,7 @@ class FavoritesRemoteViewsFactory( override fun onCreate() { runBlocking { params = getUserParamsUseCase().filter { - it.favorite - }.map { - DomainParamMapper.map(it) + it.isFavorite }.toMutableList() } } @@ -51,11 +48,7 @@ class FavoritesRemoteViewsFactory( override fun onDataSetChanged() { runBlocking { - params = getUserParamsUseCase().filter { - it.favorite - }.map { - DomainParamMapper.map(it) - }.toMutableList() + params = getUserParamsUseCase().filter { it.isFavorite }.toMutableList() } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/work/StartUpWorker.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/work/StartUpWorker.kt index 708f1d5..05e18e0 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/work/StartUpWorker.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/work/StartUpWorker.kt @@ -17,9 +17,8 @@ import androidx.work.WorkerParameters import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.data.utils.RootUtils import com.androidvip.sysctlgui.domain.repository.AppPrefs -import com.androidvip.sysctlgui.domain.usecase.ApplyParamsUseCase +import com.androidvip.sysctlgui.domain.usecase.ApplyParamUseCase import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase -import com.topjohnwu.superuser.Shell import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay @@ -36,8 +35,8 @@ class StartUpWorker( private val workerContext = Dispatchers.Default private val appPrefs: AppPrefs by inject() private val rootUtils: RootUtils by inject() - private val getUserParamsUseCase: GetUserParamsUseCase by inject() - private val applyParamsUseCase: ApplyParamsUseCase by inject() + private val getUserParams: GetUserParamsUseCase by inject() + private val applyParam: ApplyParamUseCase by inject() private val notificationManager: NotificationManagerCompat get() = NotificationManagerCompat.from(context) @@ -63,16 +62,16 @@ class StartUpWorker( } private suspend fun applyConfig(builder: NotificationCompat.Builder) { - getUserParamsUseCase().forEach { - builder.setContentText(it.toString()) + getUserParams().forEach { param -> + builder.setContentText(param.toString()) notifyIfPossible(builder) delay(250L) - applyParamsUseCase(it) + applyParam(param) } } - private suspend fun checkRequirements() = withContext(workerContext) { - appPrefs.runOnStartUp && Shell.rootAccess() + private suspend fun checkRequirements(): Boolean { + return appPrefs.runOnStartUp && rootUtils.isRootAvailable() } private suspend inline fun showNotificationAndThen( diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/work/TaskerWorker.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/work/TaskerWorker.kt index 410750e..c081e78 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/work/TaskerWorker.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/work/TaskerWorker.kt @@ -10,7 +10,7 @@ import androidx.work.WorkerParameters import androidx.work.workDataOf import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.domain.repository.AppPrefs -import com.androidvip.sysctlgui.domain.usecase.ApplyParamsUseCase +import com.androidvip.sysctlgui.domain.usecase.ApplyParamUseCase import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase import com.androidvip.sysctlgui.receivers.TaskerReceiver import com.androidvip.sysctlgui.toast @@ -30,8 +30,8 @@ class TaskerWorker( private val mainContext = Dispatchers.Main + SupervisorJob() private val workerContext = Dispatchers.IO private val appPrefs: AppPrefs by inject() - private val getUserParamsUseCase: GetUserParamsUseCase by inject() - private val applyParamsUseCase: ApplyParamsUseCase by inject() + private val getUserParams: GetUserParamsUseCase by inject() + private val applyParam: ApplyParamUseCase by inject() override suspend fun doWork(): Result { withContext(workerContext) { @@ -54,16 +54,16 @@ class TaskerWorker( } private suspend fun applyParams(listNumber: Int) { - val params = getUserParamsUseCase() + val params = getUserParams() when (listNumber) { Consts.LIST_NUMBER_PRIMARY_TASKER, - Consts.LIST_NUMBER_SECONDARY_TASKER -> params.filter { it.taskerParam } - Consts.LIST_NUMBER_FAVORITES -> params.filter { it.favorite } + Consts.LIST_NUMBER_SECONDARY_TASKER -> params.filter { it.isTaskerParam } + Consts.LIST_NUMBER_FAVORITES -> params.filter { it.isFavorite } Consts.LIST_NUMBER_APPLY_ON_BOOT -> params else -> emptyList() }.forEach { - applyParamsUseCase(it) + applyParam(it) } } diff --git a/app/src/main/res/drawable-night/ic_launcher_background.xml b/app/src/main/res/drawable-night/ic_launcher_background.xml deleted file mode 100644 index 5314cbc..0000000 --- a/app/src/main/res/drawable-night/ic_launcher_background.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/circle_file.xml b/app/src/main/res/drawable/circle_file.xml deleted file mode 100644 index 5e09b4b..0000000 --- a/app/src/main/res/drawable/circle_file.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/circle_folder.xml b/app/src/main/res/drawable/circle_folder.xml deleted file mode 100644 index 6c8c6fc..0000000 --- a/app/src/main/res/drawable/circle_folder.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/fast_scroll_thumb.xml b/app/src/main/res/drawable/fast_scroll_thumb.xml deleted file mode 100644 index 5735284..0000000 --- a/app/src/main/res/drawable/fast_scroll_thumb.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/fast_scroll_track.xml b/app/src/main/res/drawable/fast_scroll_track.xml deleted file mode 100644 index c6471b0..0000000 --- a/app/src/main/res/drawable/fast_scroll_track.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_action_tasker.xml b/app/src/main/res/drawable/ic_action_tasker.xml deleted file mode 100644 index 59e6a77..0000000 --- a/app/src/main/res/drawable/ic_action_tasker.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_arrow_upward.xml b/app/src/main/res/drawable/ic_arrow_upward.xml new file mode 100644 index 0000000..8eff018 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_upward.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml deleted file mode 100644 index 8d6dbeb..0000000 --- a/app/src/main/res/drawable/ic_close.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_config.xml b/app/src/main/res/drawable/ic_config.xml deleted file mode 100644 index a69f689..0000000 --- a/app/src/main/res/drawable/ic_config.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_delete_sweep.xml b/app/src/main/res/drawable/ic_delete_sweep.xml index 940a568..4ff2112 100644 --- a/app/src/main/res/drawable/ic_delete_sweep.xml +++ b/app/src/main/res/drawable/ic_delete_sweep.xml @@ -1,8 +1,12 @@ - - \ No newline at end of file + + + + diff --git a/app/src/main/res/drawable/ic_documentation.xml b/app/src/main/res/drawable/ic_documentation.xml index d9e0a7e..428b620 100644 --- a/app/src/main/res/drawable/ic_documentation.xml +++ b/app/src/main/res/drawable/ic_documentation.xml @@ -1,7 +1,13 @@ - - \ No newline at end of file + + + + diff --git a/app/src/main/res/drawable/ic_edit_outline.xml b/app/src/main/res/drawable/ic_edit_outline.xml deleted file mode 100644 index 2271737..0000000 --- a/app/src/main/res/drawable/ic_edit_outline.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_export.xml b/app/src/main/res/drawable/ic_export.xml new file mode 100644 index 0000000..248a716 --- /dev/null +++ b/app/src/main/res/drawable/ic_export.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_favorite.xml b/app/src/main/res/drawable/ic_favorite.xml new file mode 100644 index 0000000..a55d082 --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_favorite_outlined.xml b/app/src/main/res/drawable/ic_favorite_outlined.xml new file mode 100644 index 0000000..75654f6 --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite_outlined.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_file.xml b/app/src/main/res/drawable/ic_file.xml new file mode 100644 index 0000000..1655262 --- /dev/null +++ b/app/src/main/res/drawable/ic_file.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_file_outline.xml b/app/src/main/res/drawable/ic_file_outline.xml deleted file mode 100644 index f925ee2..0000000 --- a/app/src/main/res/drawable/ic_file_outline.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_folder.xml b/app/src/main/res/drawable/ic_folder.xml new file mode 100644 index 0000000..769af8f --- /dev/null +++ b/app/src/main/res/drawable/ic_folder.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_folder_outline.xml b/app/src/main/res/drawable/ic_folder_outline.xml deleted file mode 100644 index 512eb78..0000000 --- a/app/src/main/res/drawable/ic_folder_outline.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_heart_broken.xml b/app/src/main/res/drawable/ic_heart_broken.xml new file mode 100644 index 0000000..8b4943e --- /dev/null +++ b/app/src/main/res/drawable/ic_heart_broken.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_history.xml b/app/src/main/res/drawable/ic_history.xml new file mode 100644 index 0000000..041a166 --- /dev/null +++ b/app/src/main/res/drawable/ic_history.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_import.xml b/app/src/main/res/drawable/ic_import.xml new file mode 100644 index 0000000..4ad7f44 --- /dev/null +++ b/app/src/main/res/drawable/ic_import.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_info_outline.xml b/app/src/main/res/drawable/ic_info_outline.xml deleted file mode 100644 index 5aa45fb..0000000 --- a/app/src/main/res/drawable/ic_info_outline.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 2c34399..5314cbc 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -6,5 +6,5 @@ android:width="108dp" android:height="108dp" /> - + diff --git a/app/src/main/res/drawable/ic_more_vert.xml b/app/src/main/res/drawable/ic_more_vert.xml deleted file mode 100644 index 0f7ce68..0000000 --- a/app/src/main/res/drawable/ic_more_vert.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_name.xml b/app/src/main/res/drawable/ic_name.xml deleted file mode 100644 index 91e26d4..0000000 --- a/app/src/main/res/drawable/ic_name.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_open_in_browser.xml b/app/src/main/res/drawable/ic_open_in_browser.xml new file mode 100644 index 0000000..5a2c383 --- /dev/null +++ b/app/src/main/res/drawable/ic_open_in_browser.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml deleted file mode 100644 index 22f41a3..0000000 --- a/app/src/main/res/drawable/ic_search.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_settings_outline.xml b/app/src/main/res/drawable/ic_settings_outline.xml deleted file mode 100644 index 711fde8..0000000 --- a/app/src/main/res/drawable/ic_settings_outline.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_tasker.xml b/app/src/main/res/drawable/ic_tasker.xml new file mode 100644 index 0000000..905fb28 --- /dev/null +++ b/app/src/main/res/drawable/ic_tasker.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_tasker_outlined.xml b/app/src/main/res/drawable/ic_tasker_outlined.xml new file mode 100644 index 0000000..165e5d7 --- /dev/null +++ b/app/src/main/res/drawable/ic_tasker_outlined.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/layout-land/activity_main2.xml b/app/src/main/res/layout-land/activity_main2.xml deleted file mode 100644 index 8a6a6bd..0000000 --- a/app/src/main/res/layout-land/activity_main2.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_main2.xml b/app/src/main/res/layout/activity_main2.xml deleted file mode 100644 index 7d94f7e..0000000 --- a/app/src/main/res/layout/activity_main2.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml index d270c1f..ba0c7bc 100644 --- a/app/src/main/res/layout/activity_splash.xml +++ b/app/src/main/res/layout/activity_splash.xml @@ -25,7 +25,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/d16" - android:fontFamily="sans-serif-medium" android:gravity="center_horizontal" android:text="@string/splash_status_checking_root" android:textAppearance="?textAppearanceHeadline5" /> diff --git a/app/src/main/res/layout/activity_tasker_plugin.xml b/app/src/main/res/layout/activity_tasker_plugin.xml index 1981a4a..5a11583 100644 --- a/app/src/main/res/layout/activity_tasker_plugin.xml +++ b/app/src/main/res/layout/activity_tasker_plugin.xml @@ -13,8 +13,10 @@ android:layout_marginEnd="@dimen/d16" android:layout_marginBottom="@dimen/d16" android:text="@string/done" - app:backgroundTint="?colorSecondary" - app:icon="@drawable/ic_check" /> + android:textColor="?colorOnTertiary" + app:backgroundTint="?colorTertiary" + app:icon="@drawable/ic_check" + app:iconTint="?colorOnPrimary" /> + android:textColor="?colorPrimary" + android:textStyle="bold" /> - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/list_item_settings.xml b/app/src/main/res/layout/list_item_settings.xml deleted file mode 100644 index fa3909c..0000000 --- a/app/src/main/res/layout/list_item_settings.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/settings_activity.xml b/app/src/main/res/layout/settings_activity.xml deleted file mode 100644 index 01ee430..0000000 --- a/app/src/main/res/layout/settings_activity.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/menu/menu_browse_params.xml b/app/src/main/res/menu/menu_browse_params.xml deleted file mode 100644 index e78ed41..0000000 --- a/app/src/main/res/menu/menu_browse_params.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml deleted file mode 100644 index db1f6b9..0000000 --- a/app/src/main/res/menu/menu_main.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - diff --git a/app/src/main/res/menu/menu_main_search.xml b/app/src/main/res/menu/menu_main_search.xml deleted file mode 100644 index 46a8dac..0000000 --- a/app/src/main/res/menu/menu_main_search.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/menu/menu_search.xml b/app/src/main/res/menu/menu_search.xml deleted file mode 100644 index 392e261..0000000 --- a/app/src/main/res/menu/menu_search.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/nav_main.xml b/app/src/main/res/menu/nav_main.xml deleted file mode 100644 index 58fdaf5..0000000 --- a/app/src/main/res/menu/nav_main.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/menu/popup_manage_params.xml b/app/src/main/res/menu/popup_manage_params.xml deleted file mode 100644 index 6d3116c..0000000 --- a/app/src/main/res/menu/popup_manage_params.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml deleted file mode 100644 index 5647a7f..0000000 --- a/app/src/main/res/navigation/main_navigation.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values-land/integers.xml b/app/src/main/res/values-land/integers.xml deleted file mode 100644 index 169ae66..0000000 --- a/app/src/main/res/values-land/integers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - 2 - diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index e3152f0..bf64536 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -96,7 +96,7 @@ Restaurar a sua cópia de segurança anterior Opções de exportação Parâmetros exportados com sucesso - Para na exportação de parâmetros: nenhum parâmetro encontrado + Falha: nenhum parâmetro encontrado A exportação de parâmetros falhou devido a um erro do armazenamento Falha ao exportar parâmetros Importar, exportar ou fazer backup dos parâmetros do kernel diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 84ea67b..02d9158 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1,5 +1,4 @@ - Sysctl GUI Kök erişimi denetleniyor Busybox kontrol ediliyor Eski veritabanı şeması kontrol ediliyor diff --git a/app/src/main/res/values-v14/dimens.xml b/app/src/main/res/values-v14/dimens.xml deleted file mode 100644 index 212af5f..0000000 --- a/app/src/main/res/values-v14/dimens.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - 0dp - - diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml deleted file mode 100644 index 62657f3..0000000 --- a/app/src/main/res/values/integers.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - 1 - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b334345..b22598b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -98,7 +98,7 @@ Backup all current runtime parameters. Please note that not all of these can be reapplied back once restored. Failed to export parameters Parameter export failed due to an IO error - Parameter export failed: no parameter to export + Failed: no parameter found Export options Parameters successfully exported Import, export or back up kernel parameters @@ -119,4 +119,11 @@ Use dynamic colors (Monet theme) when available Force dark theme Force dark theme when available + Favorites + Presets + File creation cancelled or failed + File picking cancelled or failed + There was an error while opening the file + There was an error while processing the file\n + Param deleted: %s diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml deleted file mode 100644 index 6238da5..0000000 --- a/app/src/main/res/xml/preferences.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/buildSrc/src/main/kotlin/AndroidX.kt b/buildSrc/src/main/kotlin/AndroidX.kt deleted file mode 100644 index 52d512d..0000000 --- a/buildSrc/src/main/kotlin/AndroidX.kt +++ /dev/null @@ -1,26 +0,0 @@ -object AndroidX { - const val activity = "androidx.activity:activity-ktx:1.7.2" - const val appCompat = "androidx.appcompat:appcompat:1.6.1" - const val constraintLayout = "androidx.constraintlayout:constraintlayout:2.1.4" - const val core = "androidx.core:core-ktx:1.10.1" - const val splashScreen = "androidx.core:core-splashscreen:1.0.1" - - private const val lifecycleVersion = "2.6.1" - const val lifecycleLiveData = "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" - const val lifecycleViewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" - - const val preference = "androidx.preference:preference-ktx:1.2.0" - const val swipeRefreshLayout = "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" - - private const val navigationVersion = "2.6.0" - const val navigationFragment = "androidx.navigation:navigation-fragment-ktx:$navigationVersion" - const val navigationUi = "androidx.navigation:navigation-ui-ktx:$navigationVersion" - - private const val roomVersion = "2.5.2" - const val room = "androidx.room:room-ktx:$roomVersion" - const val roomRuntime = "androidx.room:room-runtime:$roomVersion" - const val roomCompiler = "androidx.room:room-compiler:$roomVersion" - - private const val workManagerVersion = "2.9.0" - const val workManager = "androidx.work:work-runtime-ktx:$workManagerVersion" -} diff --git a/buildSrc/src/main/kotlin/BuildPlugins.kt b/buildSrc/src/main/kotlin/BuildPlugins.kt deleted file mode 100644 index 168cf36..0000000 --- a/buildSrc/src/main/kotlin/BuildPlugins.kt +++ /dev/null @@ -1,7 +0,0 @@ -object BuildPlugins { - private const val agpVersion = "7.4.2" - const val gradle = "com.android.tools.build:gradle:$agpVersion" - - private const val kotlinVersion = "1.9.24" - const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" -} diff --git a/buildSrc/src/main/kotlin/Compose.kt b/buildSrc/src/main/kotlin/Compose.kt deleted file mode 100644 index 6ee5046..0000000 --- a/buildSrc/src/main/kotlin/Compose.kt +++ /dev/null @@ -1,8 +0,0 @@ -object Compose { - const val BoM = "androidx.compose:compose-bom:2024.06.00" - const val kotlinCompilerExtensionVersion = "1.5.14" - const val material3 = "androidx.compose.material3:material3" - const val material = "androidx.compose.material:material" - const val uiTooling = "androidx.compose.ui:ui-tooling" - const val activity = "androidx.activity:activity-compose:1.6.1" -} diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt deleted file mode 100644 index a6e42e6..0000000 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ /dev/null @@ -1,12 +0,0 @@ - -object Dependencies { - private const val koinVersion = "3.4.2" - const val koinAndroid = "io.insert-koin:koin-android:$koinVersion" - const val koinCore = "io.insert-koin:koin-core:$koinVersion" - - const val coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" - const val tapTargetView = "com.getkeepsafe.taptargetview:taptargetview:1.13.3" - const val libSuCore = "com.github.topjohnwu.libsu:core:2.5.1" - const val libSuIo = "com.github.topjohnwu.libsu:io:2.5.1" - const val liveEvent = "com.github.hadilq:live-event:1.3.0" -} diff --git a/buildSrc/src/main/kotlin/Google.kt b/buildSrc/src/main/kotlin/Google.kt deleted file mode 100644 index 6ab7f05..0000000 --- a/buildSrc/src/main/kotlin/Google.kt +++ /dev/null @@ -1,4 +0,0 @@ -object Google { - const val material = "com.google.android.material:material:1.9.0" - const val gson = "com.google.code.gson:gson:2.8.9" -} diff --git a/buildSrc/src/main/kotlin/Modules.kt b/buildSrc/src/main/kotlin/Modules.kt deleted file mode 100644 index d81ddd5..0000000 --- a/buildSrc/src/main/kotlin/Modules.kt +++ /dev/null @@ -1,7 +0,0 @@ -object Modules { - const val main = ":app" - const val domain = ":domain" - const val data = ":data" - const val utils = ":common:utils" - const val design = ":common:design" -} diff --git a/common/design/build.gradle.kts b/common/design/build.gradle.kts index d9ddb38..7208696 100644 --- a/common/design/build.gradle.kts +++ b/common/design/build.gradle.kts @@ -51,11 +51,7 @@ dependencies { api(libs.androidx.material.icons.core) api(libs.androidx.window) - api(AndroidX.constraintLayout) - api(AndroidX.swipeRefreshLayout) - api(Compose.material) - implementation(AndroidX.splashScreen) - implementation(Google.material) + api(libs.material) androidTestApi(platform(libs.androidx.compose.bom)) debugApi(libs.androidx.ui.tooling) diff --git a/common/design/src/main/java/com/androidvip/sysctlgui/design/BaseBottomSheetFragment.kt b/common/design/src/main/java/com/androidvip/sysctlgui/design/BaseBottomSheetFragment.kt deleted file mode 100644 index c237184..0000000 --- a/common/design/src/main/java/com/androidvip/sysctlgui/design/BaseBottomSheetFragment.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.androidvip.sysctlgui.design - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.ViewCompat -import androidx.viewbinding.ViewBinding -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.google.android.material.shape.MaterialShapeDrawable -import com.google.android.material.shape.ShapeAppearanceModel - -abstract class BaseBottomSheetFragment : BottomSheetDialogFragment() { - - lateinit var binding: Binding - - abstract fun setViewBinding(inflater: LayoutInflater, container: ViewGroup?): Binding - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - binding = this.setViewBinding(inflater, container) - return binding.root - } - - override fun onStart() { - super.onStart() - val bottomSheetBehavior = BottomSheetBehavior.from(view?.parent as? View ?: return) - bottomSheetBehavior.addBottomSheetCallback( - object : BottomSheetBehavior.BottomSheetCallback() { - override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit - - override fun onStateChanged(bottomSheet: View, newState: Int) { - when (newState) { - BottomSheetBehavior.STATE_EXPANDED -> { - val shape = createMaterialShapeDrawable(bottomSheet) - ViewCompat.setBackground(bottomSheet, shape) - } - BottomSheetBehavior.STATE_HIDDEN -> dismiss() - else -> Unit - } - } - } - ) - } - - private fun createMaterialShapeDrawable(bottomSheet: View): MaterialShapeDrawable { - val shapeAppearanceModel = ShapeAppearanceModel.builder( - context, - 0, - R.style.ShapeAppearance_SysctlGui_BottomSheet - ).build() - - val currentShape = bottomSheet.background as MaterialShapeDrawable - return MaterialShapeDrawable(shapeAppearanceModel).apply { - initializeElevationOverlay(context) - fillColor = currentShape.fillColor - tintList = currentShape.tintList - elevation = currentShape.elevation - strokeWidth = currentShape.strokeWidth - strokeColor = currentShape.strokeColor - } - } -} diff --git a/common/design/src/main/java/com/androidvip/sysctlgui/design/DesignResources.kt b/common/design/src/main/java/com/androidvip/sysctlgui/design/DesignResources.kt deleted file mode 100644 index 2a29b4f..0000000 --- a/common/design/src/main/java/com/androidvip/sysctlgui/design/DesignResources.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.androidvip.sysctlgui.design - -typealias DesignIds = com.androidvip.sysctlgui.design.R.id -typealias DesignLayouts = com.androidvip.sysctlgui.design.R.layout -typealias DesignStyles = com.androidvip.sysctlgui.design.R.style diff --git a/common/design/src/main/java/com/androidvip/sysctlgui/design/ModalBottomSheet.kt b/common/design/src/main/java/com/androidvip/sysctlgui/design/ModalBottomSheet.kt deleted file mode 100644 index 24968e5..0000000 --- a/common/design/src/main/java/com/androidvip/sysctlgui/design/ModalBottomSheet.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.androidvip.sysctlgui.design - -import android.content.Context -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.os.bundleOf -import com.androidvip.sysctlgui.design.databinding.ModalBottomSheetBinding - -open class ModalBottomSheet : BaseBottomSheetFragment() { - - private var listener: EventListener? = null - - override fun setViewBinding( - inflater: LayoutInflater, - container: ViewGroup? - ): ModalBottomSheetBinding = ModalBottomSheetBinding.inflate(inflater) - - override fun onAttach(context: Context) { - super.onAttach(context) - val parent = parentFragment - if (parent != null) { - listener = parent as? EventListener - } - - if (listener == null) { - listener = context as? EventListener - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.sheetTitle.text = arguments?.getString(ARG_TITLE).orEmpty() - binding.sheetDescription.text = arguments?.getCharSequence(ARG_MESSAGE) ?: "" - - arguments?.getString(ARG_POSITIVE_BUTTON_TEXT)?.let { - binding.positiveButton.apply { - text = it - visibility = View.VISIBLE - setOnClickListener { - listener?.onContinuePressed() - dismiss() - } - } - } - - arguments?.getString(ARG_NEGATIVE_BUTTON_TEXT)?.let { - binding.negativeButton.apply { - text = it - visibility = View.VISIBLE - setOnClickListener { - listener?.onCancelPressed() - dismiss() - } - } - } - } - - interface EventListener { - fun onContinuePressed() - fun onCancelPressed() - } - - companion object { - private const val ARG_TITLE = "title" - private const val ARG_MESSAGE = "message" - private const val ARG_POSITIVE_BUTTON_TEXT = "positiveButtonText" - private const val ARG_NEGATIVE_BUTTON_TEXT = "negativeButtonText" - - fun newInstance( - title: String, - message: CharSequence, - positiveButtonText: String? = null, - negativeButtonText: String? = null - ): ModalBottomSheet { - return ModalBottomSheet().apply { - arguments = bundleOf( - ARG_TITLE to title, - ARG_MESSAGE to message, - ARG_POSITIVE_BUTTON_TEXT to positiveButtonText, - ARG_NEGATIVE_BUTTON_TEXT to negativeButtonText - ) - } - } - } -} diff --git a/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Color.kt b/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Color.kt index 14a01ef..e8fc935 100644 --- a/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Color.kt +++ b/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Color.kt @@ -2,88 +2,218 @@ package com.androidvip.sysctlgui.design.theme import androidx.compose.ui.graphics.Color -val md_theme_light_primary = Color(0xFF006685) -val md_theme_light_onPrimary = Color(0xFFFFFFFF) -val md_theme_light_primaryContainer = Color(0xFFBFE9FF) -val md_theme_light_onPrimaryContainer = Color(0xFF001F2A) -val md_theme_light_secondary = Color(0xFF4D616C) -val md_theme_light_onSecondary = Color(0xFFFFFFFF) -val md_theme_light_secondaryContainer = Color(0xFFD0E6F3) -val md_theme_light_onSecondaryContainer = Color(0xFF081E27) -val md_theme_light_tertiary = Color(0xFF5E5A7D) -val md_theme_light_onTertiary = Color(0xFFFFFFFF) -val md_theme_light_tertiaryContainer = Color(0xFFE4DFFF) -val md_theme_light_onTertiaryContainer = Color(0xFF1A1836) -val md_theme_light_error = Color(0xFFBA1A1A) -val md_theme_light_errorContainer = Color(0xFFFFDAD6) -val md_theme_light_onError = Color(0xFFFFFFFF) -val md_theme_light_onErrorContainer = Color(0xFF410002) -val md_theme_light_background = Color(0xFFFBFCFE) -val md_theme_light_onBackground = Color(0xFF191C1E) -val md_theme_light_surface = Color(0xFFFBFCFE) -val md_theme_light_onSurface = Color(0xFF191C1E) -val md_theme_light_surfaceVariant = Color(0xFFDCE3E9) -val md_theme_light_onSurfaceVariant = Color(0xFF40484C) -val md_theme_light_outline = Color(0xFF70787D) -val md_theme_light_inverseOnSurface = Color(0xFFF0F1F3) -val md_theme_light_inverseSurface = Color(0xFF2E3133) -val md_theme_light_inversePrimary = Color(0xFF6DD2FF) -val md_theme_light_shadow = Color(0xFF000000) -val md_theme_light_surfaceTint = Color(0xFF006685) -val md_theme_light_outlineVariant = Color(0xFFC0C8CD) -val md_theme_light_scrim = Color(0xFF000000) +val primaryLight = Color(0xFF4B5C92) +val onPrimaryLight = Color(0xFFFFFFFF) +val primaryContainerLight = Color(0xFFDBE1FF) +val onPrimaryContainerLight = Color(0xFF334478) +val secondaryLight = Color(0xFF595E72) +val onSecondaryLight = Color(0xFFFFFFFF) +val secondaryContainerLight = Color(0xFFDDE1F9) +val onSecondaryContainerLight = Color(0xFF414659) +val tertiaryLight = Color(0xFF1D6B50) +val onTertiaryLight = Color(0xFFFFFFFF) +val tertiaryContainerLight = Color(0xFFA7F2D0) +val onTertiaryContainerLight = Color(0xFF00513A) +val errorLight = Color(0xFFBA1A1A) +val onErrorLight = Color(0xFFFFFFFF) +val errorContainerLight = Color(0xFFFFDAD6) +val onErrorContainerLight = Color(0xFF93000A) +val backgroundLight = Color(0xFFFAF8FF) +val onBackgroundLight = Color(0xFF1A1B21) +val surfaceLight = Color(0xFFFAF8FF) +val onSurfaceLight = Color(0xFF1A1B21) +val surfaceVariantLight = Color(0xFFE2E2EC) +val onSurfaceVariantLight = Color(0xFF45464F) +val outlineLight = Color(0xFF757680) +val outlineVariantLight = Color(0xFFC5C6D0) +val scrimLight = Color(0xFF000000) +val inverseSurfaceLight = Color(0xFF2F3036) +val inverseOnSurfaceLight = Color(0xFFF1F0F7) +val inversePrimaryLight = Color(0xFFB4C5FF) +val surfaceDimLight = Color(0xFFDAD9E0) +val surfaceBrightLight = Color(0xFFFAF8FF) +val surfaceContainerLowestLight = Color(0xFFFFFFFF) +val surfaceContainerLowLight = Color(0xFFF4F3FA) +val surfaceContainerLight = Color(0xFFEEEDF4) +val surfaceContainerHighLight = Color(0xFFE8E7EF) +val surfaceContainerHighestLight = Color(0xFFE3E2E9) -val md_theme_dark_primary = Color(0xFF6DD2FF) -val md_theme_dark_onPrimary = Color(0xFF003547) -val md_theme_dark_primaryContainer = Color(0xFF004D65) -val md_theme_dark_onPrimaryContainer = Color(0xFFBFE9FF) -val md_theme_dark_secondary = Color(0xFFB4CAD6) -val md_theme_dark_onSecondary = Color(0xFF1F333D) -val md_theme_dark_secondaryContainer = Color(0xFF364954) -val md_theme_dark_onSecondaryContainer = Color(0xFFD0E6F3) -val md_theme_dark_tertiary = Color(0xFFC7C2EA) -val md_theme_dark_onTertiary = Color(0xFF2F2D4C) -val md_theme_dark_tertiaryContainer = Color(0xFF464364) -val md_theme_dark_onTertiaryContainer = Color(0xFFE4DFFF) -val md_theme_dark_error = Color(0xFFFFB4AB) -val md_theme_dark_errorContainer = Color(0xFF93000A) -val md_theme_dark_onError = Color(0xFF690005) -val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) -val md_theme_dark_background = Color(0xFF191C1E) -val md_theme_dark_onBackground = Color(0xFFE1E2E5) -val md_theme_dark_surface = Color(0xFF191C1E) -val md_theme_dark_onSurface = Color(0xFFE1E2E5) -val md_theme_dark_surfaceVariant = Color(0xFF40484C) -val md_theme_dark_onSurfaceVariant = Color(0xFFC0C8CD) -val md_theme_dark_outline = Color(0xFF8A9297) -val md_theme_dark_inverseOnSurface = Color(0xFF191C1E) -val md_theme_dark_inverseSurface = Color(0xFFE1E2E5) -val md_theme_dark_inversePrimary = Color(0xFF006685) -val md_theme_dark_shadow = Color(0xFF000000) -val md_theme_dark_surfaceTint = Color(0xFF6DD2FF) -val md_theme_dark_outlineVariant = Color(0xFF40484C) -val md_theme_dark_scrim = Color(0xFF000000) +val primaryLightMediumContrast = Color(0xFF213367) +val onPrimaryLightMediumContrast = Color(0xFFFFFFFF) +val primaryContainerLightMediumContrast = Color(0xFF5A6BA2) +val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val secondaryLightMediumContrast = Color(0xFF303648) +val onSecondaryLightMediumContrast = Color(0xFFFFFFFF) +val secondaryContainerLightMediumContrast = Color(0xFF676C81) +val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val tertiaryLightMediumContrast = Color(0xFF003F2C) +val onTertiaryLightMediumContrast = Color(0xFFFFFFFF) +val tertiaryContainerLightMediumContrast = Color(0xFF307A5E) +val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val errorLightMediumContrast = Color(0xFF740006) +val onErrorLightMediumContrast = Color(0xFFFFFFFF) +val errorContainerLightMediumContrast = Color(0xFFCF2C27) +val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF) +val backgroundLightMediumContrast = Color(0xFFFAF8FF) +val onBackgroundLightMediumContrast = Color(0xFF1A1B21) +val surfaceLightMediumContrast = Color(0xFFFAF8FF) +val onSurfaceLightMediumContrast = Color(0xFF101116) +val surfaceVariantLightMediumContrast = Color(0xFFE2E2EC) +val onSurfaceVariantLightMediumContrast = Color(0xFF34363E) +val outlineLightMediumContrast = Color(0xFF51525B) +val outlineVariantLightMediumContrast = Color(0xFF6B6C75) +val scrimLightMediumContrast = Color(0xFF000000) +val inverseSurfaceLightMediumContrast = Color(0xFF2F3036) +val inverseOnSurfaceLightMediumContrast = Color(0xFFF1F0F7) +val inversePrimaryLightMediumContrast = Color(0xFFB4C5FF) +val surfaceDimLightMediumContrast = Color(0xFFC6C6CD) +val surfaceBrightLightMediumContrast = Color(0xFFFAF8FF) +val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF) +val surfaceContainerLowLightMediumContrast = Color(0xFFF4F3FA) +val surfaceContainerLightMediumContrast = Color(0xFFE8E7EF) +val surfaceContainerHighLightMediumContrast = Color(0xFFDDDCE3) +val surfaceContainerHighestLightMediumContrast = Color(0xFFD2D1D8) -val green_200 = Color(0xFF80E4A9) -val green_500 = Color(0xFF00C853) -val green_800 = Color(0xFF00B439) +val primaryLightHighContrast = Color(0xFF15295C) +val onPrimaryLightHighContrast = Color(0xFFFFFFFF) +val primaryContainerLightHighContrast = Color(0xFF35477B) +val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF) +val secondaryLightHighContrast = Color(0xFF262C3D) +val onSecondaryLightHighContrast = Color(0xFFFFFFFF) +val secondaryContainerLightHighContrast = Color(0xFF43485C) +val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF) +val tertiaryLightHighContrast = Color(0xFF003323) +val onTertiaryLightHighContrast = Color(0xFFFFFFFF) +val tertiaryContainerLightHighContrast = Color(0xFF00543C) +val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF) +val errorLightHighContrast = Color(0xFF600004) +val onErrorLightHighContrast = Color(0xFFFFFFFF) +val errorContainerLightHighContrast = Color(0xFF98000A) +val onErrorContainerLightHighContrast = Color(0xFFFFFFFF) +val backgroundLightHighContrast = Color(0xFFFAF8FF) +val onBackgroundLightHighContrast = Color(0xFF1A1B21) +val surfaceLightHighContrast = Color(0xFFFAF8FF) +val onSurfaceLightHighContrast = Color(0xFF000000) +val surfaceVariantLightHighContrast = Color(0xFFE2E2EC) +val onSurfaceVariantLightHighContrast = Color(0xFF000000) +val outlineLightHighContrast = Color(0xFF2A2C34) +val outlineVariantLightHighContrast = Color(0xFF474951) +val scrimLightHighContrast = Color(0xFF000000) +val inverseSurfaceLightHighContrast = Color(0xFF2F3036) +val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF) +val inversePrimaryLightHighContrast = Color(0xFFB4C5FF) +val surfaceDimLightHighContrast = Color(0xFFB9B8BF) +val surfaceBrightLightHighContrast = Color(0xFFFAF8FF) +val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF) +val surfaceContainerLowLightHighContrast = Color(0xFFF1F0F7) +val surfaceContainerLightHighContrast = Color(0xFFE3E2E9) +val surfaceContainerHighLightHighContrast = Color(0xFFD5D3DB) +val surfaceContainerHighestLightHighContrast = Color(0xFFC6C6CD) -val orange200 = Color(0xFFFFCB80) -val orange500 = Color(0xFFFF9600) -val orange800 = Color(0xFFFF7900) +val primaryDark = Color(0xFFB4C5FF) +val onPrimaryDark = Color(0xFF1A2D60) +val primaryContainerDark = Color(0xFF334478) +val onPrimaryContainerDark = Color(0xFFDBE1FF) +val secondaryDark = Color(0xFFC1C5DD) +val onSecondaryDark = Color(0xFF2B3042) +val secondaryContainerDark = Color(0xFF414659) +val onSecondaryContainerDark = Color(0xFFDDE1F9) +val tertiaryDark = Color(0xFF8CD5B4) +val onTertiaryDark = Color(0xFF003827) +val tertiaryContainerDark = Color(0xFF00513A) +val onTertiaryContainerDark = Color(0xFFA7F2D0) +val errorDark = Color(0xFFFFB4AB) +val onErrorDark = Color(0xFF690005) +val errorContainerDark = Color(0xFF93000A) +val onErrorContainerDark = Color(0xFFFFDAD6) +val backgroundDark = Color(0xFF121318) +val onBackgroundDark = Color(0xFFE3E2E9) +val surfaceDark = Color(0xFF121318) +val onSurfaceDark = Color(0xFFE3E2E9) +val surfaceVariantDark = Color(0xFF45464F) +val onSurfaceVariantDark = Color(0xFFC5C6D0) +val outlineDark = Color(0xFF8F909A) +val outlineVariantDark = Color(0xFF45464F) +val scrimDark = Color(0xFF000000) +val inverseSurfaceDark = Color(0xFFE3E2E9) +val inverseOnSurfaceDark = Color(0xFF2F3036) +val inversePrimaryDark = Color(0xFF4B5C92) +val surfaceDimDark = Color(0xFF121318) +val surfaceBrightDark = Color(0xFF38393F) +val surfaceContainerLowestDark = Color(0xFF0D0E13) +val surfaceContainerLowDark = Color(0xFF1A1B21) +val surfaceContainerDark = Color(0xFF1E1F25) +val surfaceContainerHighDark = Color(0xFF292A2F) +val surfaceContainerHighestDark = Color(0xFF34343A) -val white = Color.White -val white87 = Color(0xDEFFFFFF) -val white70 = Color(0xB3FFFFFF) -val white54 = Color(0x8AFFFFFF) -val white50 = Color(0x80FFFFFF) -val white38 = Color(0x61FFFFFF) -val white12 = Color(0x12FFFFFF) +val primaryDarkMediumContrast = Color(0xFFD3DBFF) +val onPrimaryDarkMediumContrast = Color(0xFF0D2255) +val primaryContainerDarkMediumContrast = Color(0xFF7D8FC8) +val onPrimaryContainerDarkMediumContrast = Color(0xFF000000) +val secondaryDarkMediumContrast = Color(0xFFD7DBF3) +val onSecondaryDarkMediumContrast = Color(0xFF202536) +val secondaryContainerDarkMediumContrast = Color(0xFF8B90A5) +val onSecondaryContainerDarkMediumContrast = Color(0xFF000000) +val tertiaryDarkMediumContrast = Color(0xFFA1ECCA) +val onTertiaryDarkMediumContrast = Color(0xFF002C1E) +val tertiaryContainerDarkMediumContrast = Color(0xFF569E80) +val onTertiaryContainerDarkMediumContrast = Color(0xFF000000) +val errorDarkMediumContrast = Color(0xFFFFD2CC) +val onErrorDarkMediumContrast = Color(0xFF540003) +val errorContainerDarkMediumContrast = Color(0xFFFF5449) +val onErrorContainerDarkMediumContrast = Color(0xFF000000) +val backgroundDarkMediumContrast = Color(0xFF121318) +val onBackgroundDarkMediumContrast = Color(0xFFE3E2E9) +val surfaceDarkMediumContrast = Color(0xFF121318) +val onSurfaceDarkMediumContrast = Color(0xFFFFFFFF) +val surfaceVariantDarkMediumContrast = Color(0xFF45464F) +val onSurfaceVariantDarkMediumContrast = Color(0xFFDBDBE6) +val outlineDarkMediumContrast = Color(0xFFB1B1BB) +val outlineVariantDarkMediumContrast = Color(0xFF8F9099) +val scrimDarkMediumContrast = Color(0xFF000000) +val inverseSurfaceDarkMediumContrast = Color(0xFFE3E2E9) +val inverseOnSurfaceDarkMediumContrast = Color(0xFF292A2F) +val inversePrimaryDarkMediumContrast = Color(0xFF34467A) +val surfaceDimDarkMediumContrast = Color(0xFF121318) +val surfaceBrightDarkMediumContrast = Color(0xFF43444A) +val surfaceContainerLowestDarkMediumContrast = Color(0xFF06070C) +val surfaceContainerLowDarkMediumContrast = Color(0xFF1C1D23) +val surfaceContainerDarkMediumContrast = Color(0xFF27282D) +val surfaceContainerHighDarkMediumContrast = Color(0xFF313238) +val surfaceContainerHighestDarkMediumContrast = Color(0xFF3C3D43) -val black = Color.Black -val black87 = Color(0xDE000000) -val black70 = Color(0xB3000000) -val black54 = Color(0x8A000000) -val black50 = Color(0x80000000) -val black38 = Color(0x61000000) -val black12 = Color(0x12000000) +val primaryDarkHighContrast = Color(0xFFEDEFFF) +val onPrimaryDarkHighContrast = Color(0xFF000000) +val primaryContainerDarkHighContrast = Color(0xFFAFC1FD) +val onPrimaryContainerDarkHighContrast = Color(0xFF000928) +val secondaryDarkHighContrast = Color(0xFFEDEFFF) +val onSecondaryDarkHighContrast = Color(0xFF000000) +val secondaryContainerDarkHighContrast = Color(0xFFBDC2D9) +val onSecondaryContainerDarkHighContrast = Color(0xFF060A1B) +val tertiaryDarkHighContrast = Color(0xFFB8FFDE) +val onTertiaryDarkHighContrast = Color(0xFF000000) +val tertiaryContainerDarkHighContrast = Color(0xFF88D2B1) +val onTertiaryContainerDarkHighContrast = Color(0xFF000E08) +val errorDarkHighContrast = Color(0xFFFFECE9) +val onErrorDarkHighContrast = Color(0xFF000000) +val errorContainerDarkHighContrast = Color(0xFFFFAEA4) +val onErrorContainerDarkHighContrast = Color(0xFF220001) +val backgroundDarkHighContrast = Color(0xFF121318) +val onBackgroundDarkHighContrast = Color(0xFFE3E2E9) +val surfaceDarkHighContrast = Color(0xFF121318) +val onSurfaceDarkHighContrast = Color(0xFFFFFFFF) +val surfaceVariantDarkHighContrast = Color(0xFF45464F) +val onSurfaceVariantDarkHighContrast = Color(0xFFFFFFFF) +val outlineDarkHighContrast = Color(0xFFEFEFFA) +val outlineVariantDarkHighContrast = Color(0xFFC2C2CC) +val scrimDarkHighContrast = Color(0xFF000000) +val inverseSurfaceDarkHighContrast = Color(0xFFE3E2E9) +val inverseOnSurfaceDarkHighContrast = Color(0xFF000000) +val inversePrimaryDarkHighContrast = Color(0xFF34467A) +val surfaceDimDarkHighContrast = Color(0xFF121318) +val surfaceBrightDarkHighContrast = Color(0xFF4F5056) +val surfaceContainerLowestDarkHighContrast = Color(0xFF000000) +val surfaceContainerLowDarkHighContrast = Color(0xFF1E1F25) +val surfaceContainerDarkHighContrast = Color(0xFF2F3036) +val surfaceContainerHighDarkHighContrast = Color(0xFF3A3B41) +val surfaceContainerHighestDarkHighContrast = Color(0xFF46464C) diff --git a/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Shape.kt b/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Shape.kt deleted file mode 100644 index d0f01ac..0000000 --- a/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Shape.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.androidvip.sysctlgui.design.theme - -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Shapes -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.ui.unit.dp - -val SysctlGuiShapes = Shapes( - extraSmall = RoundedCornerShape(4.dp), - small = RoundedCornerShape(8.dp), - medium = RoundedCornerShape(12.dp), - large = RoundedCornerShape(16.dp), - extraLarge = RoundedCornerShape(24.dp), -) - -val MaterialTheme.bottomSheetShape - @Composable - @ReadOnlyComposable - get() = RoundedCornerShape( - topStart = 24.dp, - topEnd = 24.dp, - bottomStart = 0.dp, - bottomEnd = 0.dp - ) diff --git a/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Theme.kt b/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Theme.kt index 60d1c57..69a1120 100644 --- a/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Theme.kt +++ b/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Theme.kt @@ -10,90 +10,263 @@ import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext -internal val LightColors = lightColorScheme( - primary = md_theme_light_primary, - onPrimary = md_theme_light_onPrimary, - primaryContainer = md_theme_light_primaryContainer, - onPrimaryContainer = md_theme_light_onPrimaryContainer, - secondary = md_theme_light_secondary, - onSecondary = md_theme_light_onSecondary, - secondaryContainer = md_theme_light_secondaryContainer, - onSecondaryContainer = md_theme_light_onSecondaryContainer, - tertiary = md_theme_light_tertiary, - onTertiary = md_theme_light_onTertiary, - tertiaryContainer = md_theme_light_tertiaryContainer, - onTertiaryContainer = md_theme_light_onTertiaryContainer, - error = md_theme_light_error, - errorContainer = md_theme_light_errorContainer, - onError = md_theme_light_onError, - onErrorContainer = md_theme_light_onErrorContainer, - background = md_theme_light_background, - onBackground = md_theme_light_onBackground, - surface = md_theme_light_surface, - onSurface = md_theme_light_onSurface, - surfaceVariant = md_theme_light_surfaceVariant, - onSurfaceVariant = md_theme_light_onSurfaceVariant, - outline = md_theme_light_outline, - inverseOnSurface = md_theme_light_inverseOnSurface, - inverseSurface = md_theme_light_inverseSurface, - inversePrimary = md_theme_light_inversePrimary, - surfaceTint = md_theme_light_surfaceTint, - outlineVariant = md_theme_light_outlineVariant, - scrim = md_theme_light_scrim +private val lightScheme = lightColorScheme( + primary = primaryLight, + onPrimary = onPrimaryLight, + primaryContainer = primaryContainerLight, + onPrimaryContainer = onPrimaryContainerLight, + secondary = secondaryLight, + onSecondary = onSecondaryLight, + secondaryContainer = secondaryContainerLight, + onSecondaryContainer = onSecondaryContainerLight, + tertiary = tertiaryLight, + onTertiary = onTertiaryLight, + tertiaryContainer = tertiaryContainerLight, + onTertiaryContainer = onTertiaryContainerLight, + error = errorLight, + onError = onErrorLight, + errorContainer = errorContainerLight, + onErrorContainer = onErrorContainerLight, + background = backgroundLight, + onBackground = onBackgroundLight, + surface = surfaceLight, + onSurface = onSurfaceLight, + surfaceVariant = surfaceVariantLight, + onSurfaceVariant = onSurfaceVariantLight, + outline = outlineLight, + outlineVariant = outlineVariantLight, + scrim = scrimLight, + inverseSurface = inverseSurfaceLight, + inverseOnSurface = inverseOnSurfaceLight, + inversePrimary = inversePrimaryLight, + surfaceDim = surfaceDimLight, + surfaceBright = surfaceBrightLight, + surfaceContainerLowest = surfaceContainerLowestLight, + surfaceContainerLow = surfaceContainerLowLight, + surfaceContainer = surfaceContainerLight, + surfaceContainerHigh = surfaceContainerHighLight, + surfaceContainerHighest = surfaceContainerHighestLight, ) -internal val DarkColors = darkColorScheme( - primary = md_theme_dark_primary, - onPrimary = md_theme_dark_onPrimary, - primaryContainer = md_theme_dark_primaryContainer, - onPrimaryContainer = md_theme_dark_onPrimaryContainer, - secondary = md_theme_dark_secondary, - onSecondary = md_theme_dark_onSecondary, - secondaryContainer = md_theme_dark_secondaryContainer, - onSecondaryContainer = md_theme_dark_onSecondaryContainer, - tertiary = md_theme_dark_tertiary, - onTertiary = md_theme_dark_onTertiary, - tertiaryContainer = md_theme_dark_tertiaryContainer, - onTertiaryContainer = md_theme_dark_onTertiaryContainer, - error = md_theme_dark_error, - errorContainer = md_theme_dark_errorContainer, - onError = md_theme_dark_onError, - onErrorContainer = md_theme_dark_onErrorContainer, - background = md_theme_dark_background, - onBackground = md_theme_dark_onBackground, - surface = md_theme_dark_surface, - onSurface = md_theme_dark_onSurface, - surfaceVariant = md_theme_dark_surfaceVariant, - onSurfaceVariant = md_theme_dark_onSurfaceVariant, - outline = md_theme_dark_outline, - inverseOnSurface = md_theme_dark_inverseOnSurface, - inverseSurface = md_theme_dark_inverseSurface, - inversePrimary = md_theme_dark_inversePrimary, - surfaceTint = md_theme_dark_surfaceTint, - outlineVariant = md_theme_dark_outlineVariant, - scrim = md_theme_dark_scrim +private val mediumContrastLightColorScheme = lightColorScheme( + primary = primaryLightMediumContrast, + onPrimary = onPrimaryLightMediumContrast, + primaryContainer = primaryContainerLightMediumContrast, + onPrimaryContainer = onPrimaryContainerLightMediumContrast, + secondary = secondaryLightMediumContrast, + onSecondary = onSecondaryLightMediumContrast, + secondaryContainer = secondaryContainerLightMediumContrast, + onSecondaryContainer = onSecondaryContainerLightMediumContrast, + tertiary = tertiaryLightMediumContrast, + onTertiary = onTertiaryLightMediumContrast, + tertiaryContainer = tertiaryContainerLightMediumContrast, + onTertiaryContainer = onTertiaryContainerLightMediumContrast, + error = errorLightMediumContrast, + onError = onErrorLightMediumContrast, + errorContainer = errorContainerLightMediumContrast, + onErrorContainer = onErrorContainerLightMediumContrast, + background = backgroundLightMediumContrast, + onBackground = onBackgroundLightMediumContrast, + surface = surfaceLightMediumContrast, + onSurface = onSurfaceLightMediumContrast, + surfaceVariant = surfaceVariantLightMediumContrast, + onSurfaceVariant = onSurfaceVariantLightMediumContrast, + outline = outlineLightMediumContrast, + outlineVariant = outlineVariantLightMediumContrast, + scrim = scrimLightMediumContrast, + inverseSurface = inverseSurfaceLightMediumContrast, + inverseOnSurface = inverseOnSurfaceLightMediumContrast, + inversePrimary = inversePrimaryLightMediumContrast, + surfaceDim = surfaceDimLightMediumContrast, + surfaceBright = surfaceBrightLightMediumContrast, + surfaceContainerLowest = surfaceContainerLowestLightMediumContrast, + surfaceContainerLow = surfaceContainerLowLightMediumContrast, + surfaceContainer = surfaceContainerLightMediumContrast, + surfaceContainerHigh = surfaceContainerHighLightMediumContrast, + surfaceContainerHighest = surfaceContainerHighestLightMediumContrast, +) + +private val highContrastLightColorScheme = lightColorScheme( + primary = primaryLightHighContrast, + onPrimary = onPrimaryLightHighContrast, + primaryContainer = primaryContainerLightHighContrast, + onPrimaryContainer = onPrimaryContainerLightHighContrast, + secondary = secondaryLightHighContrast, + onSecondary = onSecondaryLightHighContrast, + secondaryContainer = secondaryContainerLightHighContrast, + onSecondaryContainer = onSecondaryContainerLightHighContrast, + tertiary = tertiaryLightHighContrast, + onTertiary = onTertiaryLightHighContrast, + tertiaryContainer = tertiaryContainerLightHighContrast, + onTertiaryContainer = onTertiaryContainerLightHighContrast, + error = errorLightHighContrast, + onError = onErrorLightHighContrast, + errorContainer = errorContainerLightHighContrast, + onErrorContainer = onErrorContainerLightHighContrast, + background = backgroundLightHighContrast, + onBackground = onBackgroundLightHighContrast, + surface = surfaceLightHighContrast, + onSurface = onSurfaceLightHighContrast, + surfaceVariant = surfaceVariantLightHighContrast, + onSurfaceVariant = onSurfaceVariantLightHighContrast, + outline = outlineLightHighContrast, + outlineVariant = outlineVariantLightHighContrast, + scrim = scrimLightHighContrast, + inverseSurface = inverseSurfaceLightHighContrast, + inverseOnSurface = inverseOnSurfaceLightHighContrast, + inversePrimary = inversePrimaryLightHighContrast, + surfaceDim = surfaceDimLightHighContrast, + surfaceBright = surfaceBrightLightHighContrast, + surfaceContainerLowest = surfaceContainerLowestLightHighContrast, + surfaceContainerLow = surfaceContainerLowLightHighContrast, + surfaceContainer = surfaceContainerLightHighContrast, + surfaceContainerHigh = surfaceContainerHighLightHighContrast, + surfaceContainerHighest = surfaceContainerHighestLightHighContrast, +) + +private val darkScheme = darkColorScheme( + primary = primaryDark, + onPrimary = onPrimaryDark, + primaryContainer = primaryContainerDark, + onPrimaryContainer = onPrimaryContainerDark, + secondary = secondaryDark, + onSecondary = onSecondaryDark, + secondaryContainer = secondaryContainerDark, + onSecondaryContainer = onSecondaryContainerDark, + tertiary = tertiaryDark, + onTertiary = onTertiaryDark, + tertiaryContainer = tertiaryContainerDark, + onTertiaryContainer = onTertiaryContainerDark, + error = errorDark, + onError = onErrorDark, + errorContainer = errorContainerDark, + onErrorContainer = onErrorContainerDark, + background = backgroundDark, + onBackground = onBackgroundDark, + surface = surfaceDark, + onSurface = onSurfaceDark, + surfaceVariant = surfaceVariantDark, + onSurfaceVariant = onSurfaceVariantDark, + outline = outlineDark, + outlineVariant = outlineVariantDark, + scrim = scrimDark, + inverseSurface = inverseSurfaceDark, + inverseOnSurface = inverseOnSurfaceDark, + inversePrimary = inversePrimaryDark, + surfaceDim = surfaceDimDark, + surfaceBright = surfaceBrightDark, + surfaceContainerLowest = surfaceContainerLowestDark, + surfaceContainerLow = surfaceContainerLowDark, + surfaceContainer = surfaceContainerDark, + surfaceContainerHigh = surfaceContainerHighDark, + surfaceContainerHighest = surfaceContainerHighestDark, +) + +private val mediumContrastDarkColorScheme = darkColorScheme( + primary = primaryDarkMediumContrast, + onPrimary = onPrimaryDarkMediumContrast, + primaryContainer = primaryContainerDarkMediumContrast, + onPrimaryContainer = onPrimaryContainerDarkMediumContrast, + secondary = secondaryDarkMediumContrast, + onSecondary = onSecondaryDarkMediumContrast, + secondaryContainer = secondaryContainerDarkMediumContrast, + onSecondaryContainer = onSecondaryContainerDarkMediumContrast, + tertiary = tertiaryDarkMediumContrast, + onTertiary = onTertiaryDarkMediumContrast, + tertiaryContainer = tertiaryContainerDarkMediumContrast, + onTertiaryContainer = onTertiaryContainerDarkMediumContrast, + error = errorDarkMediumContrast, + onError = onErrorDarkMediumContrast, + errorContainer = errorContainerDarkMediumContrast, + onErrorContainer = onErrorContainerDarkMediumContrast, + background = backgroundDarkMediumContrast, + onBackground = onBackgroundDarkMediumContrast, + surface = surfaceDarkMediumContrast, + onSurface = onSurfaceDarkMediumContrast, + surfaceVariant = surfaceVariantDarkMediumContrast, + onSurfaceVariant = onSurfaceVariantDarkMediumContrast, + outline = outlineDarkMediumContrast, + outlineVariant = outlineVariantDarkMediumContrast, + scrim = scrimDarkMediumContrast, + inverseSurface = inverseSurfaceDarkMediumContrast, + inverseOnSurface = inverseOnSurfaceDarkMediumContrast, + inversePrimary = inversePrimaryDarkMediumContrast, + surfaceDim = surfaceDimDarkMediumContrast, + surfaceBright = surfaceBrightDarkMediumContrast, + surfaceContainerLowest = surfaceContainerLowestDarkMediumContrast, + surfaceContainerLow = surfaceContainerLowDarkMediumContrast, + surfaceContainer = surfaceContainerDarkMediumContrast, + surfaceContainerHigh = surfaceContainerHighDarkMediumContrast, + surfaceContainerHighest = surfaceContainerHighestDarkMediumContrast, +) + +private val highContrastDarkColorScheme = darkColorScheme( + primary = primaryDarkHighContrast, + onPrimary = onPrimaryDarkHighContrast, + primaryContainer = primaryContainerDarkHighContrast, + onPrimaryContainer = onPrimaryContainerDarkHighContrast, + secondary = secondaryDarkHighContrast, + onSecondary = onSecondaryDarkHighContrast, + secondaryContainer = secondaryContainerDarkHighContrast, + onSecondaryContainer = onSecondaryContainerDarkHighContrast, + tertiary = tertiaryDarkHighContrast, + onTertiary = onTertiaryDarkHighContrast, + tertiaryContainer = tertiaryContainerDarkHighContrast, + onTertiaryContainer = onTertiaryContainerDarkHighContrast, + error = errorDarkHighContrast, + onError = onErrorDarkHighContrast, + errorContainer = errorContainerDarkHighContrast, + onErrorContainer = onErrorContainerDarkHighContrast, + background = backgroundDarkHighContrast, + onBackground = onBackgroundDarkHighContrast, + surface = surfaceDarkHighContrast, + onSurface = onSurfaceDarkHighContrast, + surfaceVariant = surfaceVariantDarkHighContrast, + onSurfaceVariant = onSurfaceVariantDarkHighContrast, + outline = outlineDarkHighContrast, + outlineVariant = outlineVariantDarkHighContrast, + scrim = scrimDarkHighContrast, + inverseSurface = inverseSurfaceDarkHighContrast, + inverseOnSurface = inverseOnSurfaceDarkHighContrast, + inversePrimary = inversePrimaryDarkHighContrast, + surfaceDim = surfaceDimDarkHighContrast, + surfaceBright = surfaceBrightDarkHighContrast, + surfaceContainerLowest = surfaceContainerLowestDarkHighContrast, + surfaceContainerLow = surfaceContainerLowDarkHighContrast, + surfaceContainer = surfaceContainerDarkHighContrast, + surfaceContainerHigh = surfaceContainerHighDarkHighContrast, + surfaceContainerHighest = surfaceContainerHighestDarkHighContrast, ) @Composable fun SysctlGuiTheme( darkTheme: Boolean = isSystemInDarkTheme(), - forceDark: Boolean = false, dynamicColor: Boolean = false, + contrastLevel: Int = 1, content: @Composable () -> Unit ) { val colorScheme = when { - forceDark -> DarkColors dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { val context = LocalContext.current if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } - darkTheme -> DarkColors - else -> LightColors + + darkTheme -> when (contrastLevel) { + 2 -> mediumContrastDarkColorScheme + 3 -> highContrastDarkColorScheme + else -> darkScheme + } + + else -> when (contrastLevel) { + 2 -> mediumContrastLightColorScheme + 3 -> highContrastLightColorScheme + else -> lightScheme + } } MaterialTheme( colorScheme = colorScheme, - shapes = SysctlGuiShapes, + typography = Typography, content = content ) } diff --git a/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Type.kt b/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Type.kt new file mode 100644 index 0000000..9277e5c --- /dev/null +++ b/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Type.kt @@ -0,0 +1,133 @@ +package com.androidvip.sysctlgui.design.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp +import com.androidvip.sysctlgui.design.R + +val passionOneFontFamily = FontFamily( + Font(resId = R.font.passionone_regular, weight = FontWeight.Normal), + Font(resId = R.font.passionone_regular, weight = FontWeight.Medium), + Font(resId = R.font.passionone_regular, weight = FontWeight.SemiBold), + Font(resId = R.font.passionone_bold, weight = FontWeight.Bold), + Font(resId = R.font.passionone_bold, weight = FontWeight.ExtraBold), + Font(resId = R.font.passionone_bold, weight = FontWeight.Black) +) + +val sansationFontFamily = FontFamily( + Font(resId = R.font.sansation_regular), + Font(resId = R.font.sansation_regular_italic, style = FontStyle.Italic), + Font(resId = R.font.sansation_bold, weight = FontWeight.Medium), + Font(resId = R.font.sansation_bold, weight = FontWeight.SemiBold), + Font(resId = R.font.sansation_bold, weight = FontWeight.Bold), + Font(resId = R.font.sansation_bold_italic, weight = FontWeight.Bold, style = FontStyle.Italic), +) + +val Typography = Typography( + displayLarge = TextStyle( + fontFamily = passionOneFontFamily, + fontWeight = FontWeight.Black, + fontSize = 57.sp, + lineHeight = 64.sp, + letterSpacing = 0.sp + ), + displayMedium = TextStyle( + fontFamily = passionOneFontFamily, + fontWeight = FontWeight.Bold, + fontSize = 45.sp, + lineHeight = 52.sp, + letterSpacing = 0.sp + ), + displaySmall = TextStyle( + fontFamily = passionOneFontFamily, + fontWeight = FontWeight.Bold, + fontSize = 36.sp, + lineHeight = 44.sp, + letterSpacing = 0.sp + ), + headlineLarge = TextStyle( + fontFamily = passionOneFontFamily, + fontWeight = FontWeight.Bold, + fontSize = 32.sp, + lineHeight = 40.sp, + letterSpacing = 0.sp + ), + headlineMedium = TextStyle( + fontFamily = passionOneFontFamily, + fontWeight = FontWeight.Bold, + fontSize = 28.sp, + lineHeight = 36.sp, + letterSpacing = 0.sp + ), + headlineSmall = TextStyle( + fontFamily = passionOneFontFamily, + fontWeight = FontWeight.SemiBold, + fontSize = 24.sp, + lineHeight = 32.sp, + letterSpacing = 0.sp + ), + titleLarge = TextStyle( + fontFamily = sansationFontFamily, + fontWeight = FontWeight.Bold, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + titleMedium = TextStyle( + fontFamily = sansationFontFamily, + fontWeight = FontWeight.Medium, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.15.sp + ), + titleSmall = TextStyle( + fontFamily = sansationFontFamily, + fontWeight = FontWeight.SemiBold, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.1.sp + ), + bodyLarge = TextStyle( + fontWeight = FontWeight.Medium, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.15.sp + ), + bodyMedium = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.25.sp + ), + bodySmall = TextStyle( + fontWeight = FontWeight.Normal, + fontSize = 12.sp, + lineHeight = 16.sp, + letterSpacing = 0.4.sp + ), + labelLarge = TextStyle( + fontFamily = sansationFontFamily, + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + lineHeight = 20.sp, + letterSpacing = 0.1.sp + ), + labelMedium = TextStyle( + fontFamily = sansationFontFamily, + fontWeight = FontWeight.Medium, + fontSize = 12.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ), + labelSmall = TextStyle( + fontFamily = sansationFontFamily, + fontWeight = FontWeight.Normal, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) +) diff --git a/common/design/src/main/res/font/passionone_bold.ttf b/common/design/src/main/res/font/passionone_bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..25a35e453ed4ed792204e44ba2f6378eb52b8903 GIT binary patch literal 22876 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 vmMGy literal 0 HcmV?d00001 diff --git a/common/design/src/main/res/font/passionone_regular.ttf b/common/design/src/main/res/font/passionone_regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..099be0fc86b29052f283167fe539ac9f6333ca25 GIT binary patch literal 23076 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

    &-0e_1nn|WxxNfwtb*s09f&5 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 literal 0 HcmV?d00001 diff --git a/common/design/src/main/res/font/sansation_bold_italic.ttf b/common/design/src/main/res/font/sansation_bold_italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8677879c7b2e26a20d0509bb9173fdf45a00ac1b GIT binary patch literal 38740 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

    vOjIs7K
    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
    
    literal 0
    HcmV?d00001
    
    diff --git a/common/design/src/main/res/font/sansation_regular.ttf b/common/design/src/main/res/font/sansation_regular.ttf
    new file mode 100644
    index 0000000000000000000000000000000000000000..e9bda1717a15cfe11eec38d175066f935bd206f6
    GIT binary patch
    literal 36764
    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
    
    literal 0
    HcmV?d00001
    
    diff --git a/common/design/src/main/res/font/sansation_regular_italic.ttf b/common/design/src/main/res/font/sansation_regular_italic.ttf
    new file mode 100644
    index 0000000000000000000000000000000000000000..95258de9b8db2785badfb5f3c98b14521c4109f7
    GIT binary patch
    literal 38596
    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
    
    literal 0
    HcmV?d00001
    
    diff --git a/common/design/src/main/res/layout/dialog_web.xml b/common/design/src/main/res/layout/dialog_web.xml
    deleted file mode 100644
    index 7947cf0..0000000
    --- a/common/design/src/main/res/layout/dialog_web.xml
    +++ /dev/null
    @@ -1,28 +0,0 @@
    -
    -
    -
    -    
    -
    -        
    -
    -    
    -
    -    
    -
    -
    diff --git a/common/design/src/main/res/layout/modal_bottom_sheet.xml b/common/design/src/main/res/layout/modal_bottom_sheet.xml
    deleted file mode 100644
    index ca17b9e..0000000
    --- a/common/design/src/main/res/layout/modal_bottom_sheet.xml
    +++ /dev/null
    @@ -1,79 +0,0 @@
    -
    -
    -
    -    
    -
    -    
    -
    -    
    -
    -    
    -
    -    
    -
    -    
    -
    -
    diff --git a/common/design/src/main/res/layout/preference_widget_switch_compat.xml b/common/design/src/main/res/layout/preference_widget_switch_compat.xml
    deleted file mode 100644
    index 8641b11..0000000
    --- a/common/design/src/main/res/layout/preference_widget_switch_compat.xml
    +++ /dev/null
    @@ -1,9 +0,0 @@
    -
    -
    \ No newline at end of file
    diff --git a/data/src/androidTest/java/com/androidvip/sysctlgui/data/ExampleInstrumentedTest.kt b/data/src/androidTest/java/com/androidvip/sysctlgui/data/ExampleInstrumentedTest.kt
    deleted file mode 100644
    index 9f0a565..0000000
    --- a/data/src/androidTest/java/com/androidvip/sysctlgui/data/ExampleInstrumentedTest.kt
    +++ /dev/null
    @@ -1,24 +0,0 @@
    -package com.androidvip.sysctlgui.data
    -
    -import androidx.test.platform.app.InstrumentationRegistry
    -import androidx.test.ext.junit.runners.AndroidJUnit4
    -
    -import org.junit.Test
    -import org.junit.runner.RunWith
    -
    -import org.junit.Assert.*
    -
    -/**
    - * Instrumented test, which will execute on an Android device.
    - *
    - * See [testing documentation](http://d.android.com/tools/testing).
    - */
    -@RunWith(AndroidJUnit4::class)
    -class ExampleInstrumentedTest {
    -    @Test
    -    fun useAppContext() {
    -        // Context of the app under test.
    -        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
    -        assertEquals("com.androidvip.sysctlgui.data", appContext.packageName)
    -    }
    -}
    \ No newline at end of file
    diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt b/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt
    index 1cbd751..29155ea 100644
    --- a/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt
    +++ b/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt
    @@ -28,6 +28,8 @@ import io.ktor.client.engine.android.Android
     import io.ktor.client.plugins.logging.LogLevel
     import io.ktor.client.plugins.logging.Logger
     import io.ktor.client.plugins.logging.Logging
    +import kotlinx.coroutines.CoroutineDispatcher
    +import kotlinx.coroutines.Dispatchers
     import org.koin.android.ext.koin.androidApplication
     import org.koin.android.ext.koin.androidContext
     import org.koin.core.module.dsl.factoryOf
    @@ -36,7 +38,8 @@ import org.koin.dsl.bind
     import org.koin.dsl.module
     
     val utilsModule = module {
    -    factoryOf(::RootUtils)
    +    factory { Dispatchers.IO }
    +    factory { RootUtils(Dispatchers.Default) }
         factory { PresetsFileProcessor(androidContext().contentResolver) }
         factory { AndroidStringProvider(androidApplication()) }
     }
    @@ -50,10 +53,17 @@ val repositoryModule = module {
         factoryOf(::AppPrefsImpl) bind AppPrefs::class
         factoryOf(::ParamsRepositoryImpl) bind ParamsRepository::class
         factoryOf(::PresetRepositoryImpl) bind PresetRepository::class
    -    factoryOf(::AppSettingsRepositoryImpl) bind AppSettingsRepository::class
     
         single { UserRepositoryImpl(paramDao = get().paramDao()) }
     
    +    factory {
    +        AppSettingsRepositoryImpl(
    +            context = androidContext(),
    +            sharedPreferences = get(),
    +            rootUtils = get()
    +        )
    +    }
    +
         factory {
             DocumentationRepositoryImpl(
                 offlineDataSource = get(named()),
    diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt b/data/src/main/java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt
    index fd31d0e..5188801 100644
    --- a/data/src/main/java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt
    +++ b/data/src/main/java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt
    @@ -25,6 +25,19 @@ data class KernelParamDTO(
         override val isTaskerParam: Boolean = false,
         @ColumnInfo(name = "tasker_list")
         override val taskerList: Int = Consts.LIST_NUMBER_PRIMARY_TASKER
    -) : KernelParam(name, path, value, isFavorite, isTaskerParam, taskerList)
    +) : KernelParam(name, path, value, isFavorite, isTaskerParam, taskerList) {
    +    companion object {
    +        fun fromKernelParam(param: KernelParam): KernelParamDTO {
    +            return KernelParamDTO(
    +                name = param.name,
    +                path = param.path,
    +                value = param.value,
    +                isFavorite = param.isFavorite,
    +                isTaskerParam = param.isTaskerParam,
    +                taskerList = param.taskerList
    +            )
    +        }
    +    }
    +}
     
     internal const val PARAMS_TABLE_NAME = "roomKernelParam"
    diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt
    index 386e7f8..f0543d3 100644
    --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt
    +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt
    @@ -9,6 +9,7 @@ import com.androidvip.sysctlgui.domain.enums.CommitMode
     import com.androidvip.sysctlgui.domain.enums.SettingItemType
     import com.androidvip.sysctlgui.domain.models.AppSetting
     import com.androidvip.sysctlgui.domain.repository.AppSettingsRepository
    +import kotlinx.coroutines.Dispatchers
     import kotlinx.coroutines.withContext
     import kotlin.coroutines.CoroutineContext
     
    @@ -16,7 +17,7 @@ class AppSettingsRepositoryImpl(
         private val context: Context,
         private val sharedPreferences: SharedPreferences,
         private val rootUtils: RootUtils,
    -    private val ioContext: CoroutineContext
    +    private val ioContext: CoroutineContext = Dispatchers.IO
     ) : AppSettingsRepository {
         override suspend fun getAppSettings(): List> = withContext(ioContext) {
             val usingDynamicColors = sharedPreferences.getBoolean(Prefs.DynamicColors.key, false)
    diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/ParamsRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/ParamsRepositoryImpl.kt
    index 768c335..1797c0b 100644
    --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/ParamsRepositoryImpl.kt
    +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/ParamsRepositoryImpl.kt
    @@ -96,10 +96,20 @@ class ParamsRepositoryImpl(
             val params = files.mapNotNull { file ->
                 try {
                     val path = file.absolutePath
    -                val value = rootUtils.executeCommandAndStreamOutput(
    -                    command = String.format(CAT_COMMAND_FORMAT, path)
    -                ).toList().joinToString("\n")
    -                KernelParam.createFromPath(path, value)
    +                if (file.isDirectory) {
    +                    KernelParam.createFromPath(path, "")
    +                } else {
    +                    val fileContent = runCatching { file.readText() }.getOrNull()
    +                    if (fileContent != null) {
    +                        KernelParam.createFromPath(path, fileContent)
    +                    } else {
    +                        val command = String.format(CAT_COMMAND_FORMAT, path)
    +                        val paramValue = rootUtils.executeCommandAndStreamOutput(command)
    +                            .toList()
    +                            .joinToString("\n")
    +                        KernelParam.createFromPath(path, paramValue)
    +                    }
    +                }
                 } catch (e: Exception) {
                     Log.e(TAG, "Failed to process file: ${file.path}", e)
                     null
    @@ -119,6 +129,10 @@ class ParamsRepositoryImpl(
                     !this.startsWith("sysctl")
         }
     
    +    interface ChangeListener {
    +        fun onChange()
    +    }
    +
         companion object {
             private const val BUSYBOX_PREFIX = "busybox "
             private const val SYSCTL_GET_ALL_COMMAND = "sysctl -a"
    diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt
    index 3dbb29b..f5ade4a 100644
    --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt
    +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt
    @@ -27,16 +27,16 @@ class UserRepositoryImpl(
         }
     
         override suspend fun upsertUserParam(param: KernelParam) = withContext(coroutineContext) {
    -        paramDao.upsert(param as KernelParamDTO)
    +        paramDao.upsert(KernelParamDTO.fromKernelParam(param))
         }
     
         override suspend fun upsertUserParams(params: List) =
             withContext(coroutineContext) {
    -            paramDao.upsertAll(params.map { it as KernelParamDTO })
    +            paramDao.upsertAll(params.map { KernelParamDTO.fromKernelParam(it) })
             }
     
         override suspend fun removeUserParam(param: KernelParam) = withContext(coroutineContext) {
    -        paramDao.delete(param as KernelParamDTO)
    +        paramDao.delete(KernelParamDTO.fromKernelParam(param))
         }
     
         override suspend fun clearUserParams() = withContext(coroutineContext) {
    diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/source/OnlineDocumentationDataSource.kt b/data/src/main/java/com/androidvip/sysctlgui/data/source/OnlineDocumentationDataSource.kt
    index c79b528..43f05fa 100644
    --- a/data/src/main/java/com/androidvip/sysctlgui/data/source/OnlineDocumentationDataSource.kt
    +++ b/data/src/main/java/com/androidvip/sysctlgui/data/source/OnlineDocumentationDataSource.kt
    @@ -10,6 +10,7 @@ import io.ktor.http.isSuccess
     import kotlinx.coroutines.Dispatchers
     import kotlinx.coroutines.withContext
     import org.jsoup.Jsoup
    +import java.io.File
     import kotlin.coroutines.CoroutineContext
     
     
    @@ -48,7 +49,19 @@ class OnlineDocumentationDataSource(
                 val html = response.bodyAsText()
                 val document = Jsoup.parse(html)
                 val htmlElementId = param.lastNameSegment.replace('_', '-')
    -            val elements = document.select("section#$htmlElementId p")
    +
    +
    +            if (File(param.path).isDirectory) {
    +                // If we got something out of the request, might as well return at least the URL
    +                return@withContext ParamDocumentation(
    +                    title = param.name,
    +                    documentationText = "",
    +                    documentationHtml = "", // HTML might be huge (directory documentation)
    +                    url = url
    +                )
    +            }
    +
    +            val elements = document.select("section#$htmlElementId :not(h2)")
     
                 if (elements.isEmpty()) {
                     Log.w(
    @@ -56,6 +69,9 @@ class OnlineDocumentationDataSource(
                         "No documentation found for ${param.name} with id $htmlElementId on $url"
                     )
                     return@withContext null
    +            } else {
    +                // Remove first element (usually a heading remnant)
    +                elements.removeAt(0)
                 }
     
                 return@withContext ParamDocumentation(
    @@ -71,8 +87,11 @@ class OnlineDocumentationDataSource(
         }
     
         private fun getDocumentationUrl(param: KernelParam): String {
    +        if (File(param.path).isDirectory) {
    +            return "${DOC_BASE_URL}${param.name}.html"
    +        }
             val configName = param.groupName
    -        return "${DOC_BASE_URL}$configName.html#${param.name}"
    +        return "${DOC_BASE_URL}$configName.html#${param.lastNameSegment.replace('_', '-')}"
         }
     
         /**
    @@ -87,6 +106,7 @@ class OnlineDocumentationDataSource(
                 .replace("
    ", "")
                 .replace("
    ", "") // For "code" blocks .replace("", "") + .replace("", "") // For code tags .replace("
  • ", "

  • ") .replace("

  • ", "") // For spaced bullet points diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/utils/PresetsFileProcessor.kt b/data/src/main/java/com/androidvip/sysctlgui/data/utils/PresetsFileProcessor.kt index 696b4e1..5c47959 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/utils/PresetsFileProcessor.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/utils/PresetsFileProcessor.kt @@ -3,6 +3,7 @@ package com.androidvip.sysctlgui.data.utils import android.content.ContentResolver import android.net.Uri import android.util.Log +import com.androidvip.sysctlgui.domain.exceptions.NoParameterFoundException import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.utils.isValidSysctlLine import kotlinx.coroutines.CoroutineDispatcher @@ -19,10 +20,10 @@ class PresetsFileProcessor( ): List = withContext(ioDispatcher) { contentResolver.openInputStream(uri)?.use { inputStream -> val lines = inputStream.bufferedReader().readLines() - lines.mapNotNull { line -> + val params = lines.mapNotNull { line -> if (line.isValidSysctlLine()) { runCatching { - KernelParam.Companion.createFromName( + KernelParam.createFromName( name = line.substringBefore('=').trim(), value = line.substringAfter('=').trim(), isFavorite = true @@ -33,6 +34,12 @@ class PresetsFileProcessor( null } } + + if (params.isEmpty()) { + throw NoParameterFoundException() + } + + params } ?: throw IOException("Failed to open input stream for URI: $uri") } diff --git a/data/src/test/java/com/androidvip/sysctlgui/data/ExampleUnitTest.kt b/data/src/test/java/com/androidvip/sysctlgui/data/ExampleUnitTest.kt deleted file mode 100644 index 2365b1c..0000000 --- a/data/src/test/java/com/androidvip/sysctlgui/data/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.androidvip.sysctlgui.data - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/Actions.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/enums/Actions.kt similarity index 60% rename from app/src/main/kotlin/com/androidvip/sysctlgui/helpers/Actions.kt rename to domain/src/main/java/com/androidvip/sysctlgui/domain/enums/Actions.kt index 5750be5..347b501 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/helpers/Actions.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/enums/Actions.kt @@ -1,9 +1,8 @@ -package com.androidvip.sysctlgui.helpers +package com.androidvip.sysctlgui.domain.enums enum class Actions { BrowseParams, - ListParams, ExportParams, OpenSettings, EditParam -} +} \ No newline at end of file diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/models/KernelParam.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/models/KernelParam.kt index 4128970..9759f98 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/models/KernelParam.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/models/KernelParam.kt @@ -57,6 +57,10 @@ open class KernelParam( */ fun hasValidName() = name.isKernelNameValid() + override fun toString(): String { + return "$name=$value" + } + companion object { /** @@ -74,7 +78,12 @@ open class KernelParam( ): KernelParam { require(name.isKernelNameValid()) { "Invalid name: $name" } val derivedPath = "${Consts.PROC_SYS}/${name.replace(".", "/")}" - return KernelParam(name, value, derivedPath, isFavorite) + return KernelParam( + name = name, + path = derivedPath, + value = value, + isFavorite = isFavorite + ) } /** @@ -96,7 +105,7 @@ open class KernelParam( .replace("/", ".") .removePrefix(".") - return KernelParam(derivedName, value, path) + return KernelParam(derivedName, path = path, value = value) } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f41fe46..356b3e6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,7 @@ [versions] agp = "8.12.0" +coreSplashscreen = "1.0.1" +composeMaterial = "1.8.3" libsu = "6.0.0" jsoup = "1.21.1" kotlin = "2.2.0" @@ -8,14 +10,13 @@ ksp = "2.2.0-2.0.2" coreKtx = "1.16.0" junit = "4.13.2" junitVersion = "1.3.0" -espressoCore = "3.7.0" ktor = "3.2.3" lifecycle = "2.9.2" activityCompose = "1.10.1" composeBom = "2025.07.00" appcompat = "1.7.1" -material = "1.8.3" -materialIconsCore = "1.7.8" +materialIconsCoreCompose = "1.7.8" +material = "1.12.0" navigationCompose = "2.9.3" preference = "1.2.1" room = "2.7.2" @@ -24,20 +25,22 @@ material3 = "1.5.0-alpha01" kotlinxSerializationCore = "1.9.0" koin = "4.1.0" window = "1.4.0" +workRuntimeKtx = "2.10.3" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } -androidx-material = { module = "androidx.compose.material:material", version.ref = "material" } -androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "materialIconsCore" } +androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" } +androidx-material = { module = "androidx.compose.material:material", version.ref = "composeMaterial" } +androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "materialIconsCoreCompose" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } androidx-preference = { module = "androidx.preference:preference", version.ref = "preference" } androidx-window = { module = "androidx.window:window", version.ref = "window" } +androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" } libsu-nio = { module = "com.github.topjohnwu.libsu:nio", version.ref = "libsu" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } -androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" } androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" } @@ -59,13 +62,13 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3", androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" } -kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationCore" } koin = { module = "io.insert-koin:koin-android", version.ref = "koin" } koin-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" } ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } +material = { module = "com.google.android.material:material", version.ref = "material" } [bundles] ktor-clients = ["ktor-client-core", "ktor-client-android", "ktor-client-logging"] @@ -77,5 +80,5 @@ android-library = { id = "com.android.library", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"} +jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } From 857e5881b13b69f59bd96182761a7190d2e44278 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Wed, 13 Aug 2025 23:40:03 -0300 Subject: [PATCH 06/23] bugfix: search screen v3 --- .../sysctlgui/ui/main/AppNavHost.kt | 16 ++- .../sysctlgui/ui/search/SearchScreen.kt | 133 +++++++++--------- .../sysctlgui/ui/search/SearchViewModel.kt | 38 ++--- .../sysctlgui/ui/search/SearchViewState.kt | 4 +- .../com/androidvip/sysctlgui/utils/Misc.kt | 17 +-- .../data/repository/ParamsRepositoryImpl.kt | 4 +- .../data/repository/UserRepositoryImpl.kt | 6 +- .../data/utils/PresetsFileProcessor.kt | 4 +- .../domain/repository/UserRepository.kt | 4 +- .../domain/usecase/GetUserParamsUseCase.kt | 3 +- 10 files changed, 123 insertions(+), 106 deletions(-) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt index b01c59b..aef5eb1 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt @@ -1,5 +1,9 @@ package com.androidvip.sysctlgui.ui.main +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable @@ -9,7 +13,6 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import com.androidvip.sysctlgui.core.navigation.UiRoute -import com.androidvip.sysctlgui.ui.user.UserParamsScreen import com.androidvip.sysctlgui.ui.params.browse.ParamBrowseScreen import com.androidvip.sysctlgui.ui.params.browse.ParamBrowseScreenContentPreview import com.androidvip.sysctlgui.ui.params.edit.EditParamScreen @@ -17,13 +20,18 @@ import com.androidvip.sysctlgui.ui.presets.ImportPresetScreen import com.androidvip.sysctlgui.ui.presets.PresetsScreen import com.androidvip.sysctlgui.ui.search.SearchScreen import com.androidvip.sysctlgui.ui.settings.SettingsScreen +import com.androidvip.sysctlgui.ui.user.UserParamsScreen @Composable internal fun AppNavHost(innerPadding: PaddingValues, navController: NavHostController) { NavHost( modifier = Modifier.padding(innerPadding), navController = navController, - startDestination = UiRoute.BrowseParams + startDestination = UiRoute.BrowseParams, + enterTransition = { scaleIn(initialScale = 0.9f) + fadeIn() }, + exitTransition = { scaleOut(targetScale = 0.9f) + fadeOut() }, + popEnterTransition = { scaleIn(initialScale = 1.1f) + fadeIn() }, + popExitTransition = { scaleOut(targetScale = 1.1f) + fadeOut() } ) { composable { if (LocalView.current.isInEditMode) { @@ -63,10 +71,10 @@ internal fun AppNavHost(innerPadding: PaddingValues, navController: NavHostContr composable { SearchScreen( + outerScaffoldPadding = innerPadding, onParamSelected = { navController.navigate(UiRoute.EditParam(paramName = it.name)) - }, - onNavigateBack = { navController.popBackStack() } + } ) } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt index b6ab33a..b68a311 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt @@ -1,16 +1,16 @@ package com.androidvip.sysctlgui.ui.search +import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.animation.expandHorizontally -import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkHorizontally -import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset @@ -63,8 +63,8 @@ import org.koin.compose.viewmodel.koinViewModel fun SearchScreen( viewModel: SearchViewModel = koinViewModel(), mainViewModel: MainViewModel = koinViewModel(), - onParamSelected: (UiKernelParam) -> Unit, - onNavigateBack: () -> Unit + outerScaffoldPadding: PaddingValues, + onParamSelected: (UiKernelParam) -> Unit ) { val state by viewModel.uiState.collectAsStateWithLifecycle() var searchQuery by remember { mutableStateOf("") } @@ -81,16 +81,16 @@ fun SearchScreen( ) } - LaunchedEffect(viewModel.effect) { + LaunchedEffect(Unit) { viewModel.effect.collect { effect -> when (effect) { is SearchViewEffect.EditKernelParam -> onParamSelected(effect.param) - SearchViewEffect.NavigateBack -> onNavigateBack() } } } SearchScreenContent( + outerScaffoldPadding = outerScaffoldPadding, searchQuery = searchQuery, onSearchQueryChange = { searchQuery = it @@ -98,12 +98,13 @@ fun SearchScreen( }, searchActive = searchActive, onSearchActiveChange = { searchActive = it }, - searchHints = state.searchHints, - searchResults = state.searchResults, - onNavigateBack = { viewModel.onEvent(SearchViewEvent.BackClicked) }, + state = state, onHistoryItemRemoveClicked = { viewModel.onEvent(SearchViewEvent.HistoryItemRemoveClicked(it)) }, + onParamClicked = { + viewModel.onEvent(SearchViewEvent.ParamClicked(it)) + }, onSearch = { query -> searchActive = false viewModel.onEvent(SearchViewEvent.SearchRequested(query)) @@ -114,17 +115,18 @@ fun SearchScreen( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun SearchScreenContent( + state: SearchViewState, + outerScaffoldPadding: PaddingValues = PaddingValues(), searchQuery: String, onSearchQueryChange: (String) -> Unit, searchActive: Boolean, onSearchActiveChange: (Boolean) -> Unit, - searchHints: List, - searchResults: List, onHistoryItemRemoveClicked: (SearchHint) -> Unit, - onNavigateBack: () -> Unit, - onSearch: (String) -> Unit + onParamClicked: (UiKernelParam) -> Unit, + onSearch: (String) -> Unit, ) { val focusManager = LocalFocusManager.current + val navHostTopPadding = outerScaffoldPadding.calculateTopPadding() val searchBarHorizontalPadding by animateDpAsState( targetValue = if (searchActive) 0.dp else 16.dp, label = "SearchBarHorizontalPadding", @@ -137,6 +139,9 @@ private fun SearchScreenContent( ) Scaffold( + modifier = Modifier + .fillMaxSize() + .offset(y = -navHostTopPadding), topBar = { val onActiveChange: (Boolean) -> Unit = { isActive -> if (searchActive != isActive) { @@ -157,42 +162,25 @@ private fun SearchScreenContent( onExpandedChange = onActiveChange, placeholder = { Text("Search kernel parameters") }, leadingIcon = { - AnimatedVisibility( - visible = searchActive, - enter = expandVertically() + fadeIn(), - exit = shrinkVertically() + fadeOut() - ) { - IconButton(onClick = { - focusManager.clearFocus() - onSearchActiveChange(false) - onSearchQueryChange("") - if (searchQuery.isEmpty()) { - onNavigateBack() + AnimatedContent( + targetState = searchActive, + label = "SearchIconsAnimation" + ) { targetSearchActive -> + if (targetSearchActive) { + IconButton(onClick = { + focusManager.clearFocus() + onSearchActiveChange(false) + onSearchQueryChange("") + }) { + Icon( + Icons.AutoMirrored.Rounded.ArrowBack, + contentDescription = "Back" + ) } - }) { - Icon( - Icons.AutoMirrored.Rounded.ArrowBack, - contentDescription = "Back" - ) + } else { + Icon(Icons.Rounded.Search, contentDescription = "Search Icon") } } - - - AnimatedVisibility( - visible = !searchActive, - enter = expandVertically( - animationSpec = tween(delayMillis = 200) - ) + fadeIn( - animationSpec = tween(delayMillis = 200) - ), - exit = shrinkVertically( - animationSpec = tween(durationMillis = 0) - ) + fadeOut( - animationSpec = tween(durationMillis = 0) - ) - ) { - Icon(Icons.Rounded.Search, contentDescription = "Search Icon") - } }, trailingIcon = { AnimatedVisibility( @@ -223,7 +211,7 @@ private fun SearchScreenContent( windowInsets = SearchBarDefaults.windowInsets, ) { SearchViewContent( - searchHints = searchHints, + searchHints = state.searchHints, onHistoryItemRemoveClicked = onHistoryItemRemoveClicked, onSearchQueryChange = onSearchQueryChange, onSearch = onSearch, @@ -233,12 +221,23 @@ private fun SearchScreenContent( } } ) { innerPadding -> - Box(modifier = Modifier.padding(innerPadding)) { - if (!searchActive) { - SearchResultsContent(searchResults, searchQuery) + AnimatedContent( + targetState = state.loading, + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { + if (it) { + Box(modifier = Modifier.fillMaxSize()) { + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } + } else { + SearchResultsContent( + searchResults = state.searchResults, + searchQuery = searchQuery, + onParamClicked = onParamClicked + ) } - - CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } } } @@ -268,12 +267,12 @@ private fun SearchViewContent( .padding(horizontal = 16.dp, vertical = 12.dp) ) } - items(historyHints, key = { it.hint }) { hintItem -> + items(historyHints, key = { "history:${it.hint}" }) { historyItem -> ListItem( colors = ListItemDefaults.colors( containerColor = MaterialTheme.colorScheme.surfaceContainerHigh ), - headlineContent = { Text(hintItem.hint) }, + headlineContent = { Text(historyItem.hint) }, leadingContent = { Icon( painter = painterResource(R.drawable.ic_history), @@ -282,7 +281,7 @@ private fun SearchViewContent( }, trailingContent = { IconButton( - onClick = { onHistoryItemRemoveClicked(hintItem) }, + onClick = { onHistoryItemRemoveClicked(historyItem) }, modifier = Modifier.offset(16.dp) ) { Icon( @@ -293,8 +292,8 @@ private fun SearchViewContent( }, modifier = Modifier .clickable { - onSearchQueryChange(hintItem.hint) - onSearch(hintItem.hint) + onSearchQueryChange(historyItem.hint) + onSearch(historyItem.hint) onSearchActiveChange(false) } .animateItem() @@ -317,7 +316,7 @@ private fun SearchViewContent( modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp) ) } - items(suggestionHints, key = { it.hint }) { hintItem -> + items(suggestionHints, key = { "hint:${it.hint}" }) { hintItem -> ListItem( colors = ListItemDefaults.colors( containerColor = MaterialTheme.colorScheme.surfaceContainerHigh @@ -351,7 +350,11 @@ private fun SearchViewContent( } @Composable -private fun SearchResultsContent(searchResults: List, searchQuery: String) { +private fun SearchResultsContent( + searchResults: List, + searchQuery: String, + onParamClicked: (UiKernelParam) -> Unit +) { if (searchResults.isNotEmpty()) { LazyColumn( modifier = Modifier @@ -363,7 +366,7 @@ private fun SearchResultsContent(searchResults: List, searchQuery modifier = Modifier.animateItem(), param = param, showFullName = true, - onParamClicked = {} + onParamClicked = onParamClicked ) if (index < searchResults.lastIndex) { @@ -448,15 +451,17 @@ private fun SearchScreenPreview() { onSearchQueryChange = { searchQuery = it }, searchActive = searchActive, onSearchActiveChange = { searchActive = it }, - searchHints = searchHints, - searchResults = searchResults, - onNavigateBack = {}, + state = SearchViewState( + searchHints = searchHints, + searchResults = searchResults + ), onHistoryItemRemoveClicked = { searchHints = searchHints - it }, + onParamClicked = {}, onSearch = { searchResults = searchResults.filter { it.name.contains(searchQuery, ignoreCase = true) } - } + }, ) } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewModel.kt index b024a89..9d6fccb 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewModel.kt @@ -22,19 +22,19 @@ class SearchViewModel( ) : BaseViewModel() { private var preSearchJob: Job? = null private val searchableParams = mutableListOf() - private val searchHistory = mutableSetOf() + private val searchHistory = mutableListOf() init { viewModelScope.launch { - setState { copy(loading = true) } - val fetchedHistory = appPrefs.searchHistory val fetchedParams = fetchParams() searchHistory.addAll(fetchedHistory) searchableParams.addAll(fetchedParams) - val uiSearchHints = fetchedHistory.map { SearchHint(hint = it, isFromHistory = true) } + val uiSearchHints = fetchedHistory + .map { SearchHint(hint = it, isFromHistory = true) } + .takeLast(MAX_SEARCH_HINTS) setState { copy( @@ -54,7 +54,6 @@ class SearchViewModel( override fun onEvent(event: SearchViewEvent) { when (event) { - SearchViewEvent.BackClicked -> setEffect { SearchViewEffect.NavigateBack } is SearchViewEvent.HistoryItemRemoveClicked -> handleRemoveFromHistory(event.hint) is SearchViewEvent.SearchRequested -> handleSearch(event.query) is SearchViewEvent.SearchQueryChange -> handleSearchQueryChange(event.query) @@ -82,21 +81,14 @@ class SearchViewModel( searchHistory.add(query) appPrefs.addSearchToHistory(query) - val hints = withContext(Dispatchers.IO) { - val historyHints = searchHistory - .take(MAX_SEARCH_HISTORY) - .map { SearchHint(hint = it, isFromHistory = true) } - - val paramHints = searchableParams + val searchResults = withContext(Dispatchers.IO) { + searchableParams .filter { it.name.contains(query, ignoreCase = true) } - .take(MAX_SEARCH_HINTS) - .map { SearchHint(it.name) } - - historyHints + paramHints + .map(UiKernelParamMapper::map) } setState { - copy(loading = false, searchHints = hints) + copy(loading = false, searchResults = searchResults) } } } @@ -104,13 +96,21 @@ class SearchViewModel( private fun handlePreSearch(query: String) { viewModelScope.launch { val hints = withContext(Dispatchers.IO) { - searchableParams + val historyHints = searchHistory + .toList() + .take(MAX_SEARCH_HISTORY) + .map { SearchHint(hint = it, isFromHistory = true) } + + val paramHints = searchableParams .filter { it.name.contains(query, ignoreCase = true) } - .map(UiKernelParamMapper::map) + .take(MAX_SEARCH_HINTS) + .map { SearchHint(it.name) } + + historyHints + paramHints } setState { - copy(loading = false, searchResults = hints) + copy(loading = false, searchHints = hints) } } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewState.kt index eea820a..282a131 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewState.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchViewState.kt @@ -4,13 +4,12 @@ import com.androidvip.sysctlgui.models.SearchHint import com.androidvip.sysctlgui.models.UiKernelParam data class SearchViewState( - val loading: Boolean = true, + val loading: Boolean = false, val searchHints: List = emptyList(), val searchResults: List = emptyList() ) sealed interface SearchViewEvent { - data object BackClicked : SearchViewEvent data class SearchQueryChange(val query: String) : SearchViewEvent data class HistoryItemRemoveClicked(val hint: SearchHint) : SearchViewEvent data class SearchRequested(val query: String) : SearchViewEvent @@ -18,6 +17,5 @@ sealed interface SearchViewEvent { } sealed interface SearchViewEffect { - data object NavigateBack : SearchViewEffect data class EditKernelParam(val param: UiKernelParam) : SearchViewEffect } diff --git a/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/Misc.kt b/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/Misc.kt index 806811e..ad8eace 100644 --- a/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/Misc.kt +++ b/common/utils/src/main/kotlin/com/androidvip/sysctlgui/utils/Misc.kt @@ -4,22 +4,23 @@ import android.view.View import androidx.core.view.HapticFeedbackConstantsCompat import androidx.core.view.ViewCompat + /** * Checks if a string is a valid sysctl line. * A valid sysctl line must: - * - Contain exactly one "=" character. - * - Not have blank parts before or after the "=". - * - Have a key that matches the pattern: `^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)+$` + * - Contain an "=" character. + * - Have a key that matches the pattern: `^[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*$` * (e.g., "vm.swappiness", "net.ipv4.tcp_congestion_control"). + * - Have a non-blank value after the "=". * * @return `true` if the string is a valid sysctl line, `false` otherwise. */ -fun String.isValidSysctlLine(): Boolean { - val parts = this.split("=", limit = 2) - if (parts.size != 2 || parts.any { it.isBlank() }) return false +fun String.isValidSysctlOutputLine(): Boolean { + val trimmedLine = this.trim() + val linePattern = """^([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*)\s*=\s*(.+)$""".toRegex() + val matchResult = linePattern.matchEntire(trimmedLine) - val keyPattern = "^[a-zA-Z0-9_]+(\\.[a-zA-Z0-9_]+)+$".toRegex() - return keyPattern.matches(parts.first()) + return matchResult != null } fun performHapticFeedbackForToggle(newState: Boolean, view: View) { diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/ParamsRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/ParamsRepositoryImpl.kt index 1797c0b..cc080e0 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/ParamsRepositoryImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/ParamsRepositoryImpl.kt @@ -5,7 +5,7 @@ import com.androidvip.sysctlgui.data.utils.RootUtils import com.androidvip.sysctlgui.domain.enums.CommitMode import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.domain.repository.ParamsRepository -import com.androidvip.sysctlgui.utils.isValidSysctlLine +import com.androidvip.sysctlgui.utils.isValidSysctlOutputLine import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -124,7 +124,7 @@ class ParamsRepositoryImpl( } private fun String.isValidSysctlOutput(): Boolean { - return isValidSysctlLine() && + return isValidSysctlOutputLine() && !this.contains("denied", ignoreCase = true) && !this.startsWith("sysctl") } diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt index f5ade4a..d2e3d52 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt @@ -19,9 +19,13 @@ class UserRepositoryImpl( private val paramDao: ParamDao, private val coroutineContext: CoroutineContext = Dispatchers.IO ) : UserRepository { - override val userParams: Flow> + override val userParamsFlow: Flow> get() = paramDao.getAllAsFlow() + override suspend fun getUserParams(): List = withContext(coroutineContext) { + paramDao.getAll() + } + override suspend fun getParamByName(name: String) = withContext(coroutineContext) { paramDao.getParamByName(name) } diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/utils/PresetsFileProcessor.kt b/data/src/main/java/com/androidvip/sysctlgui/data/utils/PresetsFileProcessor.kt index 5c47959..8ec44f9 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/utils/PresetsFileProcessor.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/utils/PresetsFileProcessor.kt @@ -5,7 +5,7 @@ import android.net.Uri import android.util.Log import com.androidvip.sysctlgui.domain.exceptions.NoParameterFoundException import com.androidvip.sysctlgui.domain.models.KernelParam -import com.androidvip.sysctlgui.utils.isValidSysctlLine +import com.androidvip.sysctlgui.utils.isValidSysctlOutputLine import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -21,7 +21,7 @@ class PresetsFileProcessor( contentResolver.openInputStream(uri)?.use { inputStream -> val lines = inputStream.bufferedReader().readLines() val params = lines.mapNotNull { line -> - if (line.isValidSysctlLine()) { + if (line.isValidSysctlOutputLine()) { runCatching { KernelParam.createFromName( name = line.substringBefore('=').trim(), diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/UserRepository.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/UserRepository.kt index b577cdc..953acd6 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/UserRepository.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/UserRepository.kt @@ -11,7 +11,9 @@ interface UserRepository { * Retrieves a [Flow] that emits a list of user-configurable kernel parameters. * The [Flow] will emit a new list whenever the underlying data changes. */ - val userParams: Flow> + val userParamsFlow: Flow> + + suspend fun getUserParams(): List suspend fun getParamByName(name: String): KernelParam? diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetUserParamsUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetUserParamsUseCase.kt index 529fdca..cefe97c 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetUserParamsUseCase.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/GetUserParamsUseCase.kt @@ -1,8 +1,7 @@ package com.androidvip.sysctlgui.domain.usecase import com.androidvip.sysctlgui.domain.repository.UserRepository -import kotlinx.coroutines.flow.single class GetUserParamsUseCase(private val repository: UserRepository) { - suspend operator fun invoke() = repository.userParams.single() + suspend operator fun invoke() = repository.getUserParams() } From 6b872b3ad5394841842b619a2b5092dc531b94b2 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Thu, 14 Aug 2025 20:58:56 -0300 Subject: [PATCH 07/23] bugfix: browse screen back button v3 --- .../sysctlgui/ui/main/MainScreen.kt | 7 ++- .../ui/params/browse/ParamBrowseScreen.kt | 43 +++++++++++++++++-- .../ui/params/browse/ParamBrowseState.kt | 2 + .../ui/params/browse/ParamBrowseViewModel.kt | 17 +++++++- 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt index 079de14..f180312 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt @@ -1,5 +1,6 @@ package com.androidvip.sysctlgui.ui.main +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn @@ -52,6 +53,8 @@ private fun MainScreenContent( navController: NavHostController, snackbarHostState: SnackbarHostState ) { + val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current + Scaffold( topBar = { AnimatedVisibility( @@ -65,7 +68,9 @@ private fun MainScreenContent( showSearch = state.showSearchAction, showBack = state.showBackButton, onSearchPressed = { navController.navigate(UiRoute.Search) }, - onBackPressed = { navController.popBackStack() } + onBackPressed = { + onBackPressedDispatcherOwner?.onBackPressedDispatcher?.onBackPressed() + } ) } }, diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt index 5ff042e..ddff009 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt @@ -20,6 +20,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -31,6 +35,7 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment @@ -57,13 +62,16 @@ import com.androidvip.sysctlgui.ui.main.MainViewModel import com.androidvip.sysctlgui.ui.main.MainViewState import com.androidvip.sysctlgui.ui.params.DocumentationBottomSheet import com.androidvip.sysctlgui.utils.browse +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import org.koin.compose.viewmodel.koinViewModel import java.io.File +import kotlin.time.Duration.Companion.seconds -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @Composable fun ParamBrowseScreen( mainViewModel: MainViewModel = koinViewModel(), @@ -115,7 +123,9 @@ fun ParamBrowseScreen( backEnabled = state.backEnabled, onBackPressed = { viewModel.onEvent(ParamBrowseViewEvent.BackRequested) - } + }, + isRefreshing = state.loading, + onRefresh = { viewModel.onEvent(ParamBrowseViewEvent.RefreshRequested) } ) documentation?.let { @@ -126,6 +136,8 @@ fun ParamBrowseScreen( } } + +@OptIn(ExperimentalMaterialApi::class) @Composable private fun ParamBrowseScreenContent( params: List, @@ -135,8 +147,11 @@ private fun ParamBrowseScreenContent( onDocumentationClicked: (ParamDocumentation) -> Unit, backEnabled: Boolean = false, onBackPressed: () -> Unit, + isRefreshing: Boolean, + onRefresh: () -> Unit ) { val listState = rememberLazyListState() + val pullRefreshState = rememberPullRefreshState(refreshing = isRefreshing, onRefresh = onRefresh) var headerVisible by remember { mutableStateOf(backEnabled) } BackHandler(enabled = backEnabled, onBack = onBackPressed) @@ -173,7 +188,11 @@ private fun ParamBrowseScreenContent( val finalHeaderVisible = (headerVisible || isAtTop) && backEnabled - Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState), + ) { val spacerHeight by animateDpAsState(if (finalHeaderVisible) 56.dp else 0.dp) LazyColumn( state = listState, @@ -226,6 +245,14 @@ private fun ParamBrowseScreenContent( onClicked = { onDocumentationClicked(documentation!!) } ) } + + PullRefreshIndicator( + refreshing = isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + backgroundColor = MaterialTheme.colorScheme.surfaceContainerHighest, + contentColor = MaterialTheme.colorScheme.tertiary + ) } } @@ -287,6 +314,8 @@ internal fun ParamBrowseScreenContentPreview() { var params by remember(currentPath) { mutableStateOf(mapFilesToParams(File(currentPath).listFiles())) } + var isRefreshingPreview by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() SysctlGuiTheme(dynamicColor = true) { Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { @@ -307,6 +336,14 @@ internal fun ParamBrowseScreenContentPreview() { onDocumentationClicked = {}, backEnabled = currentPath != root.path, onBackPressed = { currentPath = File(currentPath).parent ?: root.path }, + isRefreshing = isRefreshingPreview, + onRefresh = { + scope.launch { + isRefreshingPreview = true + delay(2.seconds) + isRefreshingPreview = false + } + } ) } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseState.kt index a141586..ec5dd8d 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseState.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseState.kt @@ -4,6 +4,7 @@ import com.androidvip.sysctlgui.domain.models.ParamDocumentation import com.androidvip.sysctlgui.models.UiKernelParam data class ParamBrowseState( + val loading: Boolean = false, val params: List = emptyList(), val currentPath: String = "", val backEnabled: Boolean = false, @@ -20,4 +21,5 @@ sealed interface ParamBrowseViewEvent { data class ParamClicked(val param: UiKernelParam) : ParamBrowseViewEvent data class DocumentationClicked(val docs: ParamDocumentation) : ParamBrowseViewEvent object BackRequested : ParamBrowseViewEvent + object RefreshRequested : ParamBrowseViewEvent } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt index f3b49d9..b2596c0 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt @@ -27,13 +27,15 @@ class ParamBrowseViewModel( init { viewModelScope.launch { + setState { copy(loading = true) } val startingDirectory = File(Consts.PROC_SYS) val params = getParams(startingDirectory) setState { copy( params = params, - currentPath = startingDirectory.absolutePath + currentPath = startingDirectory.absolutePath, + loading = false ) } } @@ -47,11 +49,20 @@ class ParamBrowseViewModel( ParamBrowseViewEvent.BackRequested -> onBackRequested() is ParamBrowseViewEvent.ParamClicked -> onParamClicked(event.param) + ParamBrowseViewEvent.RefreshRequested -> handleRefreshRequested() } } + private fun handleRefreshRequested() { + val currentPath = currentState.currentPath + if (currentPath == Consts.PROC_SYS) return + + fetchChildParams(currentPath) + } + private fun fetchChildParams(parentParam: UiKernelParam) { viewModelScope.launch { + setState { copy(loading = true) } runCatching { val newParamsDeferred = async { getParams(File(parentParam.path)) } val directoryDocumentationDeferred = async { getParamDocumentation(parentParam) } @@ -64,11 +75,13 @@ class ParamBrowseViewModel( params = newParams, currentPath = parentParam.path, backEnabled = parentParam.path != Consts.PROC_SYS, - documentation = directoryDocumentation + documentation = directoryDocumentation, + loading = false ) } }.onFailure { setEffect { ParamBrowseViewEffect.ShowError(it.message ?: "Unknown error") } + setState { copy(loading = false) } } } } From 5509aea438d0e920123974f293265716a93d6014 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Thu, 14 Aug 2025 20:59:38 -0300 Subject: [PATCH 08/23] bugfix: main viewModel instance --- .../androidvip/sysctlgui/di/PresentationModule.kt | 3 ++- .../androidvip/sysctlgui/ui/main/MainActivity.kt | 15 ++++++++++++++- .../androidvip/sysctlgui/ui/main/MainNavBar.kt | 9 ++++++++- .../sysctlgui/ui/settings/SettingsScreen.kt | 1 + .../sysctlgui/ui/start/StartErrorActivity.kt | 3 +++ 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/di/PresentationModule.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/di/PresentationModule.kt index 87aac32..2b8a58e 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/di/PresentationModule.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/di/PresentationModule.kt @@ -9,11 +9,12 @@ import com.androidvip.sysctlgui.ui.settings.SettingsViewModel import com.androidvip.sysctlgui.ui.user.UserParamsViewModel import com.androidvip.sysctlgui.widgets.FavoriteWidgetParamUpdater import org.koin.android.ext.koin.androidContext +import org.koin.core.module.dsl.singleOf import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module internal val presentationModule = module { - viewModelOf(::MainViewModel) + singleOf(::MainViewModel) viewModelOf(::SettingsViewModel) viewModelOf(::ParamBrowseViewModel) viewModelOf(::EditParamViewModel) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt index d00fece..9101eae 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt @@ -1,10 +1,12 @@ package com.androidvip.sysctlgui.ui.main import android.app.NotificationManager +import android.graphics.Color import android.os.Build import android.os.Bundle import android.os.Handler import androidx.activity.ComponentActivity +import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts @@ -21,7 +23,18 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - enableEdgeToEdge() + enableEdgeToEdge( + statusBarStyle = SystemBarStyle.auto( + lightScrim = Color.TRANSPARENT, + darkScrim = Color.TRANSPARENT, + detectDarkMode = { resources -> + prefs.forceDark || + resources.configuration.uiMode and + android.content.res.Configuration.UI_MODE_NIGHT_MASK == + android.content.res.Configuration.UI_MODE_NIGHT_YES + } + ) + ) setContent { SysctlGuiTheme( diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt index 8e42edb..588bb03 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewDynamicColors import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.navigation.NavDestination.Companion.hasRoute @@ -81,7 +82,13 @@ internal fun MainNavBar(navController: NavHostController = rememberNavController contentDescription = route.name, ) }, - label = { Text(route.name) }, + label = { + Text( + text = route.name, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, selected = selected, onClick = { navController.navigate(route.route) { diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt index 534a37e..3a26724 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt @@ -77,6 +77,7 @@ internal fun SettingsScreen( MainViewState( showTopBar = true, showNavBar = true, + showSearchAction = false ) ) ) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartErrorActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartErrorActivity.kt index 5602352..0909855 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartErrorActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartErrorActivity.kt @@ -4,6 +4,7 @@ import android.content.Intent import android.graphics.drawable.AnimatedVectorDrawable import android.os.Bundle import android.os.Handler +import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import com.androidvip.sysctlgui.R @@ -14,6 +15,8 @@ class StartErrorActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() + binding = ActivityStartErrorBinding.inflate(layoutInflater) setContentView(binding.root) From fb6914130a6cd70d573b783d50f9adfe4d2b4236 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Thu, 14 Aug 2025 22:30:08 -0300 Subject: [PATCH 09/23] bugfix: edit param screen --- .../sysctlgui/ui/main/MainScreen.kt | 4 +- .../ui/params/browse/ParamBrowseViewModel.kt | 4 +- .../ui/params/edit/EditParamScreen.kt | 149 ++++++++++-------- .../ui/params/edit/EditParamViewModel.kt | 21 ++- .../repository/DocumentationRepositoryImpl.kt | 10 +- .../data/repository/UserRepositoryImpl.kt | 6 +- 6 files changed, 119 insertions(+), 75 deletions(-) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt index f180312..60d7a87 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable @@ -37,7 +38,8 @@ fun MainScreen(viewModel: MainViewModel = koinViewModel()) { if (effect is MainViewEffect.ShowSnackbar) { val result = snackbarHostState.showSnackbar( message = effect.message, - actionLabel = effect.actionLabel + actionLabel = effect.actionLabel, + duration = SnackbarDuration.Long ) viewModel.onEvent(MainViewEvent.OnSnackbarResult(result)) } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt index b2596c0..1a36e1a 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt @@ -65,7 +65,9 @@ class ParamBrowseViewModel( setState { copy(loading = true) } runCatching { val newParamsDeferred = async { getParams(File(parentParam.path)) } - val directoryDocumentationDeferred = async { getParamDocumentation(parentParam) } + val directoryDocumentationDeferred = async { + runCatching { getParamDocumentation(parentParam) }.getOrNull() + } val newParams = newParamsDeferred.await() val directoryDocumentation = directoryDocumentationDeferred.await() diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt index c9ee615..f28758f 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt @@ -2,6 +2,7 @@ package com.androidvip.sysctlgui.ui.params.edit import android.content.ClipData import android.widget.Toast +import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.SizeTransform @@ -12,6 +13,7 @@ import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -97,7 +99,7 @@ fun EditParamScreen( val context = LocalContext.current val state = viewModel.uiState.collectAsStateWithLifecycle() val taskerListOptions = listOf("Primary", "Secondary") - var showSelectTaskerListDialog by rememberSaveable { mutableStateOf(true) } + var showSelectTaskerListDialog by rememberSaveable { mutableStateOf(false) } var selectedOptionIndex by rememberSaveable { mutableIntStateOf(Consts.LIST_NUMBER_PRIMARY_TASKER) } @@ -156,6 +158,7 @@ fun EditParamScreen( viewModel.onEvent(EditParamViewEvent.ApplyPressed(it)) }, onTaskerClicked = { + showSelectTaskerListDialog = it viewModel.onEvent(EditParamViewEvent.TaskerTogglePressed(it, selectedOptionIndex)) }, onDocsReadMorePressed = { @@ -229,6 +232,8 @@ private fun EditParamContent( modifier = Modifier .combinedClickable( enabled = true, + indication = null, + interactionSource = remember { MutableInteractionSource() }, onClick = { Toast.makeText(context, "Long press to copy", Toast.LENGTH_SHORT).show() }, @@ -287,7 +292,7 @@ private fun EditParamContent( ) } - if (param.isTaskerParam && state.taskerAvailable) { + AnimatedVisibility(visible = param.isTaskerParam && state.taskerAvailable) { val listName = taskerListNameResolver(param.taskerList) AssistChip( onClick = { onTaskerClicked(true) }, @@ -337,6 +342,11 @@ private fun ParamValueContent( var editedValue by remember(param.value) { mutableStateOf(param.value) } val view = LocalView.current + BackHandler( + enabled = isEditing, + onBack = { isEditing = false } + ) + HorizontalDivider() Row( @@ -454,75 +464,90 @@ private fun ParamDocs( color = MaterialTheme.colorScheme.onBackground ) - if (documentation != null) { - val documentationText = if (!documentation.documentationHtml.isNullOrEmpty()) { - AnnotatedString.fromHtml( - htmlString = documentation.documentationHtml.orEmpty(), - linkStyles = TextLinkStyles( - style = MaterialTheme.typography.bodyMedium.toSpanStyle().copy( - color = MaterialTheme.colorScheme.primary, - textDecoration = TextDecoration.Underline, - fontWeight = FontWeight.Medium - ), - pressedStyle = MaterialTheme.typography.bodyMedium.toSpanStyle().copy( - color = MaterialTheme.colorScheme.tertiary, - textDecoration = TextDecoration.Underline, - fontWeight = FontWeight.Medium - ) - ) + AnimatedContent(targetState = documentation != null) { documentationAvailable -> + if (documentationAvailable && documentation != null) { + DocumentationContent( + documentation = documentation, + onReadMorePressed = onReadMorePressed ) } else { - AnnotatedString(documentation.documentationText) - } - - SelectionContainer { - Text( + Card( modifier = Modifier .fillMaxWidth() - .padding(vertical = 8.dp), - text = documentationText, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) + .padding(24.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.errorContainer + ) + ) { + Row( + modifier = Modifier.padding(24.dp), + horizontalArrangement = Arrangement.spacedBy( + 16.dp, + Alignment.CenterHorizontally + ) + ) { + Icon( + imageVector = Icons.Rounded.Warning, + contentDescription = stringResource(android.R.string.dialog_alert_title), + tint = MaterialTheme.colorScheme.onErrorContainer + ) + Text( + text = "No documentation available", + style = MaterialTheme.typography.bodyLarge.copy(), + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onErrorContainer + ) + } + } } + } + } +} - TextButton( - onClick = onReadMorePressed, - modifier = Modifier - .padding(vertical = 8.dp) - .align(Alignment.End) - ) { - Text(text = "Read more") - } +@Composable +private fun DocumentationContent( + documentation: ParamDocumentation, + onReadMorePressed: () -> Unit +) { + Column { + val documentationText = if (!documentation.documentationHtml.isNullOrEmpty()) { + AnnotatedString.fromHtml( + htmlString = documentation.documentationHtml.orEmpty(), + linkStyles = TextLinkStyles( + style = MaterialTheme.typography.bodyMedium.toSpanStyle().copy( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline, + fontWeight = FontWeight.Medium + ), + pressedStyle = MaterialTheme.typography.bodyMedium.toSpanStyle().copy( + color = MaterialTheme.colorScheme.tertiary, + textDecoration = TextDecoration.Underline, + fontWeight = FontWeight.Medium + ) + ) + ) } else { - Card( + AnnotatedString(documentation.documentationText) + } + + SelectionContainer { + Text( modifier = Modifier .fillMaxWidth() - .padding(24.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.errorContainer - ) - ) { - Row( - modifier = Modifier.padding(24.dp), - horizontalArrangement = Arrangement.spacedBy( - 16.dp, - Alignment.CenterHorizontally - ) - ) { - Icon( - imageVector = Icons.Rounded.Warning, - contentDescription = stringResource(android.R.string.dialog_alert_title), - tint = MaterialTheme.colorScheme.onErrorContainer - ) - Text( - text = "No documentation available", - style = MaterialTheme.typography.bodyLarge.copy(), - fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.onErrorContainer - ) - } - } + .padding(vertical = 8.dp), + text = documentationText, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + + TextButton( + onClick = onReadMorePressed, + modifier = Modifier + .padding(vertical = 8.dp) + .align(Alignment.End) + ) { + Text(text = "Read more") } } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt index d8cb6ea..915f9b1 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt @@ -12,7 +12,6 @@ import com.androidvip.sysctlgui.domain.usecase.GetUserParamByNameUseCase import com.androidvip.sysctlgui.domain.usecase.IsTaskerInstalledUseCase import com.androidvip.sysctlgui.domain.usecase.UpsertUserParamUseCase import com.androidvip.sysctlgui.helpers.UiKernelParamMapper -import com.androidvip.sysctlgui.models.UiKernelParam import com.androidvip.sysctlgui.utils.BaseViewModel import kotlinx.coroutines.launch @@ -37,16 +36,18 @@ class EditParamViewModel( ?: getRuntimeParam(paramName) ?: return@launch setEffect { EditParamViewEffect.GoBack } - val documentation = getDocumentation(param) - setState { copy( kernelParam = UiKernelParamMapper.map(param), taskerAvailable = isTaskerInstalled(), - keyboardType = guessKeyboardType(param.value), - documentation = documentation, + keyboardType = guessKeyboardType(param.value) ) } + + val documentation = runCatching { getDocumentation(param) }.getOrNull() + setState { + copy(documentation = documentation) + } } } @@ -56,7 +57,7 @@ class EditParamViewModel( when (event) { is EditParamViewEvent.ApplyPressed -> applyKernelParam(event.newValue) is EditParamViewEvent.UndoRequested -> { - previousKernelParamValue?.let { applyKernelParam(it) } + previousKernelParamValue?.let { applyKernelParam(it, true) } } is EditParamViewEvent.DocumentationReadMoreClicked -> onDocumentationReadMoreClicked() is EditParamViewEvent.FavoriteTogglePressed -> onFavoriteTogglePressed(event.newState) @@ -64,7 +65,7 @@ class EditParamViewModel( } } - private fun applyKernelParam(newValue: String) { + private fun applyKernelParam(newValue: String, isUndo: Boolean = false) { val oldParam = currentState.kernelParam viewModelScope.launch { val newParam = oldParam.copy(value = newValue) @@ -73,7 +74,11 @@ class EditParamViewModel( upsertUserParam(newParam) }.onSuccess { setState { copy(kernelParam = newParam) } - setEffect { EditParamViewEffect.ShowApplySuccess(oldParam.value) } + if (!isUndo) { + setEffect { + EditParamViewEffect.ShowApplySuccess(previousValue = oldParam.value) + } + } previousKernelParamValue = oldParam.value }.onFailure { Log.e("EditParamViewModel", "Failed to apply param", it) diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt index 09ae565..b5f272b 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt @@ -5,6 +5,7 @@ import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.domain.models.ParamDocumentation import com.androidvip.sysctlgui.domain.repository.AppPrefs import com.androidvip.sysctlgui.domain.repository.DocumentationRepository +import kotlinx.coroutines.withTimeout /** * Repository for fetching documentation for kernel parameters. @@ -14,7 +15,6 @@ import com.androidvip.sysctlgui.domain.repository.DocumentationRepository * * @property offlineDataSource The data source for fetching documentation offline. * @property onlineDataSource The data source for fetching documentation online. - * @property appPrefs The application preferences, used to determine whether to use online or * offline documentation. */ class DocumentationRepositoryImpl( @@ -26,9 +26,15 @@ class DocumentationRepositoryImpl( online: Boolean ): ParamDocumentation? { return if (online) { - onlineDataSource.getDocumentation(param) + withTimeout(REQUEST_TIMEOUT_MS) { + onlineDataSource.getDocumentation(param) + } } else { offlineDataSource.getDocumentation(param) } } + + companion object { + private const val REQUEST_TIMEOUT_MS = 3000L + } } diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt index d2e3d52..e6fbcc4 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt @@ -31,7 +31,11 @@ class UserRepositoryImpl( } override suspend fun upsertUserParam(param: KernelParam) = withContext(coroutineContext) { - paramDao.upsert(KernelParamDTO.fromKernelParam(param)) + val currentDatabaseParam = getParamByName(param.name) + val newParam = KernelParamDTO.fromKernelParam(param).copy( + id = currentDatabaseParam?.id ?: 0 + ) + paramDao.upsert(newParam) } override suspend fun upsertUserParams(params: List) = From 076aca2f47d65377f913e7e026a691c3c3044857 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Thu, 14 Aug 2025 22:39:02 -0300 Subject: [PATCH 10/23] bugfix: favorites screen v3 --- .../kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt | 2 +- .../com/androidvip/sysctlgui/ui/user/UserParamsViewModel.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt index b68a311..08788dc 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt @@ -75,7 +75,7 @@ fun SearchScreen( MainViewEvent.OnSateChangeRequested( MainViewState( showTopBar = false, - showNavBar = true, + showNavBar = false, ) ) ) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsViewModel.kt index cff23a5..90ed643 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsViewModel.kt @@ -36,10 +36,11 @@ class UserParamsViewModel( private fun loadParams(predicate: (UiKernelParam) -> Boolean) { viewModelScope.launch { + setState { copy(loading = true) } val params = getUserParams() .map(UiKernelParamMapper::map) .filter(predicate) - setState { copy(userParams = params) } + setState { copy(userParams = params, loading = false) } } } From 628c9c91e86965d92858d6c646f07ed891ae4a34 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Thu, 14 Aug 2025 23:13:27 -0300 Subject: [PATCH 11/23] bugfix: import presets --- .../sysctlgui/di/PresentationModule.kt | 2 +- .../sysctlgui/ui/main/MainActivity.kt | 4 ++++ .../sysctlgui/ui/main/MainNavBar.kt | 23 ++++++++++++------- .../sysctlgui/ui/presets/PresetsScreen.kt | 2 +- .../data/repository/UserRepositoryImpl.kt | 15 +++++++++--- .../domain/usecase/ApplyParamUseCase.kt | 2 +- 6 files changed, 34 insertions(+), 14 deletions(-) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/di/PresentationModule.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/di/PresentationModule.kt index 2b8a58e..1ab7173 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/di/PresentationModule.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/di/PresentationModule.kt @@ -19,7 +19,7 @@ internal val presentationModule = module { viewModelOf(::ParamBrowseViewModel) viewModelOf(::EditParamViewModel) viewModelOf(::SearchViewModel) - viewModelOf(::PresetsViewModel) + singleOf(::PresetsViewModel) viewModelOf(::UserParamsViewModel) single { FavoriteWidgetParamUpdater(androidContext()).getListener() } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt index 9101eae..6e6c0b9 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt @@ -37,6 +37,10 @@ class MainActivity : ComponentActivity() { ) setContent { + // TODO: Make the switch dynamic with the view model + // TODO: Test presets + // TODO: Translations + // TODO: Handle shortcuts / widgets SysctlGuiTheme( darkTheme = prefs.forceDark || isSystemInDarkTheme(), contrastLevel = prefs.contrastLevel, diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt index 588bb03..b493c5b 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt @@ -1,13 +1,14 @@ package com.androidvip.sysctlgui.ui.main +import androidx.compose.animation.AnimatedContent import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Favorite -import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.outlined.Build import androidx.compose.material.icons.outlined.FavoriteBorder import androidx.compose.material.icons.outlined.Home import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.rounded.Build +import androidx.compose.material.icons.rounded.Favorite +import androidx.compose.material.icons.rounded.Home import androidx.compose.material.icons.rounded.Settings import androidx.compose.material3.Icon import androidx.compose.material3.NavigationBar @@ -42,7 +43,7 @@ internal fun MainNavBar(navController: NavHostController = rememberNavController TopLevelRoute( name = browseParamsTitle, route = UiRoute.BrowseParams, - selectedIcon = Icons.Filled.Home, + selectedIcon = Icons.Rounded.Home, unselectedIcon = Icons.Outlined.Home ), TopLevelRoute( @@ -54,7 +55,7 @@ internal fun MainNavBar(navController: NavHostController = rememberNavController TopLevelRoute( name = favoritesTitle, route = UiRoute.Favorites, - selectedIcon = Icons.Filled.Favorite, + selectedIcon = Icons.Rounded.Favorite, unselectedIcon = Icons.Outlined.FavoriteBorder ), TopLevelRoute( @@ -77,10 +78,16 @@ internal fun MainNavBar(navController: NavHostController = rememberNavController NavigationBarItem( icon = { - Icon( - imageVector = if (selected) route.selectedIcon else route.unselectedIcon, - contentDescription = route.name, - ) + AnimatedContent(targetState = selected) { selectedState -> + Icon( + imageVector = if (selectedState) { + route.selectedIcon + } else { + route.unselectedIcon + }, + contentDescription = route.name, + ) + } }, label = { Text( diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt index 3b3b98a..eaa63ce 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt @@ -63,7 +63,7 @@ fun PresetsScreen( } ) val createFileLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.CreateDocument("text/plain"), + contract = ActivityResultContracts.CreateDocument("*/*"), onResult = { uri -> viewModel.onEvent(PresetsViewEvent.BackUpFileCreated(uri)) } diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt index e6fbcc4..a75d123 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/UserRepositoryImpl.kt @@ -38,11 +38,20 @@ class UserRepositoryImpl( paramDao.upsert(newParam) } - override suspend fun upsertUserParams(params: List) = - withContext(coroutineContext) { - paramDao.upsertAll(params.map { KernelParamDTO.fromKernelParam(it) }) + override suspend fun upsertUserParams( + params: List + ) = withContext(coroutineContext) { + val currentDatabaseParams = paramDao.getAll() + val newParams = params.map { param -> + val currentDatabaseParam = currentDatabaseParams.find { it.name == param.name } + KernelParamDTO.fromKernelParam(param).copy( + id = currentDatabaseParam?.id ?: 0 + ) } + paramDao.upsertAll(newParams) + } + override suspend fun removeUserParam(param: KernelParam) = withContext(coroutineContext) { paramDao.delete(KernelParamDTO.fromKernelParam(param)) } diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ApplyParamUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ApplyParamUseCase.kt index 1001864..afa5561 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ApplyParamUseCase.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/ApplyParamUseCase.kt @@ -71,6 +71,6 @@ class ApplyParamUseCase( CommitMode.SYSCTL -> "Failed to execute sysctl command for '${param.name}'" CommitMode.ECHO -> "Failed to write value '${param.value}' to '${param.path}'" } - throw ApplyValueException(errorMessage) + throw ApplyValueException(errorMessage + message) } } From 996e543ba92f05bdf1838e3454757aa02cb4f432 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Fri, 15 Aug 2025 23:22:10 -0300 Subject: [PATCH 12/23] bugfix: fixed favorites widget --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 8 +- .../sysctlgui/di/PresentationModule.kt | 4 +- .../sysctlgui/ui/main/AppNavHost.kt | 8 +- .../sysctlgui/ui/main/MainActivity.kt | 34 +++--- .../sysctlgui/ui/main/MainScreen.kt | 13 ++- .../ui/params/edit/EditParamViewModel.kt | 3 + .../sysctlgui/ui/start/StartActivity.kt | 39 +------ .../widgets/FavoriteWidgetParamUpdater.kt | 30 ----- .../widgets/FavoritesGlanceWidget.kt | 108 +++++++++++++++++ .../widgets/FavoritesGlanceWidgetReceiver.kt | 8 ++ .../sysctlgui/widgets/FavoritesWidget.kt | 109 ------------------ .../widgets/FavoritesWidgetService.kt | 82 ------------- .../widgets/UpdateFavoriteWidgetUseCase.kt | 14 +++ .../widgets/ViewKernelParamDetailsAction.kt | 29 +++++ app/src/main/res/layout/favorites_widget.xml | 3 +- ...o.xml => favorites_glance_widget_info.xml} | 10 +- .../domain/usecase/UpsertUserParamUseCase.kt | 4 +- gradle/libs.versions.toml | 2 + 19 files changed, 216 insertions(+), 293 deletions(-) delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoriteWidgetParamUpdater.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidget.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidgetReceiver.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidget.kt delete mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidgetService.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/widgets/UpdateFavoriteWidgetUseCase.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/widgets/ViewKernelParamDetailsAction.kt rename app/src/main/res/xml/{favorites_widget_info.xml => favorites_glance_widget_info.xml} (53%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1643914..cbf9b2a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -111,6 +111,7 @@ dependencies { implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.appcompat) implementation(libs.androidx.material) + implementation(libs.androidx.glance.appwidget) implementation(libs.androidx.navigation.compose) implementation(libs.androidx.window) implementation(libs.androidx.work.runtime.ktx) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dad076a..9282f21 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -58,15 +58,14 @@ - + android:resource="@xml/favorites_glance_widget_info" /> - R.id.navigationBrowse - Actions.ListParams.name -> R.id.navigationList - Actions.ExportParams.name -> R.id.navigationExport - Actions.OpenSettings.name -> R.id.navigationSettings - else -> null - }?.let { id -> - navHost.navController.navigate(id) - }*/ + private fun getRouteFromIntent(): UiRoute { + val extraDestination = intent.getStringExtra(EXTRA_DESTINATION) + ?: return UiRoute.BrowseParams + + val extraParamName = intent.getStringExtra(EXTRA_PARAM_NAME) + + return when (extraDestination) { + Actions.BrowseParams.name -> UiRoute.BrowseParams + Actions.ExportParams.name -> UiRoute.Presets + Actions.OpenSettings.name -> UiRoute.Settings + Actions.EditParam.name -> UiRoute.EditParam(extraParamName.orEmpty()) + else -> UiRoute.BrowseParams + } } private fun checkNotificationPermission() { @@ -83,5 +82,6 @@ class MainActivity : ComponentActivity() { companion object { internal const val EXTRA_DESTINATION = "destination" + internal const val EXTRA_PARAM_NAME = "paramName" } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt index 60d7a87..7005d0a 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt @@ -28,7 +28,10 @@ import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun MainScreen(viewModel: MainViewModel = koinViewModel()) { +fun MainScreen( + viewModel: MainViewModel = koinViewModel(), + startDestination: UiRoute = UiRoute.BrowseParams +) { val state by viewModel.uiState.collectAsStateWithLifecycle() val navController = rememberNavController() val snackbarHostState = remember { SnackbarHostState() } @@ -46,14 +49,15 @@ fun MainScreen(viewModel: MainViewModel = koinViewModel()) { } } - MainScreenContent(state, navController, snackbarHostState) + MainScreenContent(state, navController, snackbarHostState, startDestination) } @Composable private fun MainScreenContent( state: MainViewState, navController: NavHostController, - snackbarHostState: SnackbarHostState + snackbarHostState: SnackbarHostState, + startDestination: UiRoute = UiRoute.BrowseParams ) { val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current @@ -92,7 +96,8 @@ private fun MainScreenContent( content = { innerPadding -> AppNavHost( innerPadding = innerPadding, - navController = navController + navController = navController, + startDestination = startDestination ) } ) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt index 915f9b1..05b4529 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt @@ -13,6 +13,7 @@ import com.androidvip.sysctlgui.domain.usecase.IsTaskerInstalledUseCase import com.androidvip.sysctlgui.domain.usecase.UpsertUserParamUseCase import com.androidvip.sysctlgui.helpers.UiKernelParamMapper import com.androidvip.sysctlgui.utils.BaseViewModel +import com.androidvip.sysctlgui.widgets.UpdateFavoriteWidgetUseCase import kotlinx.coroutines.launch class EditParamViewModel( @@ -23,6 +24,7 @@ class EditParamViewModel( private val getRuntimeParam: GetRuntimeParamUseCase, private val getUserParam: GetUserParamByNameUseCase, private val isTaskerInstalled: IsTaskerInstalledUseCase, + private val updateFavoriteWidget: UpdateFavoriteWidgetUseCase, private val appPrefs: AppPrefs ) : BaseViewModel() { private val paramName: String? = savedStateHandle.get(PARAM_NAME_KEY) @@ -80,6 +82,7 @@ class EditParamViewModel( } } previousKernelParamValue = oldParam.value + updateFavoriteWidget() }.onFailure { Log.e("EditParamViewModel", "Failed to apply param", it) setEffect { diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartActivity.kt index 5fe650c..5356d7f 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartActivity.kt @@ -73,40 +73,11 @@ class StartActivity : AppCompatActivity() { } private fun navigate() { - val shortcutNames = arrayOf( - Actions.BrowseParams.name, - Actions.ExportParams.name, - Actions.OpenSettings.name - ) - val nextIntent = when (intent.action) { - in shortcutNames -> { - Intent(this, MainActivity::class.java).apply { - putExtra(MainActivity.EXTRA_DESTINATION, intent.action) - } - } - - Actions.EditParam.name -> { - Intent(this, MainActivity::class.java) - // TODO: handle edit param intent - /*Intent(this, EditKernelParamActivity::class.java).apply { - putExtra( - EditKernelParamActivity.EXTRA_PARAM, - intent.extras!!.getParcelable( - EditKernelParamActivity.EXTRA_PARAM - ) - ) - putExtra( - EditKernelParamActivity.EXTRA_EDIT_SAVED_PARAM, - intent.getBooleanExtra( - EditKernelParamActivity.EXTRA_EDIT_SAVED_PARAM, - false - ) - ) - }*/ - } - - else -> { - Intent(this, MainActivity::class.java) + val shortcutNames = Actions.entries.map { it.name } + val nextIntent = Intent(this, MainActivity::class.java).apply { + if (intent.action in shortcutNames) { + putExtra(MainActivity.EXTRA_DESTINATION, intent.action) + putExtras(intent.extras ?: Bundle()) } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoriteWidgetParamUpdater.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoriteWidgetParamUpdater.kt deleted file mode 100644 index c2a67f0..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoriteWidgetParamUpdater.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.androidvip.sysctlgui.widgets - -import android.app.PendingIntent -import android.appwidget.AppWidgetManager -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import com.androidvip.sysctlgui.data.repository.ParamsRepositoryImpl - -class FavoriteWidgetParamUpdater(private val context: Context) : - ParamsRepositoryImpl.ChangeListener { - override fun onChange() { - val intentUpdate = Intent(context, FavoritesWidget::class.java) - intentUpdate.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE - val idArray = AppWidgetManager.getInstance(context).getAppWidgetIds( - ComponentName(context, FavoritesWidget::class.java) - ) - - if (idArray.isEmpty()) return - - val flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - - idArray.forEach { - intentUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(it)) - PendingIntent.getBroadcast(context, it, intentUpdate, flags).send() - } - } - - fun getListener(): ParamsRepositoryImpl.ChangeListener = this -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidget.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidget.kt new file mode 100644 index 0000000..01529f6 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidget.kt @@ -0,0 +1,108 @@ +package com.androidvip.sysctlgui.widgets + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.glance.GlanceId +import androidx.glance.GlanceModifier +import androidx.glance.action.actionParametersOf +import androidx.glance.action.clickable +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.action.actionRunCallback +import androidx.glance.appwidget.lazy.LazyColumn +import androidx.glance.appwidget.lazy.items +import androidx.glance.appwidget.provideContent +import androidx.glance.background +import androidx.glance.layout.Alignment +import androidx.glance.layout.Column +import androidx.glance.layout.Spacer +import androidx.glance.layout.fillMaxSize +import androidx.glance.layout.fillMaxWidth +import androidx.glance.layout.height +import androidx.glance.layout.padding +import androidx.glance.text.FontWeight +import androidx.glance.text.Text +import androidx.glance.text.TextStyle +import androidx.glance.unit.ColorProvider +import com.androidvip.sysctlgui.design.theme.onPrimaryContainerLight +import com.androidvip.sysctlgui.design.theme.primaryContainerLight +import com.androidvip.sysctlgui.design.theme.primaryLight +import com.androidvip.sysctlgui.domain.models.KernelParam +import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class FavoritesGlanceWidget : GlanceAppWidget(), KoinComponent { + private val getUserParamsUseCase: GetUserParamsUseCase by inject() + + override suspend fun provideGlance(context: Context, id: GlanceId) { + val favoriteParams = getUserParamsUseCase().filter { it.isFavorite } + + provideContent { + FavoritesWidgetContent(params = favoriteParams) + } + } + + @Composable + fun FavoritesWidgetContent(params: List) { + Column( + modifier = GlanceModifier + .fillMaxSize() + .background(primaryContainerLight) + .padding(16.dp), + horizontalAlignment = Alignment.Horizontal.Start + ) { + Text( + text = "Favorite Kernel Params", + style = TextStyle( + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + color = ColorProvider(onPrimaryContainerLight) + ), + modifier = GlanceModifier.padding(vertical = 8.dp) + ) + Spacer(GlanceModifier.height(8.dp)) + + if (params.isEmpty()) { + Text("No favorite parameters yet.") + } else { + LazyColumn { + items(params) { param -> + FavoriteItem(param = param) + Spacer(GlanceModifier.height(4.dp)) + } + } + } + } + } + + @Composable + fun FavoriteItem(param: KernelParam) { + Column( + modifier = GlanceModifier + .padding(vertical = 8.dp) + .fillMaxWidth() + .clickable( + onClick = actionRunCallback( + parameters = actionParametersOf(kernelParamNameKey to param.path) + ) + ) + ) { + Text( + text = param.name, + style = TextStyle( + fontWeight = FontWeight.Medium, + color = ColorProvider(primaryLight) + ) + ) + Text( + text = param.value, + style = TextStyle( + fontWeight = FontWeight.Medium, + color = ColorProvider(primaryLight) + ) + ) + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidgetReceiver.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidgetReceiver.kt new file mode 100644 index 0000000..e9eb354 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidgetReceiver.kt @@ -0,0 +1,8 @@ +package com.androidvip.sysctlgui.widgets + +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetReceiver + +class FavoritesGlanceWidgetReceiver : GlanceAppWidgetReceiver() { + override val glanceAppWidget: GlanceAppWidget = FavoritesGlanceWidget() +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidget.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidget.kt deleted file mode 100644 index afec2c9..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidget.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.androidvip.sysctlgui.widgets - -import android.app.PendingIntent -import android.appwidget.AppWidgetManager -import android.appwidget.AppWidgetProvider -import android.content.Context -import android.content.Intent -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK -import android.widget.RemoteViews -import androidx.core.net.toUri -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.domain.enums.Actions -import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase -import com.androidvip.sysctlgui.helpers.UiKernelParamMapper -import com.androidvip.sysctlgui.models.UiKernelParam -import com.androidvip.sysctlgui.ui.start.StartActivity -import com.androidvip.sysctlgui.widgets.FavoritesWidget.Companion.EDIT_PARAM_EXTRA -import kotlinx.coroutines.runBlocking -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject -import kotlin.collections.map - -class FavoritesWidget : AppWidgetProvider(), KoinComponent { - private val getUserParamsUseCase: GetUserParamsUseCase by inject() - - override fun onUpdate( - context: Context, - appWidgetManager: AppWidgetManager, - appWidgetIds: IntArray - ) { - for (appWidgetId in appWidgetIds) { - updateAppWidget(context, appWidgetManager, appWidgetId) - } - super.onUpdate(context, appWidgetManager, appWidgetIds) - } - - override fun onReceive(context: Context?, intent: Intent?) { - if (context == null || intent == null) return - if (intent.action != EDIT_PARAM_EXTRA) { - return super.onReceive(context, intent) - } - - runBlocking { - val params = getUserParamsUseCase() - .filter { it.isFavorite } - .map(UiKernelParamMapper::map) - .toMutableList() - - if (params.isEmpty()) return@runBlocking - - val param = params[intent.getIntExtra(EXTRA_ITEM, 0)] - Intent(context, StartActivity::class.java).apply { - flags = FLAG_ACTIVITY_NEW_TASK - action = Actions.EditParam.name - // TODO - /*putExtra(EditKernelParamActivity.EXTRA_PARAM, param) - putExtra(EditKernelParamActivity.EXTRA_EDIT_SAVED_PARAM, true)*/ - context.startActivity(this) - } - } - - super.onReceive(context, intent) - } - - override fun onDisabled(context: Context?) { - super.onDisabled(context) - } - - companion object { - const val EDIT_PARAM_EXTRA = "com.androidvip.sysctlgui.EDIT_PARAM_EXTRA" - const val EXTRA_ITEM = "com.androidvip.sysctlgui.EXTRA_ITEM" - } -} - -internal fun updateAppWidget( - context: Context, - appWidgetManager: AppWidgetManager, - appWidgetId: Int -) { - val intent = Intent(context, FavoritesWidgetService::class.java).apply { - putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) - data = toUri(Intent.URI_INTENT_SCHEME).toUri() - } - - val views = RemoteViews( - context.packageName, - R.layout.favorites_widget - ).apply { - setRemoteAdapter(R.id.favorites_list, intent) - setEmptyView(R.id.favorites_list, R.id.empty_view) - } - - val editParamPendingIntent: PendingIntent = Intent( - context, - FavoritesWidget::class.java - ).run { - action = EDIT_PARAM_EXTRA - putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) - data = toUri(Intent.URI_INTENT_SCHEME).toUri() - - val flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - - PendingIntent.getBroadcast(context, 0, this, flags) - } - views.setPendingIntentTemplate(R.id.favorites_list, editParamPendingIntent) - - appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.favorites_list) - appWidgetManager.updateAppWidget(appWidgetId, views) -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidgetService.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidgetService.kt deleted file mode 100644 index 2876d99..0000000 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesWidgetService.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.androidvip.sysctlgui.widgets - -import android.appwidget.AppWidgetManager -import android.content.Context -import android.content.Intent -import android.widget.RemoteViews -import android.widget.RemoteViewsService -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.domain.models.KernelParam -import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase -import com.androidvip.sysctlgui.widgets.FavoritesWidget.Companion.EXTRA_ITEM -import kotlinx.coroutines.runBlocking -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -class FavoritesWidgetService : RemoteViewsService() { - override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory { - return FavoritesRemoteViewsFactory(applicationContext, intent!!) - } -} - -class FavoritesRemoteViewsFactory( - val context: Context, - val intent: Intent -) : RemoteViewsService.RemoteViewsFactory, KoinComponent { - private val getUserParamsUseCase: GetUserParamsUseCase by inject() - - private var widgetId: Any = intent.getIntExtra( - AppWidgetManager.EXTRA_APPWIDGET_ID, - AppWidgetManager.INVALID_APPWIDGET_ID - ) - - private var params: MutableList = mutableListOf() - - override fun onCreate() { - runBlocking { - params = getUserParamsUseCase().filter { - it.isFavorite - }.toMutableList() - } - } - - override fun getLoadingView(): RemoteViews? = null - - override fun getItemId(position: Int): Long { - return position.toLong() - } - - override fun onDataSetChanged() { - runBlocking { - params = getUserParamsUseCase().filter { it.isFavorite }.toMutableList() - } - } - - override fun hasStableIds(): Boolean = true - - override fun getViewAt(position: Int): RemoteViews { - val views = RemoteViews( - context.packageName, - R.layout.list_item_kernel_param_widget_list - ) - - val param = params[position] - views.setTextViewText(R.id.listKernelParamName, param.name) - views.setTextViewText(R.id.listKernelParamValue, param.value) - - val fillInIntent = Intent().apply { - putExtra(EXTRA_ITEM, position) - } - - views.setOnClickFillInIntent(R.id.listKernelParamLayout, fillInIntent) - return views - } - - override fun getCount(): Int = params.size - - override fun getViewTypeCount(): Int = 1 - - override fun onDestroy() { - params.clear() - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/UpdateFavoriteWidgetUseCase.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/UpdateFavoriteWidgetUseCase.kt new file mode 100644 index 0000000..b4ed4ae --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/UpdateFavoriteWidgetUseCase.kt @@ -0,0 +1,14 @@ +package com.androidvip.sysctlgui.widgets + +import android.content.Context +import androidx.glance.appwidget.GlanceAppWidgetManager + +class UpdateFavoriteWidgetUseCase(private val context: Context) { + suspend operator fun invoke() { + val manager = GlanceAppWidgetManager(context) + val glanceIds = manager.getGlanceIds(FavoritesGlanceWidget::class.java) + glanceIds.forEach { glanceId -> + FavoritesGlanceWidget().update(context, glanceId) + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/ViewKernelParamDetailsAction.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/ViewKernelParamDetailsAction.kt new file mode 100644 index 0000000..009e0d3 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/ViewKernelParamDetailsAction.kt @@ -0,0 +1,29 @@ +package com.androidvip.sysctlgui.widgets + +import android.content.Context +import android.content.Intent +import androidx.glance.GlanceId +import androidx.glance.action.ActionParameters +import androidx.glance.appwidget.action.ActionCallback +import com.androidvip.sysctlgui.domain.enums.Actions +import com.androidvip.sysctlgui.ui.main.MainActivity +import com.androidvip.sysctlgui.ui.start.StartActivity + +internal val kernelParamNameKey = ActionParameters.Key("kernelParamNameKey") + +class ViewKernelParamDetailsAction : ActionCallback { + override suspend fun onAction( + context: Context, + glanceId: GlanceId, + parameters: ActionParameters + ) { + val paramName = parameters[kernelParamNameKey] ?: return + + val intent = Intent(context, StartActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + action = Actions.EditParam.name + putExtra(MainActivity.EXTRA_PARAM_NAME, paramName) + } + context.startActivity(intent) + } +} diff --git a/app/src/main/res/layout/favorites_widget.xml b/app/src/main/res/layout/favorites_widget.xml index 08b3875..6089b65 100644 --- a/app/src/main/res/layout/favorites_widget.xml +++ b/app/src/main/res/layout/favorites_widget.xml @@ -3,7 +3,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - android:background="@color/colorBackgroundAlt"> + android:background="@color/neutral_800"> diff --git a/app/src/main/res/xml/favorites_widget_info.xml b/app/src/main/res/xml/favorites_glance_widget_info.xml similarity index 53% rename from app/src/main/res/xml/favorites_widget_info.xml rename to app/src/main/res/xml/favorites_glance_widget_info.xml index 8dbf6cc..b78fe79 100644 --- a/app/src/main/res/xml/favorites_widget_info.xml +++ b/app/src/main/res/xml/favorites_glance_widget_info.xml @@ -1,10 +1,12 @@ + android:widgetCategory="home_screen" + tools:ignore="UnusedAttribute" /> \ No newline at end of file diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/UpsertUserParamUseCase.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/UpsertUserParamUseCase.kt index 3ce75c7..b4a2095 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/UpsertUserParamUseCase.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/usecase/UpsertUserParamUseCase.kt @@ -1,7 +1,7 @@ package com.androidvip.sysctlgui.domain.usecase -import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.domain.exceptions.BlankValueNotAllowedException +import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.domain.repository.AppPrefs import com.androidvip.sysctlgui.domain.repository.UserRepository @@ -23,4 +23,4 @@ class UpsertUserParamUseCase( return repository.upsertUserParam(param) } -} +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 356b3e6..b362bae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,7 @@ kotlinxSerializationCore = "1.9.0" koin = "4.1.0" window = "1.4.0" workRuntimeKtx = "2.10.3" +glanceAppwidget = "1.1.1" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -53,6 +54,7 @@ androidx-room-runtime = { group = "androidx.room", name = "room-runtime", versio androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-glance-appwidget = { group = "androidx.glance", name = "glance-appwidget", version.ref = "glanceAppwidget" } androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } From 1b9ffbe582c7fffe8e8d50d81aa3053b8ba2e723 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Sat, 16 Aug 2025 22:10:50 -0300 Subject: [PATCH 13/23] feature: added new string resources --- .../com/androidvip/sysctlgui/Extensions.kt | 58 ---------- .../sysctlgui/ui/components/ErrorContainer.kt | 4 +- .../sysctlgui/ui/main/MainTopBar.kt | 2 +- .../ui/params/DocumentationBottomSheet.kt | 3 +- .../ui/params/browse/ParamBrowseScreen.kt | 9 +- .../ui/params/browse/ParamFileRow.kt | 13 +-- .../sysctlgui/ui/params/browse/ParamRow.kt | 11 +- .../ui/params/edit/ActionToggleButton.kt | 31 +++--- .../ui/params/edit/EditParamScreen.kt | 46 +++++--- .../ui/params/edit/EditParamViewModel.kt | 21 +++- .../ui/presets/ImportPresetScreen.kt | 16 +-- .../sysctlgui/ui/presets/PresetsScreen.kt | 9 +- .../sysctlgui/ui/presets/PresetsViewModel.kt | 4 +- .../sysctlgui/ui/search/SearchScreen.kt | 29 +++-- .../sysctlgui/ui/start/StartActivity.kt | 6 +- .../ui/tasker/TaskerPluginActivity.kt | 18 ++++ .../widgets/FavoritesGlanceWidget.kt | 2 +- .../res/layout/activity_tasker_plugin.xml | 4 +- app/src/main/res/values-pt-rBR/strings.xml | 49 ++++++++- app/src/main/res/values/strings.xml | 46 +++++++- .../res/xml/favorites_glance_widget_info.xml | 2 +- common/design/src/main/res/values/colors.xml | 100 +++++++++--------- .../repository/AppSettingsRepositoryImpl.kt | 89 +++++++++------- .../repository/DocumentationRepositoryImpl.kt | 5 +- .../source/OnlineDocumentationDataSource.kt | 19 ++-- data/src/main/res/values-pt-rBR/strings.xml | 30 ++++++ data/src/main/res/values/strings.xml | 30 ++++++ .../domain/repository/ParamsRepository.kt | 6 +- 28 files changed, 414 insertions(+), 248 deletions(-) create mode 100644 data/src/main/res/values-pt-rBR/strings.xml create mode 100644 data/src/main/res/values/strings.xml diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/Extensions.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/Extensions.kt index 4f82a5e..7f30045 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/Extensions.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/Extensions.kt @@ -1,48 +1,14 @@ package com.androidvip.sysctlgui -import android.app.Activity import android.content.Context -import android.content.res.ColorStateList -import android.net.Uri import android.os.Bundle import android.os.Handler import android.os.Looper -import android.view.View import android.widget.Toast -import androidx.annotation.AttrRes -import androidx.core.view.ViewCompat -import androidx.fragment.app.Fragment import com.androidvip.sysctlgui.receivers.TaskerReceiver -import com.google.android.material.color.ColorRoles -import com.google.android.material.color.MaterialColors -import com.google.android.material.snackbar.Snackbar -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.io.InputStream import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract -fun View.goAway() { this.visibility = View.GONE } -fun View.hide() { this.visibility = View.INVISIBLE } -fun View.show() { this.visibility = View.VISIBLE } - -fun View.getColorRoles(@AttrRes colorAttrRes: Int = androidx.appcompat.R.attr.colorPrimary): ColorRoles { - val color = MaterialColors.getColor(this, colorAttrRes) - return MaterialColors.getColorRoles(context, color) -} - -fun Snackbar.showAsLight() { - val roles = view.getColorRoles() - ViewCompat.setBackgroundTintList(view, ColorStateList.valueOf(roles.accentContainer)) - setTextColor(roles.accent) - show() -} - -fun Fragment.toast(messageRes: Int, length: Int = Toast.LENGTH_SHORT) { - if (!isAdded) return - requireContext().toast(getString(messageRes), length) -} - fun Context?.toast(messageRes: Int, length: Int = Toast.LENGTH_SHORT) { if (this == null) return toast(getString(messageRes), length) @@ -61,20 +27,6 @@ fun Context.runOnUiThread(f: Context.() -> Unit) { } } -fun Uri.readLines(context: Context?, forEachLine: (String) -> Unit) { - context?.contentResolver?.openInputStream(this).readLines(forEachLine) -} - -fun InputStream?.readLines(forEachLine: (String) -> Unit) { - this?.use { inputStream -> - inputStream.bufferedReader().use { - it.readLines().forEach { line -> - forEachLine(line) - } - } - } -} - @ExperimentalContracts fun Bundle?.isValidTaskerBundle() : Boolean { contract { @@ -82,13 +34,3 @@ fun Bundle?.isValidTaskerBundle() : Boolean { } return this != null && containsKey(TaskerReceiver.BUNDLE_EXTRA_LIST_NUMBER) } - -suspend inline fun Activity?.runSafeOnUiThread(crossinline uiBlock: () -> Unit) { - this?.let { - if (!it.isFinishing && !it.isDestroyed) { - withContext(Dispatchers.Main) { - runCatching(uiBlock) - } - } - } -} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/ErrorContainer.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/ErrorContainer.kt index d1f6940..80bf0f6 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/ErrorContainer.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/components/ErrorContainer.kt @@ -26,7 +26,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import com.androidvip.sysctlgui.R private const val ERROR_CONTAINER_ANIMATION_DURATION = 4000 @@ -80,7 +82,7 @@ internal fun ErrorContainer(message: String, onAnimationEnd: () -> Unit) { ) Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { Text( - text = "Error", + text = stringResource(R.string.error), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onErrorContainer ) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainTopBar.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainTopBar.kt index 4b214ce..c82019a 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainTopBar.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainTopBar.kt @@ -35,7 +35,7 @@ fun MainTopBar( IconButton(onClick = onBackPressed) { Icon( imageVector = Icons.AutoMirrored.Rounded.ArrowBack, - contentDescription = "Back" + contentDescription = stringResource(R.string.go_back) ) } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/DocumentationBottomSheet.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/DocumentationBottomSheet.kt index 8ba0aab..eb7b3d2 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/DocumentationBottomSheet.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/DocumentationBottomSheet.kt @@ -18,6 +18,7 @@ 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.AnnotatedString import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.font.FontWeight @@ -109,7 +110,7 @@ private fun DocumentationBottomSheetContent( contentDescription = null, modifier = Modifier.padding(end = 8.dp) ) - Text(text = "Read more") + Text(text = stringResource(R.string.read_more)) } } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt index ddff009..ec7f5f4 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics @@ -151,7 +152,8 @@ private fun ParamBrowseScreenContent( onRefresh: () -> Unit ) { val listState = rememberLazyListState() - val pullRefreshState = rememberPullRefreshState(refreshing = isRefreshing, onRefresh = onRefresh) + val pullRefreshState = + rememberPullRefreshState(refreshing = isRefreshing, onRefresh = onRefresh) var headerVisible by remember { mutableStateOf(backEnabled) } BackHandler(enabled = backEnabled, onBack = onBackPressed) @@ -236,7 +238,10 @@ private fun ParamBrowseScreenContent( modifier = Modifier.align(Alignment.BottomCenter) ) { InfoItem( - text = "Read documentation for \"${documentation?.title}\"", + text = stringResource( + R.string.read_documentation_format, + documentation?.title.orEmpty() + ), textStyle = MaterialTheme.typography.titleSmall.copy( textDecoration = TextDecoration.Underline, color = MaterialTheme.colorScheme.primary diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamFileRow.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamFileRow.kt index 43f299c..72f7106 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamFileRow.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamFileRow.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight @@ -43,9 +44,9 @@ fun ParamFileRow( ) { Box(modifier = Modifier.clickable { onParamClicked(param) }) { val rowDescription = if (param.isDirectory) { - "Directory: ${param.name}" + stringResource(R.string.acessibility_directory_description_format, param.name) } else { - "Parameter: ${param.name}" + stringResource(R.string.acessibility_param_description_format, param.name) } Row( modifier = modifier @@ -101,7 +102,7 @@ private fun ParamIcon(param: UiKernelParam) { } } - val iconId = if (param.isDirectory) R.drawable.ic_folder else R.drawable.ic_file + val iconId = if (param.isDirectory) R.drawable.ic_folder else R.drawable.ic_file Box( modifier = Modifier @@ -112,7 +113,7 @@ private fun ParamIcon(param: UiKernelParam) { ) { Icon( painter = painterResource(iconId), - contentDescription = "Parameter icon", + contentDescription = stringResource(R.string.acessibility_param_icon_description), modifier = Modifier.size(24.dp), tint = iconColor ) @@ -124,14 +125,14 @@ private fun TrailingIcon(param: UiKernelParam, showFavoriteIcon: Boolean) { if (param.isDirectory) { Icon( imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowRight, - contentDescription = "Navigate do directory", + contentDescription = stringResource(R.string.acessibility_davegate_to_directory_description), modifier = Modifier.size(24.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant ) } else if (param.isFavorite && showFavoriteIcon) { Icon( imageVector = Icons.Rounded.Favorite, - contentDescription = "Favorite", + contentDescription = stringResource(R.string.favorites), tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(18.dp) ) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamRow.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamRow.kt index da3c96b..c47fd07 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamRow.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamRow.kt @@ -17,6 +17,7 @@ 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.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription @@ -24,6 +25,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp +import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme import com.androidvip.sysctlgui.models.UiKernelParam @@ -34,8 +36,11 @@ fun ParamRow( onParamClicked: (UiKernelParam) -> Unit, showFullName: Boolean = false ) { - val rowDescription = "Parameter: ${param.name}" - val rowState = if (param.isFavorite) "Marked as favorite" else "" + val rowDescription = stringResource( + R.string.acessibility_param_description_format, + param.name + ) + val rowState = if (param.isFavorite) stringResource(R.string.marked_as_favorite) else "" Row( modifier = modifier @@ -100,7 +105,7 @@ private fun ParamRowPreview() { SysctlGuiTheme(contrastLevel = 1) { Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { ParamRow(param = param, onParamClicked = {}, showFullName = true) - ParamRow(param = param.copy(isFavorite = true), onParamClicked = {},) + ParamRow(param = param.copy(isFavorite = true), onParamClicked = {}) } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/ActionToggleButton.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/ActionToggleButton.kt index 3bf22ef..a8819e4 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/ActionToggleButton.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/ActionToggleButton.kt @@ -28,6 +28,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewDynamicColors import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp @@ -95,7 +96,10 @@ internal fun ActionToggleButton( Icon( painter = if (isCurrentlyActive) iconOnActive else iconOnInactive, - contentDescription = "Toggle $contentDescription", + contentDescription = stringResource( + R.string.toggle_format, + contentDescription.orEmpty() + ), tint = iconTint ) } @@ -113,10 +117,11 @@ internal fun FavoriteButton( isActive = isFavorite, iconOnActive = painterResource(R.drawable.ic_favorite), iconOnInactive = painterResource(R.drawable.ic_favorite_outlined), - contentDescription = "favorite", + contentDescription = stringResource(R.string.mark_as_favorite), onToggle = onFavoriteClick ) } + @Composable internal fun TaskerButton( modifier: Modifier = Modifier, @@ -128,7 +133,7 @@ internal fun TaskerButton( isActive = isTaskerParam, iconOnActive = painterResource(R.drawable.ic_tasker), iconOnInactive = painterResource(R.drawable.ic_tasker_outlined), - contentDescription = "tasker param", + contentDescription = stringResource(R.string.toggle_tasker_param), onToggle = onToggle ) } @@ -142,22 +147,10 @@ private fun FavoriteButtonStatesPreview() { modifier = Modifier.padding(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - FavoriteButton( - isFavorite = false, - onFavoriteClick = {} - ) - FavoriteButton( - isFavorite = true, - onFavoriteClick = {} - ) - TaskerButton( - isTaskerParam = false, - onToggle = {} - ) - TaskerButton( - isTaskerParam = true, - onToggle = {} - ) + FavoriteButton(isFavorite = false, onFavoriteClick = {}) + FavoriteButton(isFavorite = true, onFavoriteClick = {}) + TaskerButton(isTaskerParam = false, onToggle = {}) + TaskerButton(isTaskerParam = true, onToggle = {}) } } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt index f28758f..c6bc4ac 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt @@ -58,6 +58,7 @@ import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextLinkStyles @@ -98,19 +99,20 @@ fun EditParamScreen( ) { val context = LocalContext.current val state = viewModel.uiState.collectAsStateWithLifecycle() - val taskerListOptions = listOf("Primary", "Secondary") + val taskerListOptions = stringArrayResource(R.array.tasker_lists).toList() var showSelectTaskerListDialog by rememberSaveable { mutableStateOf(false) } var selectedOptionIndex by rememberSaveable { mutableIntStateOf(Consts.LIST_NUMBER_PRIMARY_TASKER) } var errorMessage by rememberSaveable { mutableStateOf("") } var showError by rememberSaveable { mutableStateOf(false) } + val appBarTitle = stringResource(R.string.edit_params) LaunchedEffect(Unit) { mainViewModel.onEvent( MainViewEvent.OnSateChangeRequested( MainViewState( - topBarTitle = "Edit kernel parameter", + topBarTitle = appBarTitle, showTopBar = true, showNavBar = false, showBackButton = true, @@ -126,6 +128,8 @@ fun EditParamScreen( } } + val successMessage = stringResource(R.string.value_applied_successfully) + val undoText = stringResource(R.string.undo) LaunchedEffect(viewModel.effect) { viewModel.effect.collect { effect -> when (effect) { @@ -141,8 +145,8 @@ fun EditParamScreen( is EditParamViewEffect.ShowApplySuccess -> { mainViewModel.onEvent( MainViewEvent.ShowSnackbarRequested( - message = "Value applied successfully", - actionLabel = "Undo" + message = successMessage, + actionLabel = undoText ) ) } @@ -173,7 +177,7 @@ fun EditParamScreen( SingleChoiceDialog( showDialog = showSelectTaskerListDialog, - title = "Choose a Tasker list", + title = stringResource(R.string.select_tasker_list), options = taskerListOptions, initialSelectedOptionIndex = selectedOptionIndex, onDismissRequest = { showSelectTaskerListDialog = false }, @@ -205,14 +209,18 @@ private fun EditParamContent( val copyParamContentToClipboard = { val clipData = ClipData.newPlainText( - "Kernel Parameter", + context.getString(R.string.kernel_params), "${param.lastNameSegment}=${param.value} (${param.path})" ) val clipEntry = ClipEntry(clipData) coroutineScope.launch { clipboardManager.setClipEntry(clipEntry) } - Toast.makeText(context, "Copied to clipboard", Toast.LENGTH_SHORT).show() + Toast.makeText( + context, + context.getString(R.string.copied_to_clipboard), + Toast.LENGTH_SHORT + ).show() } Column( @@ -235,7 +243,11 @@ private fun EditParamContent( indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = { - Toast.makeText(context, "Long press to copy", Toast.LENGTH_SHORT).show() + Toast.makeText( + context, + context.getString(R.string.long_press_to_copy), + Toast.LENGTH_SHORT + ).show() }, onLongClick = copyParamContentToClipboard ) @@ -297,11 +309,11 @@ private fun EditParamContent( AssistChip( onClick = { onTaskerClicked(true) }, modifier = Modifier.padding(16.dp), - label = { Text(text = "Tasker list: $listName") }, + label = { Text(text = stringResource(R.string.tasker_list_format, listName)) }, leadingIcon = { Icon( painter = painterResource(R.drawable.ic_tasker), - contentDescription = "Tasker list", + contentDescription = stringResource(R.string.tasker_list), tint = MaterialTheme.colorScheme.tertiary ) } @@ -355,7 +367,7 @@ private fun ParamValueContent( ) { Column(modifier = Modifier.weight(1f)) { Text( - text = "Parameter value", + text = stringResource(R.string.parameter_value), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onBackground ) @@ -388,13 +400,13 @@ private fun ParamValueContent( if (editingActive) { Icon( imageVector = Icons.Rounded.Done, - contentDescription = "Apply", + contentDescription = stringResource(R.string.apply_param), tint = MaterialTheme.colorScheme.primary ) } else { Icon( imageVector = Icons.Rounded.Edit, - contentDescription = "Edit", + contentDescription = stringResource(R.string.edit), tint = MaterialTheme.colorScheme.primary ) } @@ -432,7 +444,7 @@ fun EditableParamValue( OutlinedTextField( value = editedValue, onValueChange = onEditorValueChange, - label = { Text("New value") }, + label = { Text(stringResource(R.string.new_value)) }, singleLine = true, keyboardOptions = KeyboardOptions(keyboardType = keyboardType), modifier = Modifier.fillMaxWidth() @@ -459,7 +471,7 @@ private fun ParamDocs( Column(modifier = modifier) { Text( - text = "Documentation", + text = stringResource(R.string.documentation), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onBackground ) @@ -492,7 +504,7 @@ private fun ParamDocs( tint = MaterialTheme.colorScheme.onErrorContainer ) Text( - text = "No documentation available", + text = stringResource(R.string.no_info_available), style = MaterialTheme.typography.bodyLarge.copy(), fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onErrorContainer @@ -547,7 +559,7 @@ private fun DocumentationContent( .padding(vertical = 8.dp) .align(Alignment.End) ) { - Text(text = "Read more") + Text(text = stringResource(R.string.read_more)) } } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt index 05b4529..fc92295 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamViewModel.kt @@ -4,6 +4,11 @@ import android.util.Log import androidx.compose.ui.text.input.KeyboardType import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.domain.StringProvider +import com.androidvip.sysctlgui.domain.exceptions.ApplyValueException +import com.androidvip.sysctlgui.domain.exceptions.BlankValueNotAllowedException +import com.androidvip.sysctlgui.domain.exceptions.CommitModeException import com.androidvip.sysctlgui.domain.repository.AppPrefs import com.androidvip.sysctlgui.domain.usecase.ApplyParamUseCase import com.androidvip.sysctlgui.domain.usecase.GetParamDocumentationUseCase @@ -25,6 +30,7 @@ class EditParamViewModel( private val getUserParam: GetUserParamByNameUseCase, private val isTaskerInstalled: IsTaskerInstalledUseCase, private val updateFavoriteWidget: UpdateFavoriteWidgetUseCase, + private val stringProvider: StringProvider, private val appPrefs: AppPrefs ) : BaseViewModel() { private val paramName: String? = savedStateHandle.get(PARAM_NAME_KEY) @@ -85,8 +91,21 @@ class EditParamViewModel( updateFavoriteWidget() }.onFailure { Log.e("EditParamViewModel", "Failed to apply param", it) + val message = when (it) { + is BlankValueNotAllowedException -> stringProvider.getString( + R.string.apply_error_blank_values + ) + is CommitModeException -> stringProvider.getString( + R.string.apply_error_commit_mode + ) + + is ApplyValueException -> stringProvider.getString( + R.string.apply_error_command_execution_failed + ) + else -> it.message.orEmpty() + } setEffect { - EditParamViewEffect.ShowError(it.message.orEmpty()) + EditParamViewEffect.ShowError(message) } } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/ImportPresetScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/ImportPresetScreen.kt index 7f78ae5..00a2e1d 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/ImportPresetScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/ImportPresetScreen.kt @@ -45,6 +45,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontFamily @@ -55,6 +56,7 @@ import androidx.compose.ui.tooling.preview.PreviewDynamicColors import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.ui.main.MainViewEvent @@ -78,7 +80,7 @@ fun ImportPresetScreen( mainViewModel.onEvent( MainViewEvent.OnSateChangeRequested( MainViewState( - topBarTitle = "Applying preset", + topBarTitle = context.getString(R.string.applying_preset), showTopBar = true, showNavBar = false, showBackButton = true, @@ -155,7 +157,7 @@ private fun IncomingPresetsContent( ) { Column(modifier = Modifier.fillMaxSize()) { Text( - text = "${paramsToImport.size} parameters found", + text = stringResource(R.string.parameters_found_format, paramsToImport.size), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier @@ -246,7 +248,7 @@ private fun IncomingPresetsContent( modifier = Modifier.weight(1f), onClick = onCancelPressed ) { - Text(text = "Cancel") + Text(text = stringResource(android.R.string.cancel)) } OutlinedButton( @@ -258,7 +260,7 @@ private fun IncomingPresetsContent( contentColor = MaterialTheme.colorScheme.onPrimary ) ) { - Text(text = "Import") + Text(text = stringResource(R.string.import_text)) } } } @@ -275,7 +277,7 @@ private fun LoadingIndicator() { ) { CircularProgressIndicator() Text( - text = "Loading preset...", + text = stringResource(R.string.loading_preset), style = MaterialTheme.typography.bodyLarge, modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.onBackground, @@ -310,13 +312,13 @@ private fun SuccessIndicator(onAnimationEnd: () -> Unit) { ) { Icon( imageVector = Icons.Rounded.CheckCircle, - contentDescription = "Success", + contentDescription = stringResource(R.string.success), tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(128.dp) ) Text( - text = "Presets successfully imported", + text = stringResource(R.string.presets_import_success_message), style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.onBackground, textAlign = TextAlign.Center diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt index eaa63ce..5b4d1ea 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt @@ -33,6 +33,7 @@ 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.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.tooling.preview.PreviewDynamicColors @@ -137,14 +138,14 @@ private fun PresetsScreenContent( ) { ImportCards( onClick = onImportPressed, - title = "Import", - description = "Import presets from a file", + title = stringResource(R.string.import_text), + description = stringResource(R.string.import_presets_description), iconRes = R.drawable.ic_import ) ImportCards( onClick = onExportPressed, - title = "Export", - description = "Export presets to a file", + title = stringResource(R.string.export), + description = stringResource(R.string.export_presets_description), iconRes = R.drawable.ic_export ) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsViewModel.kt index 2ef1f1f..f184388 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsViewModel.kt @@ -47,7 +47,7 @@ class PresetsViewModel( } }.onFailure { setState { copy(incomingPresetsScreenState = IncomingPresetsScreenState.Idle) } - setEffect { PresetsViewEffect.ShowError("Failed to import params") } + setEffect { PresetsViewEffect.ShowError(stringProvider.getString(R.string.import_failure)) } } } } @@ -96,7 +96,7 @@ class PresetsViewModel( runCatching { presetsFileProcessor.backupParamsToUri(uri, userParams) }.onSuccess { - setEffect { PresetsViewEffect.ShowToast("Export complete") } + setEffect { PresetsViewEffect.ShowToast(stringProvider.getString(R.string.export_complete)) } }.onFailure { setEffect { PresetsViewEffect.ShowError(stringProvider.getString(R.string.preset_error_processing_file)) } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt index 08788dc..71b6bad 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -160,7 +161,7 @@ private fun SearchScreenContent( onSearch = onSearch, expanded = searchActive, onExpandedChange = onActiveChange, - placeholder = { Text("Search kernel parameters") }, + placeholder = { Text(stringResource(R.string.search_title)) }, leadingIcon = { AnimatedContent( targetState = searchActive, @@ -174,11 +175,16 @@ private fun SearchScreenContent( }) { Icon( Icons.AutoMirrored.Rounded.ArrowBack, - contentDescription = "Back" + contentDescription = stringResource(R.string.go_back) ) } } else { - Icon(Icons.Rounded.Search, contentDescription = "Search Icon") + Icon( + imageVector = Icons.Rounded.Search, + contentDescription = stringResource( + R.string.acessibility_search_icon + ) + ) } } }, @@ -191,7 +197,10 @@ private fun SearchScreenContent( IconButton(onClick = { onSearchQueryChange("") }) { - Icon(Icons.Rounded.Clear, contentDescription = "Clear search") + Icon( + imageVector = Icons.Rounded.Clear, + contentDescription = stringResource(R.string.clear_search) + ) } } }, @@ -258,7 +267,7 @@ private fun SearchViewContent( if (historyHints.isNotEmpty()) { item(key = "history_header") { Text( - text = "Recent Searches", + text = stringResource(R.string.recent_searches), style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.primary, @@ -276,7 +285,7 @@ private fun SearchViewContent( leadingContent = { Icon( painter = painterResource(R.drawable.ic_history), - contentDescription = "History item" + contentDescription = stringResource(R.string.history_item) ) }, trailingContent = { @@ -286,7 +295,7 @@ private fun SearchViewContent( ) { Icon( Icons.Rounded.Clear, - contentDescription = "Clear history item" + contentDescription = stringResource(R.string.clear_history_item) ) } }, @@ -342,7 +351,7 @@ private fun SearchViewContent( .padding(16.dp), contentAlignment = Alignment.Center ) { - Text("No suggestions available.") + Text(stringResource(R.string.search_no_suggestions)) } } } @@ -382,7 +391,7 @@ private fun SearchResultsContent( contentAlignment = Alignment.Center ) { Text( - text = "No results found for \"$searchQuery\"", + text = stringResource(R.string.search_no_results_format, searchQuery), style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onBackground, textAlign = TextAlign.Center, @@ -397,7 +406,7 @@ private fun SearchResultsContent( contentAlignment = Alignment.Center ) { Text( - text = "Enter a query to search for kernel parameters.", + text = stringResource(R.string.search_message), style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onBackground, textAlign = TextAlign.Center, diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartActivity.kt index 5356d7f..d173902 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/start/StartActivity.kt @@ -7,13 +7,13 @@ import android.widget.Toast import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.data.utils.RootUtils import com.androidvip.sysctlgui.databinding.ActivitySplashBinding import com.androidvip.sysctlgui.domain.enums.Actions import com.androidvip.sysctlgui.domain.repository.AppPrefs -import com.androidvip.sysctlgui.goAway import com.androidvip.sysctlgui.toast import com.androidvip.sysctlgui.ui.main.MainActivity import kotlinx.coroutines.delay @@ -41,8 +41,6 @@ class StartActivity : AppCompatActivity() { binding.splashStatusText.setText(R.string.splash_status_checking_busybox) val isBusyBoxAvailable = checkBusyBox() - binding.splashStatusText.setText(R.string.splash_status_checking_migration) - if (isRootAccessGiven) { if (!isBusyBoxAvailable) { prefs.useBusybox = false @@ -50,7 +48,7 @@ class StartActivity : AppCompatActivity() { navigate() finish() } else { - binding.splashProgress.goAway() + binding.splashProgress.isVisible = false toast(R.string.root_not_found_sum, Toast.LENGTH_LONG) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { startActivity( diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/tasker/TaskerPluginActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/tasker/TaskerPluginActivity.kt index c3b59d5..a1eb806 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/tasker/TaskerPluginActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/tasker/TaskerPluginActivity.kt @@ -3,8 +3,12 @@ package com.androidvip.sysctlgui.ui.tasker import android.content.Intent import android.os.Bundle import android.view.MenuItem +import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding import com.androidvip.sysctlgui.databinding.ActivityTaskerPluginBinding import com.androidvip.sysctlgui.receivers.TaskerReceiver import kotlin.contracts.ExperimentalContracts @@ -15,9 +19,23 @@ class TaskerPluginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() binding = ActivityTaskerPluginBinding.inflate(layoutInflater) setContentView(binding.root) + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets -> + val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) + + view.updatePadding( + left = insets.left, + top = insets.top, + right = insets.right, + bottom = insets.bottom + ) + + WindowInsetsCompat.CONSUMED + } + binding.taskerDoneButton.setOnClickListener { val selectedListNumber = binding.taskerListSpinner.selectedItemPosition // 0-based index diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidget.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidget.kt index 01529f6..0ff68df 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidget.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidget.kt @@ -85,7 +85,7 @@ class FavoritesGlanceWidget : GlanceAppWidget(), KoinComponent { .fillMaxWidth() .clickable( onClick = actionRunCallback( - parameters = actionParametersOf(kernelParamNameKey to param.path) + parameters = actionParametersOf(kernelParamNameKey to param.name) ) ) ) { diff --git a/app/src/main/res/layout/activity_tasker_plugin.xml b/app/src/main/res/layout/activity_tasker_plugin.xml index 5a11583..99a6e14 100644 --- a/app/src/main/res/layout/activity_tasker_plugin.xml +++ b/app/src/main/res/layout/activity_tasker_plugin.xml @@ -12,6 +12,7 @@ android:layout_gravity="bottom|end" android:layout_marginEnd="@dimen/d16" android:layout_marginBottom="@dimen/d16" + android:fontFamily="@font/sansation_bold" android:text="@string/done" android:textColor="?colorOnTertiary" app:backgroundTint="?colorTertiary" @@ -38,7 +39,8 @@ android:paddingTop="@dimen/d16" android:text="@string/tasker_select_list" android:textColor="?colorPrimary" - android:textStyle="bold" /> + android:textStyle="bold" + app:fontFamily="@font/sansation_bold" /> Tentar definir o melhor tipo de entrada para o teclado com base no valor do parâmetro Acesso root não encontrado. Você só pode editar parâmetros com acesso root. Informação - Nenhuma informação disponível para este parâmetro + Nenhuma documentação disponível para este parâmetro Pesquisar Aplicando seus parâmetros Notificação de inicialização @@ -119,4 +119,51 @@ Usar cores dinâmicas (Tema Monet) quando disponível Forçar tema escuro Forçar o uso do tema escuro quando disponível + Favoritos + Predefinições + A criação do arquivo foi cancelada ou falhou + A seleção de arquivo foi cancelada ou falhou + Ocorreu um erro ao abrir o arquivo + Ocorreu um erro ao processar o arquivo + Parâmetro excluído: %s + Voltar + Ler a documentação para \"%1$s\" + Diretório: %1$s + Parâmetro: %1$s + Ícone de parâmetro + Navegar para o diretório + Marcado como favorito + Alternar %1$s + Favoritar + Alternar parâmetro do Tasker + Valor aplicado com sucesso + Copiado para a área de transferência + Pressione e segure para copiar + Lista do Tasker: %1$s + Novo valor + Ler mais + Documentação + Valor do parâmetro + Aplicando predefinição + %1$d parâmetros encontrados + Importar + Carregando predefinição… + Sucesso + Predefinições importadas com sucesso + Importar predefinições de um arquivo + Exportar predefinições para um arquivo + Pesquisar parâmetros do kernel + Nenhuma sugestão disponível + Nenhum resultado encontrado para \"%1$s\" + Insira uma consulta para pesquisar parâmetros do kernel + Ícone de pesquisa + Limpar pesquisa + Pesquisas recentes + Item do histórico + Limpar item do histórico + Exportação concluída + Falha ao importar parâmetros + Valores em branco não são permitidos atualmente + O valor se recusou a aplicar com o modo de aplicação atual + Falha na execução do comando \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b22598b..37c55b0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -40,7 +40,7 @@ Try to set the best input type for the keyboard based on the value of the parameter Root access not found. You can only edit properties with root access. Information - No info available for this parameter + No documentation available for this parameter Search Start Up Allow to apply parameters on start up @@ -70,7 +70,7 @@ Execution will not delayed Set as favorite Remove from favorites - no favorites selected + No favorites selected Tasker list Primary list Secondary list @@ -124,6 +124,46 @@ File creation cancelled or failed File picking cancelled or failed There was an error while opening the file - There was an error while processing the file\n + There was an error while processing the file Param deleted: %s + Go back + Read documentation for \"%1$s\" + Directory: %1$s + Parameter: %1$s + Parameter icon + Navigate to directory + Marked as favorite + Toggle %1$s + Favorite + Toggle Tasker param + Value applied successfully + Copied to clipboard + Long press to copy + Tasker list: %1$s + New value + Read more + Documentation + Parameter value + Applying preset + %1$d parameters found + Import + Loading preset… + Success + Presets successfully imported + Import presets from a file + Export presets to a file + Search kernel parameters + No suggestions available + No results found for \"%1$s\" + Enter a query to search for kernel parameters + Search Icon + Clear search + Recent searches + History item + Clear history item + Export complete + Failed to import params + Blank values are currently not allowed + Value refused to apply with current commit mode + Command execution failed diff --git a/app/src/main/res/xml/favorites_glance_widget_info.xml b/app/src/main/res/xml/favorites_glance_widget_info.xml index b78fe79..7e03039 100644 --- a/app/src/main/res/xml/favorites_glance_widget_info.xml +++ b/app/src/main/res/xml/favorites_glance_widget_info.xml @@ -2,7 +2,7 @@ - @color/gray_800 - @color/gray_900 + @color/md_theme_light_primary + @color/md_theme_light_onPrimaryContainer @color/gray_700 @color/gray_500 - @color/violet_700 - @color/violet_500 + @color/md_theme_light_tertiary + @color/md_theme_light_tertiaryContainer @color/neutral_300 @color/gray_300 @@ -89,64 +89,64 @@ #192b3b #021219 - #006685 + #4B5C92 #FFFFFF - #BFE9FF - #001F2A - #4D616C + #DBE1FF + #334478 + #595E72 #FFFFFF - #D0E6F3 - #081E27 - #5E5A7D + #DDE1F9 + #414659 + #1D6B50 #FFFFFF - #E4DFFF - #1A1836 + #A7F2D0 + #00513A #BA1A1A #FFDAD6 #FFFFFF - #410002 - #FBFCFE - #191C1E - #FBFCFE - #191C1E - #DCE3E9 - #40484C - #70787D - #F0F1F3 - #2E3133 - #6DD2FF + #93000A + #FAF8FF + #1A1B21 + #FAF8FF + #1A1B21 + #E2E2EC + #45464F + #757680 + #F1F0F7 + #2F3036 + #B4C5FF #000000 - #006685 - #C0C8CD + #4B5C92 + #C5C6D0 #000000 - #6DD2FF - #003547 - #004D65 - #BFE9FF - #B4CAD6 - #1F333D - #364954 - #D0E6F3 - #C7C2EA - #2F2D4C - #464364 - #E4DFFF + #B4C5FF + #1A2D60 + #334478 + #DBE1FF + #C1C5DD + #2B3042 + #414659 + #DDE1F9 + #8CD5B4 + #003827 + #00513A + #A7F2D0 #FFB4AB #93000A #690005 #FFDAD6 - #191C1E - #E1E2E5 - #191C1E - #E1E2E5 - #40484C - #C0C8CD - #8A9297 - #191C1E - #E1E2E5 - #006685 + #121318 + #E3E2E9 + #121318 + #E3E2E9 + #45464F + #C5C6D0 + #8F909A + #2F3036 + #E3E2E9 + #4B5C92 #000000 - #6DD2FF - #40484C + #B4C5FF + #45464F #000000 diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt index f0543d3..68d8269 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.SharedPreferences import android.os.Build import com.androidvip.sysctlgui.data.Prefs +import com.androidvip.sysctlgui.data.R import com.androidvip.sysctlgui.data.utils.RootUtils import com.androidvip.sysctlgui.domain.enums.CommitMode import com.androidvip.sysctlgui.domain.enums.SettingItemType @@ -22,31 +23,35 @@ class AppSettingsRepositoryImpl( override suspend fun getAppSettings(): List> = withContext(ioContext) { val usingDynamicColors = sharedPreferences.getBoolean(Prefs.DynamicColors.key, false) val supportsDynamicColors = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + val currentCommitMode = sharedPreferences.getString( + Prefs.CommitMode.key, + CommitMode.SYSCTL.name.lowercase() + ) ?: CommitMode.SYSCTL.name listOf( /////////// GENERAL SETTINGS //////////// AppSetting( key = Prefs.ListFoldersFirst.key, value = sharedPreferences.getBoolean(Prefs.ListFoldersFirst.key, true), - category = "General", - title = "List folders first", - description = "List folders first when using the kernel parameter browser option", + category = context.getString(R.string.prefs_category_general), + title = context.getString(R.string.prefs_list_folders_first_title), + description = context.getString(R.string.prefs_list_folders_first_description), type = SettingItemType.Switch, ), AppSetting( key = Prefs.GuessInputType.key, value = sharedPreferences.getBoolean(Prefs.GuessInputType.key, true), - category = "General", - title = "Guess input type", - description = "Try to set the best input type for the keyboard based on the value of the parameter", + category = context.getString(R.string.prefs_category_general), + title = context.getString(R.string.prefs_guess_input_type_title), + description = context.getString(R.string.prefs_guess_input_type_description), type = SettingItemType.Switch, ), AppSetting( key = Prefs.UseOnlineDocs.key, value = sharedPreferences.getBoolean(Prefs.UseOnlineDocs.key, true), - category = "General", - title = "Use online docs", - description = "Try to use online documentation when displaying parameter descriptions", + category = context.getString(R.string.prefs_category_general), + title = context.getString(R.string.prefs_online_docs_title), + description = context.getString(R.string.prefs_online_docs_description), type = SettingItemType.Switch, ), @@ -55,42 +60,42 @@ class AppSettingsRepositoryImpl( AppSetting( key = Prefs.ForceDarkTheme.key, value = sharedPreferences.getBoolean(Prefs.ForceDarkTheme.key, false), - category = "Theme", - title = "Force Dark", - description = "Force dark theme when available", + category = context.getString(R.string.prefs_category_theme), + title = context.getString(R.string.prefs_force_dark_title), + description = context.getString(R.string.prefs_force_dark_description), type = SettingItemType.Switch, ), AppSetting( key = Prefs.DynamicColors.key, value = usingDynamicColors, enabled = supportsDynamicColors, - category = "Theme", - title = "Dynamic Colors", - description = "Use dynamic colors when available", + category = context.getString(R.string.prefs_category_theme), + title = context.getString(R.string.prefs_dynamic_colors_title), + description = context.getString(R.string.prefs_dynamic_colors_description), type = SettingItemType.Switch, ), AppSetting( key = Prefs.ContrastLevel.key, enabled = !usingDynamicColors, value = sharedPreferences.getInt(Prefs.ContrastLevel.key, 1), - category = "Theme", - title = "Contrast level", - description = "Contrast level for the theme colors", + category = context.getString(R.string.prefs_category_theme), + title = context.getString(R.string.prefs_contrast_level_title), + description = context.getString(R.string.prefs_contrast_level_description), type = SettingItemType.Slider, - values = listOf(1, 2, 3), + values = listOf(CONTRAST_LEVEL_NORMAL, CONTRAST_LEVEL_MEDIUM, CONTRAST_LEVEL_HEIGH), ), /////////// COMMIT SETTINGS //////////// AppSetting( key = Prefs.CommitMode.key, - value = sharedPreferences.getString( - Prefs.CommitMode.key, - CommitMode.SYSCTL.name.lowercase() - ) ?: "sysctl", - category = "Operations", - title = "Commit mode", - description = "Command used when applying the parameter value", + value = currentCommitMode, + category = context.getString(R.string.prefs_category_operations), + title = context.getString( + R.string.prefs_commit_mode_title_format, + currentCommitMode + ), + description = context.getString(R.string.prefs_commit_mode_description), type = SettingItemType.List, values = listOf( CommitMode.SYSCTL.name.lowercase(), @@ -101,16 +106,16 @@ class AppSettingsRepositoryImpl( key = Prefs.UseBusybox.key, value = sharedPreferences.getBoolean(Prefs.UseBusybox.key, false), enabled = rootUtils.isBusyboxAvailable(), - category = "Operations", - title = "Use busybox", - description = "Use busybox to execute commands", + category = context.getString(R.string.prefs_category_operations), + title = context.getString(R.string.prefs_use_busybox_title), + description = context.getString(R.string.prefs_use_busybox_description), type = SettingItemType.Switch, ), AppSetting( key = Prefs.ALLOW_BLANK.key, value = sharedPreferences.getBoolean(Prefs.ALLOW_BLANK.key, false), - category = "Operations", - title = "Allow blank values", + category = context.getString(R.string.prefs_category_operations), + title = context.getString(R.string.prefs_allow_blank_values_title), type = SettingItemType.Switch, ), @@ -119,28 +124,32 @@ class AppSettingsRepositoryImpl( AppSetting( key = Prefs.RunOnStartup.key, value = sharedPreferences.getBoolean(Prefs.RunOnStartup.key, false), - category = "Startup", - title = "Run on startup", - description = "Allow the application to apply parameters on startup", + category = context.getString(R.string.prefs_category_startup), + title = context.getString(R.string.prefs_run_on_startup_title), + description = context.getString(R.string.prefs_run_on_startup_description), type = SettingItemType.Switch, ), AppSetting( key = Prefs.StartupDelay.key, value = sharedPreferences.getInt(Prefs.StartupDelay.key, 0), - category = "Startup", - title = "Startup delay", - description = "Delay in seconds before applying parameters on startup", + category = context.getString(R.string.prefs_category_startup), + title = context.getString(R.string.prefs_startup_delay_title), + description = context.getString(R.string.prefs_startup_delay_description), type = SettingItemType.Slider, values = (0..10).toList(), ), AppSetting( key = "", value = Unit, - category = "Startup", - title = "Manage parameters", - description = "Manage the parameters that will be applied at startup", + category = context.getString(R.string.prefs_category_startup), + title = context.getString(R.string.prefs_manage_parameters_title), + description = context.getString(R.string.prefs_manage_parameters_description), type = SettingItemType.Text, ) ) } } + +const val CONTRAST_LEVEL_NORMAL = 1 +const val CONTRAST_LEVEL_MEDIUM = 2 +const val CONTRAST_LEVEL_HEIGH = 3 diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt index b5f272b..ee6f3ea 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/DocumentationRepositoryImpl.kt @@ -6,6 +6,7 @@ import com.androidvip.sysctlgui.domain.models.ParamDocumentation import com.androidvip.sysctlgui.domain.repository.AppPrefs import com.androidvip.sysctlgui.domain.repository.DocumentationRepository import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.withTimeoutOrNull /** * Repository for fetching documentation for kernel parameters. @@ -26,9 +27,9 @@ class DocumentationRepositoryImpl( online: Boolean ): ParamDocumentation? { return if (online) { - withTimeout(REQUEST_TIMEOUT_MS) { + withTimeoutOrNull(REQUEST_TIMEOUT_MS) { onlineDataSource.getDocumentation(param) - } + } ?: offlineDataSource.getDocumentation(param) } else { offlineDataSource.getDocumentation(param) } diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/source/OnlineDocumentationDataSource.kt b/data/src/main/java/com/androidvip/sysctlgui/data/source/OnlineDocumentationDataSource.kt index 43f05fa..c160563 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/source/OnlineDocumentationDataSource.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/source/OnlineDocumentationDataSource.kt @@ -104,14 +104,17 @@ class OnlineDocumentationDataSource( private fun String.optimizedDocumentationHtml(): String { return this.trimIndent() .replace("
    ", "")
    -            .replace("
    ", "") // For "code" blocks - .replace("", "") - .replace("", "") // For code tags - .replace("
  • ", "

  • ") - .replace("

  • ", "") // For spaced bullet points - .replace("

    ", "

    ") // For line breaks in paragraphs - .removeSuffix("
    ") // Remove the last line break + .replace("

    ", "") // For "code" blocks + .replace( + " + "", // Handles + "" + ) + .replace("", "") // Universal closer for the above + .removeSuffix("
    ") } companion object { diff --git a/data/src/main/res/values-pt-rBR/strings.xml b/data/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000..1e00c7a --- /dev/null +++ b/data/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,30 @@ + + + General + Listar pastas primeiro + Listar as pastas primeiro ao usar a opção do navegador de parâmetros do kernel + Tipo de entrada de palpite + Tentar definir o melhor tipo de entrada para o teclado virtual com base no valor do parâmetro + Usar documentação online + Tente usar documentação online ao exibir a descrições dos parâmetros + Tema + Forçar Modo Escuro + Forçar tema escuro quando disponível + Cores Dinâmicas + Usar cores dinâmicas quando disponíveis + Nível de contraste + Nível de contraste para as cores do tema + Operações + Modo de aplicação (%s) + Comando usado ao aplicar o valor do parâmetro + Usar busybox + Use o busybox para executar comandos, se disponível + Permitir valores em branco + Inicialização + Executar na inicialização + Permitir que o aplicativo aplique parâmetros na inicialização + Atraso na inicialização + Atraso em segundos antes de reaplicar os parâmetros na inicialização + Gerenciar parâmetros + Gerenciar os parâmetros que serão aplicados na inicialização + \ No newline at end of file diff --git a/data/src/main/res/values/strings.xml b/data/src/main/res/values/strings.xml new file mode 100644 index 0000000..1d0666e --- /dev/null +++ b/data/src/main/res/values/strings.xml @@ -0,0 +1,30 @@ + + + General + List folders first + List folders first when using the kernel parameter browser option + Guess input type + Try to set the best input type for the soft keyboard based on the value of the parameter + Use online documentation + Try to use online documentation when displaying parameter descriptions + Theme + Force Dark + Force dark theme when available + Dynamic Colors + Use dynamic colors when available + Contrast level + Contrast level for the theme colors + Operations + Commit mode (%s) + Command used when applying the parameter value + Use busybox + Use busybox to execute commands, if available + Allow blank values + Startup + Run on startup + Allow the application to apply parameters on startup + Startup delay + Delay in seconds before applying parameters on startup + Manage parameters + Manage the parameters that will be applied at startup + diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/ParamsRepository.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/ParamsRepository.kt index 79849aa..062e1fc 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/ParamsRepository.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/ParamsRepository.kt @@ -1,7 +1,7 @@ package com.androidvip.sysctlgui.domain.repository -import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.domain.enums.CommitMode +import com.androidvip.sysctlgui.domain.models.KernelParam import kotlinx.coroutines.flow.Flow import java.io.File @@ -62,8 +62,4 @@ interface ParamsRepository { * @return A list of [KernelParam] objects found in the given path. */ fun getParamsFromPath(path: String): Flow> - - companion object { - const val DEFAULT_ERROR_MESSAGE = "error" - } } From bb30156b951803b8de196284b464c9023ce56407 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Sun, 17 Aug 2025 11:44:18 -0300 Subject: [PATCH 14/23] bugfix: react to changed theme settings --- .../sysctlgui/ui/main/MainActivity.kt | 47 ++++++++++++------- .../sysctlgui/ui/main/MainViewModel.kt | 34 +++++++++++++- .../sysctlgui/ui/main/MainViewState.kt | 7 +++ .../sysctlgui/ui/settings/SettingsScreen.kt | 1 - .../ui/settings/SettingsViewModel.kt | 14 ++++-- .../components/TextSettingComponent.kt | 42 ++++++++--------- .../sysctlgui/design/theme/Theme.kt | 6 ++- .../sysctlgui/data/repository/AppPrefsImpl.kt | 17 +++++++ .../sysctlgui/domain/repository/AppPrefs.kt | 3 ++ 9 files changed, 126 insertions(+), 45 deletions(-) diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt index 9253425..01a668d 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt @@ -1,6 +1,7 @@ package com.androidvip.sysctlgui.ui.main import android.app.NotificationManager +import android.content.res.Configuration import android.graphics.Color import android.os.Build import android.os.Bundle @@ -11,7 +12,10 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.core.os.postDelayed +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.androidvip.sysctlgui.core.navigation.UiRoute import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme import com.androidvip.sysctlgui.domain.enums.Actions @@ -20,31 +24,26 @@ import org.koin.android.ext.android.inject class MainActivity : ComponentActivity() { private val prefs: AppPrefs by inject() + private val mainViewModel: MainViewModel by inject() private val notificationPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { _ -> } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - enableEdgeToEdge( - statusBarStyle = SystemBarStyle.auto( - lightScrim = Color.TRANSPARENT, - darkScrim = Color.TRANSPARENT, - detectDarkMode = { resources -> - prefs.forceDark || - resources.configuration.uiMode and - android.content.res.Configuration.UI_MODE_NIGHT_MASK == - android.content.res.Configuration.UI_MODE_NIGHT_YES - } - ) - ) + updateEdgeToEdgeConfiguration(prefs.forceDark) setContent { - // TODO: Make the switch dynamic with the view model - // TODO: Translations + val themeState by mainViewModel.themeState.collectAsStateWithLifecycle() + val forceDark = themeState.forceDark + + LaunchedEffect(forceDark) { + updateEdgeToEdgeConfiguration(forceDark) + } + SysctlGuiTheme( - darkTheme = prefs.forceDark || isSystemInDarkTheme(), - contrastLevel = prefs.contrastLevel, - dynamicColor = prefs.dynamicColors + darkTheme = forceDark || isSystemInDarkTheme(), + contrastLevel = themeState.contrastLevel, + dynamicColor = themeState.dynamicColors ) { MainScreen(startDestination = getRouteFromIntent()) } @@ -55,6 +54,20 @@ class MainActivity : ComponentActivity() { } } + private fun updateEdgeToEdgeConfiguration(forceDark: Boolean) { + enableEdgeToEdge( + statusBarStyle = SystemBarStyle.auto( + lightScrim = Color.TRANSPARENT, + darkScrim = Color.TRANSPARENT, + detectDarkMode = { resources -> + val isSystemDark = resources.configuration.uiMode and + Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES + forceDark || isSystemDark + } + ) + ) + } + private fun getRouteFromIntent(): UiRoute { val extraDestination = intent.getStringExtra(EXTRA_DESTINATION) ?: return UiRoute.BrowseParams diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewModel.kt index cccd608..81950f6 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewModel.kt @@ -1,9 +1,39 @@ package com.androidvip.sysctlgui.ui.main import androidx.compose.material3.SnackbarResult +import androidx.lifecycle.viewModelScope +import com.androidvip.sysctlgui.data.Prefs +import com.androidvip.sysctlgui.data.repository.CONTRAST_LEVEL_NORMAL +import com.androidvip.sysctlgui.domain.repository.AppPrefs import com.androidvip.sysctlgui.utils.BaseViewModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn -class MainViewModel : BaseViewModel() { +class MainViewModel( + private val appPrefs: AppPrefs +) : BaseViewModel() { + private val themeSettingsFlow: Flow = combine( + appPrefs.observeKey(Prefs.ForceDarkTheme.key, false), + appPrefs.observeKey(Prefs.DynamicColors.key, false), + appPrefs.observeKey(Prefs.ContrastLevel.key, CONTRAST_LEVEL_NORMAL) + ) { forceDark, dynamicColors, contrastLevel -> + ThemeSettings(forceDark, dynamicColors, contrastLevel) + } + val themeState = themeSettingsFlow.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = loadInitialThemeState() + ) + + private fun loadInitialThemeState(): ThemeSettings { + return ThemeSettings( + forceDark = appPrefs.forceDark, + contrastLevel = appPrefs.contrastLevel, + dynamicColors = appPrefs.dynamicColors + ) + } override fun createInitialState() = MainViewState() @@ -12,9 +42,11 @@ class MainViewModel : BaseViewModel { setState { event.newState } } + is MainViewEvent.ShowSnackbarRequested -> { setEffect { MainViewEffect.ShowSnackbar(event.message, event.actionLabel) } } + is MainViewEvent.OnSnackbarResult -> { val snackbarResult = event.result if (snackbarResult == SnackbarResult.ActionPerformed) { diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt index da18a59..5c634e7 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt @@ -1,6 +1,7 @@ package com.androidvip.sysctlgui.ui.main import androidx.compose.material3.SnackbarResult +import com.androidvip.sysctlgui.data.repository.CONTRAST_LEVEL_NORMAL data class MainViewState( val topBarTitle: String = "SysctlGUI", @@ -10,6 +11,12 @@ data class MainViewState( val showSearchAction: Boolean = true ) +data class ThemeSettings( + val forceDark: Boolean = false, + val dynamicColors: Boolean = false, + val contrastLevel: Int = CONTRAST_LEVEL_NORMAL +) + sealed interface MainViewEffect { data class ShowSnackbar(val message: String, val actionLabel: String? = null) : MainViewEffect data object ActUponSckbarActionPerformed : MainViewEffect diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt index 3a26724..792d189 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt @@ -70,7 +70,6 @@ internal fun SettingsScreen( } ) - LaunchedEffect(Unit) { mainViewModel.onEvent( MainViewEvent.OnSateChangeRequested( diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsViewModel.kt index 0828835..0968098 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsViewModel.kt @@ -19,8 +19,7 @@ class SettingsViewModel( init { viewModelScope.launch { - val settings = getSettings() - setState { copy(settings = settings) } + loadSettings() } } @@ -62,7 +61,16 @@ class SettingsViewModel( if (event.appSetting.key == Prefs.RunOnStartup.key) { setEffect { SettingsViewEffect.RequestNotificationPermission } } + + viewModelScope.launch { + loadSettings() + } } } } -} \ No newline at end of file + + private suspend fun loadSettings() { + val settings = getSettings() + setState { copy(settings = settings) } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/TextSettingComponent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/TextSettingComponent.kt index f8b1071..328dbeb 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/TextSettingComponent.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/TextSettingComponent.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme import com.androidvip.sysctlgui.domain.enums.SettingItemType @@ -46,7 +47,6 @@ fun TextSettingComponent( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(space = 16.dp) ) { - Box( modifier = Modifier .align(Alignment.CenterVertically) @@ -62,27 +62,27 @@ fun TextSettingComponent( modifier = Modifier.fillMaxWidth() ) } - } - - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - appSetting.values?.forEach { item -> - DropdownMenuItem( - text = { - Text( - text = item as String, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface - ) - }, - onClick = { - onValueChange(item as String) - expanded = false - } - ) + DropdownMenu( + expanded = expanded, + offset = DpOffset(16.dp, (-32).dp), + onDismissRequest = { expanded = false } + ) { + appSetting.values?.forEach { item -> + DropdownMenuItem( + text = { + Text( + text = item as String, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface + ) + }, + onClick = { + onValueChange(item as String) + expanded = false + } + ) + } } } } diff --git a/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Theme.kt b/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Theme.kt index 69a1120..4268a32 100644 --- a/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Theme.kt +++ b/common/design/src/main/java/com/androidvip/sysctlgui/design/theme/Theme.kt @@ -2,7 +2,8 @@ package com.androidvip.sysctlgui.design.theme import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.MaterialExpressiveTheme import androidx.compose.material3.darkColorScheme import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme @@ -238,6 +239,7 @@ private val highContrastDarkColorScheme = darkColorScheme( surfaceContainerHighest = surfaceContainerHighestDarkHighContrast, ) +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SysctlGuiTheme( darkTheme: Boolean = isSystemInDarkTheme(), @@ -264,7 +266,7 @@ fun SysctlGuiTheme( } } - MaterialTheme( + MaterialExpressiveTheme( colorScheme = colorScheme, typography = Typography, content = content diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppPrefsImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppPrefsImpl.kt index 4528454..1ef8f3f 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppPrefsImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppPrefsImpl.kt @@ -4,11 +4,26 @@ import android.content.SharedPreferences import androidx.core.content.edit import com.androidvip.sysctlgui.data.Prefs import com.androidvip.sysctlgui.domain.repository.AppPrefs +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow /** * Implementation of [AppPrefs] that uses [SharedPreferences] to store and retrieve app preferences. */ class AppPrefsImpl(private val prefs: SharedPreferences) : AppPrefs { + @Suppress("UNCHECKED_CAST") + override fun observeKey(key: String, default: T): Flow = callbackFlow { + val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, updatedKey -> + if (updatedKey == key) { + trySend(prefs.all[key] as T ?: default) + } + } + prefs.registerOnSharedPreferenceChangeListener(listener) + trySend(prefs.all[key] as T ?: default) + awaitClose { prefs.unregisterOnSharedPreferenceChangeListener(listener) } + } + override var listFoldersFirst: Boolean get() = prefs.getBoolean(Prefs.ListFoldersFirst.key, true) set(value) { @@ -88,4 +103,6 @@ class AppPrefsImpl(private val prefs: SharedPreferences) : AppPrefs { currentHistory.remove(query) prefs.edit { putStringSet(Prefs.SearchHistory.key, currentHistory) } } + + } diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/AppPrefs.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/AppPrefs.kt index 251b8cc..16f7f7d 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/AppPrefs.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/repository/AppPrefs.kt @@ -1,5 +1,7 @@ package com.androidvip.sysctlgui.domain.repository +import kotlinx.coroutines.flow.Flow + /** * Interface for accessing and modifying application preferences. * @@ -7,6 +9,7 @@ package com.androidvip.sysctlgui.domain.repository * allowing various parts of the app to read and write preference values. */ interface AppPrefs { + fun observeKey(key: String, default: T): Flow var listFoldersFirst: Boolean var guessInputType: Boolean var commitMode: String From 0d3d1021796729070bd96410121a94d59276becd Mon Sep 17 00:00:00 2001 From: Lennoard Date: Sun, 17 Aug 2025 15:25:07 -0300 Subject: [PATCH 15/23] bugfix: removed unused resources --- app/build.gradle.kts | 4 +- .../sysctlgui/ui/main/MainActivity.kt | 1 + .../sysctlgui/ui/search/SearchScreen.kt | 2 +- .../sysctlgui/ui/user/UserParamsScreen.kt | 4 +- .../widgets/FavoritesGlanceWidget.kt | 6 +- .../main/res/drawable/ic_backup_params.xml | 9 -- .../main/res/drawable/ic_export_params.xml | 9 -- .../res/drawable/ic_favorite_selected.xml | 9 -- .../res/drawable/ic_favorite_unselected.xml | 9 -- .../res/drawable/ic_file_import_outline.xml | 8 -- .../main/res/drawable/ic_import_params.xml | 9 -- .../main/res/drawable/ic_restore_params.xml | 9 -- .../list_item_kernel_param_widget_list.xml | 29 ------- app/src/main/res/values-de/arrays.xml | 6 -- app/src/main/res/values-de/strings.xml | 66 --------------- app/src/main/res/values-land/bools.xml | 4 - app/src/main/res/values-land/dimens.xml | 4 - app/src/main/res/values-pt-rBR/arrays.xml | 6 -- app/src/main/res/values-pt-rBR/strings.xml | 70 +--------------- .../main/res/values-sw600dp-land/bools.xml | 4 - .../main/res/values-sw600dp-land/dimens.xml | 4 - app/src/main/res/values-sw600dp/bools.xml | 4 - app/src/main/res/values-sw600dp/dimens.xml | 4 - app/src/main/res/values-tr/strings.xml | 66 --------------- app/src/main/res/values/arrays.xml | 10 --- app/src/main/res/values/attrs.xml | 7 -- app/src/main/res/values/bools.xml | 4 - app/src/main/res/values/dimens.xml | 10 --- app/src/main/res/values/strings.xml | 70 +--------------- common/design/src/main/res/values/colors.xml | 82 +------------------ data/src/main/res/values-pt-rBR/strings.xml | 6 +- 31 files changed, 21 insertions(+), 514 deletions(-) delete mode 100644 app/src/main/res/drawable/ic_backup_params.xml delete mode 100644 app/src/main/res/drawable/ic_export_params.xml delete mode 100644 app/src/main/res/drawable/ic_favorite_selected.xml delete mode 100644 app/src/main/res/drawable/ic_favorite_unselected.xml delete mode 100644 app/src/main/res/drawable/ic_file_import_outline.xml delete mode 100644 app/src/main/res/drawable/ic_import_params.xml delete mode 100644 app/src/main/res/drawable/ic_restore_params.xml delete mode 100644 app/src/main/res/layout/list_item_kernel_param_widget_list.xml delete mode 100644 app/src/main/res/values-de/arrays.xml delete mode 100644 app/src/main/res/values-land/bools.xml delete mode 100644 app/src/main/res/values-land/dimens.xml delete mode 100644 app/src/main/res/values-pt-rBR/arrays.xml delete mode 100644 app/src/main/res/values-sw600dp-land/bools.xml delete mode 100644 app/src/main/res/values-sw600dp-land/dimens.xml delete mode 100644 app/src/main/res/values-sw600dp/bools.xml delete mode 100644 app/src/main/res/values-sw600dp/dimens.xml delete mode 100644 app/src/main/res/values/attrs.xml delete mode 100644 app/src/main/res/values/bools.xml delete mode 100644 app/src/main/res/values/dimens.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cbf9b2a..2e4c304 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,7 +20,9 @@ android { versionCode = 16 versionName = "2.2.2" vectorDrawables.useSupportLibrary = true - resourceConfigurations.addAll(listOf("en", "de", "pt-rBR")) + androidResources { + localeFilters += listOf("en", "de", "pt-rBR", "tr") + } javaCompileOptions { annotationProcessorOptions { arguments += mapOf( diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt index 01a668d..08cc9cd 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt @@ -40,6 +40,7 @@ class MainActivity : ComponentActivity() { updateEdgeToEdgeConfiguration(forceDark) } + // TODO: Landscape Support SysctlGuiTheme( darkTheme = forceDark || isSystemInDarkTheme(), contrastLevel = themeState.contrastLevel, diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt index 71b6bad..d376049 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt @@ -318,7 +318,7 @@ private fun SearchViewContent( if (suggestionHints.isNotEmpty()) { item { Text( - text = "Suggestions", + text = stringResource(R.string.suggestions), style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Bold, diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt index 8043c5d..e32aa7c 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -144,9 +145,8 @@ private fun FavoritesScreenContent( tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(128.dp) ) - // TODO: Empty state image Text( - text = "No favorites added", + text = stringResource(R.string.empty_favorites_widget), style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.onBackground, textAlign = TextAlign.Center, diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidget.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidget.kt index 0ff68df..f4ea51e 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidget.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/widgets/FavoritesGlanceWidget.kt @@ -2,6 +2,7 @@ package com.androidvip.sysctlgui.widgets import android.content.Context import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.glance.GlanceId @@ -25,6 +26,7 @@ import androidx.glance.text.FontWeight import androidx.glance.text.Text import androidx.glance.text.TextStyle import androidx.glance.unit.ColorProvider +import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.design.theme.onPrimaryContainerLight import com.androidvip.sysctlgui.design.theme.primaryContainerLight import com.androidvip.sysctlgui.design.theme.primaryLight @@ -54,7 +56,7 @@ class FavoritesGlanceWidget : GlanceAppWidget(), KoinComponent { horizontalAlignment = Alignment.Horizontal.Start ) { Text( - text = "Favorite Kernel Params", + text = stringResource(R.string.favorite_widget_title), style = TextStyle( fontWeight = FontWeight.Bold, fontSize = 16.sp, @@ -65,7 +67,7 @@ class FavoritesGlanceWidget : GlanceAppWidget(), KoinComponent { Spacer(GlanceModifier.height(8.dp)) if (params.isEmpty()) { - Text("No favorite parameters yet.") + Text(stringResource(R.string.empty_favorites_widget)) } else { LazyColumn { items(params) { param -> diff --git a/app/src/main/res/drawable/ic_backup_params.xml b/app/src/main/res/drawable/ic_backup_params.xml deleted file mode 100644 index b6e306a..0000000 --- a/app/src/main/res/drawable/ic_backup_params.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_export_params.xml b/app/src/main/res/drawable/ic_export_params.xml deleted file mode 100644 index 4556bb7..0000000 --- a/app/src/main/res/drawable/ic_export_params.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_favorite_selected.xml b/app/src/main/res/drawable/ic_favorite_selected.xml deleted file mode 100644 index 7c78dca..0000000 --- a/app/src/main/res/drawable/ic_favorite_selected.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_favorite_unselected.xml b/app/src/main/res/drawable/ic_favorite_unselected.xml deleted file mode 100644 index beb8d66..0000000 --- a/app/src/main/res/drawable/ic_favorite_unselected.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_file_import_outline.xml b/app/src/main/res/drawable/ic_file_import_outline.xml deleted file mode 100644 index a03b753..0000000 --- a/app/src/main/res/drawable/ic_file_import_outline.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_import_params.xml b/app/src/main/res/drawable/ic_import_params.xml deleted file mode 100644 index fbc9092..0000000 --- a/app/src/main/res/drawable/ic_import_params.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_restore_params.xml b/app/src/main/res/drawable/ic_restore_params.xml deleted file mode 100644 index 20c7b56..0000000 --- a/app/src/main/res/drawable/ic_restore_params.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/layout/list_item_kernel_param_widget_list.xml b/app/src/main/res/layout/list_item_kernel_param_widget_list.xml deleted file mode 100644 index b02125f..0000000 --- a/app/src/main/res/layout/list_item_kernel_param_widget_list.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/values-de/arrays.xml b/app/src/main/res/values-de/arrays.xml deleted file mode 100644 index 2749019..0000000 --- a/app/src/main/res/values-de/arrays.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - sysctl -w verwenden - echo \'value\' > /proc/sys/… verwenden - - \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index fb984c8..f28c3e7 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -2,74 +2,28 @@ Überprüfe Rootstatus Überprüfe busybox - Prüfe auf ein altes Datenbankschema - Führe Datenbankmigration aus Fehler Einstellungen Über - Variabeln anzeigen - Kernel-Parameter in einer Liste anzeigen und verändern - Sysctl Operationen - Werte werden aus der Datei gelesen - Verwenden sie diese Option um aus einer .conf oder .json Datei die Parameter zu verwenden - Favoriten anzeigen - Erreichen sie ihre favorisierten Kernel-Parameter mit dieser Liste Beenden Kernel Parameter Variabeln durchstöbern In /proc/sys nach einem bestimmten Kernel-Parameter stöbern. Parameter verändern - Oh nein! Ein Fehler ist aufgetretten. - Überprüfen sie ihre Eingaben - Fehlgeschlagen - Ungültiger Pfad - Sysctl - Mit Sysctl können Kernel-Parameter zur Laufzeit geändert werden. Die verfügbaren Parameter verden aus /proc/sys bestimmt. - Mit dieser App soll eine graphische Möglichkeit geschaffen werden diese Parameter zu ändern. Der Quellcode ist einsehbar auf
    GitHub. Support finden sie im XDA Thread.
    - Ordner zuerst anzeigen - Beim Durchstöbern der Kernel-Parameter werden Ordner zuerst angezeigt - Anwendung - Operationen - Leerstehende Werte zulassen - Übernahmemodus - Eingabetyp erraten Fertig - Zur Gewährleistung von Kompatibilität wird Busybox verwendet - Busybox verwenden - Zurzeit wird sysctl verwendet - Anhand des Parameterwertes wird versucht den Eingabetyp der Tastatur zu setzten Rootzugriff verweigert. Werte können nur mit Rootzugriff geändert werden. - Information Keine Informationen für den Parameter verfügbar Suche - Systemstart - Beim Systemstart die Parameter übernehmen wende Parameter an Ihre Parameter werden angewendet Wende in %d Sekunden an… Startbenachrichtigung Ihre Parameter werden angewendet - Parameter verwalten - Verwalte Parameter die beim Systemstart angewandt werden - Exportiere Parameter - Exportiert die beim Start angewandeten Parameter - Keine Parameter gefunden - Parameter wiederherstellen - Stellt die Parameter von einem vorherigen Backup wieder her Importieren von Parametern ist fehlgeschlagen - Ungültige oder falsch formatierte JSON-Datei - Datei konnte nicht geöffnet werden: ungültiger Dateityp. Import ist nicht möglich: die Datei ist leer. Import ist nicht möglich: die Datei enthält einen Formatierungsfehler. - %d Parameter angewendet - Dokumentation öffnen Bearbeiten Löschen - Startup Verzögerung - Die Ausführung wird auf %d Sekunden verzögert - Ausführung findet sofort statt - Als Favorit auswählen - Aus Favoriten entfernen keine Favoriten ausgewählt Tasker Liste Primäre Liste @@ -80,43 +34,23 @@ Beim Systemstart angewendete Liste SysctlGUI: Tasker Profil #%d angewendet Wähle Tasker Liste aus - Lösche von Tasker Liste #%d - #%d wurde zur Tasker Liste hinzugefügt Wähle eine Liste aus die mit Tasker verwendet werden soll Wenn Tasker dieses Plugin ausführt, werden alle Parameter dieser Liste mit ihren Werten angewendet. Tasker Einstellungen - Zu Tasker hinzufügen - Aus Tasker entfernen - Es ist ein Fehler beim Anwenden der Parameter: %s aufgetreten Rückgängig Sysctl GUI starten Sysctl GUI beim Systemstart ausführen Kein Rootzugriff Rootzugriff wird benötigt - Parameter importieren - Parameter Backup - Erstellt ein Backup von allen Laufzeit Parametern. Nicht alle können bei der Wiederhestellung angewendet werden. - Parameter konnten nicht exportiert werden Aufgrund eines IO Fehlers ist der Export der Parameter fehlgeschlagen Exportieren der Parmameter ist fehlgeschlagen: keine Parameter gefunden Export Optionen - Parameter wurden erfolgreich exportiert Kernel Parameter importieren, exportieren oder ein Backup erstellen - Bitte warten, dies kann eine Weile dauern… Browse Export - Param list Try again Anwenden Wiederhestellen Parameter Wert - Zuletzt angewendeter Wert - Aktueller Wert - Der ausgewählte Wert konnte nicht angewendet werden - Der ausgewählte Wert konnte nicht angewendet werden. Versuche \"echo\" Modus. - Dynamische Farben - Dynamische Farben (Monet Theme) verwenden wenn möglich - Dark Theme erzwingen - Dark Theme erzwingen wenn möglich diff --git a/app/src/main/res/values-land/bools.xml b/app/src/main/res/values-land/bools.xml deleted file mode 100644 index 65b37a3..0000000 --- a/app/src/main/res/values-land/bools.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - true - \ No newline at end of file diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml deleted file mode 100644 index c946c4a..0000000 --- a/app/src/main/res/values-land/dimens.xml +++ /dev/null @@ -1,4 +0,0 @@ - - 120dp - @dimen/d32 - diff --git a/app/src/main/res/values-pt-rBR/arrays.xml b/app/src/main/res/values-pt-rBR/arrays.xml deleted file mode 100644 index 2721cdd..0000000 --- a/app/src/main/res/values-pt-rBR/arrays.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - Usar sysctl -w - Usar echo \'valor\' > /proc/sys/… - - \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 0901803..41b8666 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1,77 +1,33 @@ Sobre - Permitir valores em branco - Aplicativo Procurar variáveis Procure em /proc/sys um parâmetro específico do kernel - Modo de aplicação Pronto Editar parâmetros - Por favor, verifique os campos de entrada Erro Sair - Falhou - Adivinhar tipo de entrada - Caminho inválido Parâmetros do kernel - Listar pastas primeiro - Listar pastas primeiro ao usar a opção de navegador de parâmetros do kernel - Operações - Ler valores do arquivo - Use esta opção para carregar parâmetros de um arquivo .conf ou .json selecionado Configurações - Mostrar variáveis - Exibir e editar todos os parâmetros do kernel em uma lista Verificando o busybox Verificando acesso root - Sysctl - Sysctl é usado para modificar os parâmetros do kernel no tempo de execução. Os parâmetros disponíveis são aqueles listados em /proc/sys. - Operações Sysctl - Algo de errado não está certo - Usar busybox - Busybox será usado para garantir a compatibilidade - O objetivo principal deste aplicativo é fornecer uma maneira gráfica de editar esses parâmetros. Você pode encontrar o código fonte no GitHub. Suporte no XDA Thread. - Atualmente confiando no binário sysctl do sistema - Tentar definir o melhor tipo de entrada para o teclado com base no valor do parâmetro Acesso root não encontrado. Você só pode editar parâmetros com acesso root. - Informação Nenhuma documentação disponível para este parâmetro Pesquisar Aplicando seus parâmetros Notificação de inicialização Aplicando seus parâmetros Aplicando parâmetros - Inicialização - Permitir a reaplicação de parâmetros na inicialização - Gerenciar parâmetros - Gerenciar os parâmetros que serão aplicados na inicialização - Nenhum parâmetro encontrado - Exportar parâmetros - Exportar os parâmetros que você alterou usando este aplicativo para um arquivo JSON - Não foi possível abrir o arquivo: tipo de arquivo inválido. Não foi possível importar os parâmetros: existem erros de formatação no arquivo. Não foi possível importar os parâmetros: o arquivo está vazio. - Restaurar parâmetros - Arquivo JSON inválido ou mal formado Falha ao importar parâmetros - %d parâmetro(s) aplicados - Abrir documentação Remover Editar - Atraso de inicialização - A execução será atrasada em %d segundos - A execução não será atrasada Aplicando em %d segundos… - Mostrar favoritos - Acesse os seus parâmetros favoritos a partir desta lista - Definir como favorito - nenhum favorito selecionado + Nenhum favorito adicionado Lista do Tasker SysctlGUI: Perfil Tasker #%d aplicado Selecione a lista do Tasker - Removido da lista do Tasker #%d - Adicionado à lista do Tasker #%d Selecione uma lista para ser usada com o Tasker Uma vez acionado pelo Tasker, o plugin reaplicará imediatamente todos os parâmetros da lista selecionada com seus respectivos valores. Configurações do Tasker @@ -81,44 +37,22 @@ Lista secundária do Tasker Lista de favoritos Lista de aplicar na inicialização - Adicionar ao Tasker - Falha ao aplicar: %s Desfazer - Migrando banco de dados - Verificando esquema de banco de dados antigo Acesso root necessário Requer acesso root Executar o SysctlGUI na inicialização Iniciar o SysctlGUI - Fazer backup dos parâmetros - Fazer backup de todos os parêmetros em runtime atuais. Por favor, note que nem todos os parâmetros podem ser reaplicados novamente, uma vez restaurados. - Importar parâmetros - Restaurar a sua cópia de segurança anterior Opções de exportação - Parâmetros exportados com sucesso Falha: nenhum parâmetro encontrado A exportação de parâmetros falhou devido a um erro do armazenamento - Falha ao exportar parâmetros Importar, exportar ou fazer backup dos parâmetros do kernel - Por favor aguarde, isto pode demorar algum tempo… Nevegar Exportar - Lista de parâmetros Tentar novamente Aplicar Restaurar Parâmetro Valor - Valor aplicado - Valor atual - Remover do Tasker - Remover dos favoritos - O valor selecionado não pôde ser aplicado - O valor selecionado não pôde ser aplicado. Tente usar o modo \"echo\". - Cores dinâmicas - Usar cores dinâmicas (Tema Monet) quando disponível - Forçar tema escuro - Forçar o uso do tema escuro quando disponível Favoritos Predefinições A criação do arquivo foi cancelada ou falhou @@ -166,4 +100,6 @@ Valores em branco não são permitidos atualmente O valor se recusou a aplicar com o modo de aplicação atual Falha na execução do comando + Sugestões + Parâmetros favoritos \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp-land/bools.xml b/app/src/main/res/values-sw600dp-land/bools.xml deleted file mode 100644 index 65b37a3..0000000 --- a/app/src/main/res/values-sw600dp-land/bools.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - true - \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp-land/dimens.xml b/app/src/main/res/values-sw600dp-land/dimens.xml deleted file mode 100644 index 9c117c3..0000000 --- a/app/src/main/res/values-sw600dp-land/dimens.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - 72dp - \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp/bools.xml b/app/src/main/res/values-sw600dp/bools.xml deleted file mode 100644 index 2d2cf03..0000000 --- a/app/src/main/res/values-sw600dp/bools.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - false - \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml deleted file mode 100644 index 513920d..0000000 --- a/app/src/main/res/values-sw600dp/dimens.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - @dimen/d32 - \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 02d9158..94fc7d3 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1,74 +1,28 @@ Kök erişimi denetleniyor Busybox kontrol ediliyor - Eski veritabanı şeması kontrol ediliyor - Veritabanı taşıma gerçekleştiriliyor Hata Ayarlar Hakkında - Değişkenleri göster - Tüm çekirdek parametrelerini bir listede görüntüleme ve düzenleme - Sysctl işlemleri - Dosyadan değerleri oku - Seçilen bir .conf veya .json dosyasından parametre yüklemek için bu seçeneği kullanın - Favorileri göster - Bu listeden en sevdiğiniz çekirdek parametrelerine erişin Çıkış Çekirdek parametreleri Değişkenlere göz at Belirli bir çekirdek parametresi için /proc/sys üzerine göz atın Parametreleri düzenle - Yanlış olan bir şey doğru değil - Lütfen giriş alanlarını kontrol edin - Başarısız - Geçersiz yol - Sysctl - Sysctl, çalışma zamanında çekirdek parametrelerini değiştirmek için kullanılır. Mevcut parametreler/proc/sys altında listelenen parametrelerdir. - Bu uygulamanın temel amacı, bu parametreleri düzenlemek için grafiksel bir yol sağlamaktır. Kaynak kodunu şurada bulabilirsiniz: GitHub.DestekXDA Başlığı. - Önce klasörleri listele - Çekirdek parametresi tarayıcı seçeneğini kullanırken önce klasörleri listeleyin - Uygulama - Seçenekler - Boş değerlere izin ver - Gönderim modu - Tahmin giriş türü Bitti - Uyumluluğu sağlamak için Busybox kullanılacaktır - Busybox kutusunu kullan - Şu anda sistemin sysctl ikilisine güveniliyor - Parametrenin değerine göre klavye için en iyi giriş türünü ayarlamaya çalışın Kök erişimi bulunamadı. Yalnızca kök erişimi olan özellikleri düzenleyebilirsiniz. - Bilgi Bu parametre için bilgi yok Ara - Başlatma - Başlangıçta parametrelerin uygulanmasına izin ver Parametreler uygulanıyor Parametreleriniz uygulanıyor %d saniye içinde uygulanıyor… Başlatma Bildirimi Parametreleriniz uygulanıyor - Parametreleri yönet - Başlangıçta uygulanacak parametreleri yönetin - Parametreleri dışa aktar - Bu uygulamayı kullanarak değiştirdiğiniz parametreleri bir JSON dosyasına aktarın - Parametre bulunamadı - Parametreleri geri yükle - Önceki parametre yedeklemenizi geri yükleyin Parametreler içe aktarılamadı - Geçersiz veya hatalı biçimlendirilmiş JSON dosyası - Dosya açılamıyor: geçersiz dosya türü. Parametreler içe aktarılamıyor: boş dosya. Parametreler içe aktarılamıyor: dosyada bir biçimlendirme hatası var. - %d parametre uygulandı - Belgeyi aç Düzenle Kaldır - Başlatma gecikmesi - Yürütme %d saniye gecikecek - Yürütme gecikmeyecek - Favori olarak ayarla - Favorilerden kaldır favori seçilmedi Görevli listesi Birincil liste @@ -79,43 +33,23 @@ Önyükleme listesine uygula SysctlGUI: Görev profili #%d uygulandı Bir Görevli listesi seçin - Görevli listesinden kaldırıldı #%d - Görevli listesine #%d eklendi Görevli ile kullanılacak bir liste seçin Görevli tarafından tetiklendikten sonra, eklenti seçilen listedeki tüm parametreleri ilgili değerleriyle birlikte hemen yeniden uygulayacaktır. Görevli ayarları - Görevliye ekle - Görevliden kaldır - Parametreler uygulanamadı: %s Geri al Sysctl GUI\'yi başlat Önyüklemede Sysctl GUI\'yi çalıştır Kök erişimi gerektirir Kök erişimi gerekli - Parametreleri içe aktar - Parametreleri yedekle - Mevcut tüm çalışma zamanı parametrelerini yedekleyin. Bunların tamamının geri yüklendikten sonra tekrar uygulanamayacağını lütfen unutmayın. - Parametreler dışa aktarılamadı GÇ hatası nedeniyle parametre dışa aktarımı başarısız oldu Parametre dışa aktarılamadı: dışa aktarılacak parametre yok Dışa aktarma seçenekleri - Parametreler başarıyla dışa aktarıldı Çekirdek parametrelerini içe aktarın, dışa aktarın veya yedekleyin - Lütfen bekleyin, bu biraz zaman alabilir… Göz at Dışa Aktar - Parametre listesi Tekrar deneyin Uygula Geri Yükle Parametre Değer - Son uygulanan değer - Mevcut değer - Seçilen değer uygulanamadı - Seçilen değer uygulanamadı. \"echo\" modunu kullanmayı deneyin. - Dinamik renkler - Mevcut olduğunda dinamik renkler (Monet teması) kullanın - Karanlık temayı zorla - Mevcut olduğunda koyu temayı zorla \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index fcc996f..e0df010 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1,14 +1,4 @@ - - Use sysctl -w - Use echo \'value\' > /proc/sys/… - - - - sysctl - echo - - @string/tasker_list_primary @string/tasker_list_secondary diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml deleted file mode 100644 index 55f0cef..0000000 --- a/app/src/main/res/values/attrs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/bools.xml b/app/src/main/res/values/bools.xml deleted file mode 100644 index 2d2cf03..0000000 --- a/app/src/main/res/values/bools.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - false - \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml deleted file mode 100644 index ab87823..0000000 --- a/app/src/main/res/values/dimens.xml +++ /dev/null @@ -1,10 +0,0 @@ - - 160dp - @dimen/d16 - @dimen/d16 - - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 37c55b0..e8c6fdf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,75 +2,29 @@ Sysctl GUI Checking for root access Checking for busybox - Checking for old database schema - Performing database migration Error Settings About - Show variables - Display and edit all kernel parameters in a list - Sysctl operations - Read values from file - Use this option to load parameters from a selected .conf or .json file - Show favorites - Access your favorite kernel parameters from this list Exit Kernel parameters Browse variables Browse /proc/sys for a specific kernel parameter Edit parameters - Something wrong is not right - Please check the input fields - Failed - Invalid path - Sysctl - Sysctl is used to modify kernel parameters at runtime. The parameters available are those listed under /proc/sys. - This apps main purpose is to provide a graphical way to edit these parameters. You can find its source code on GitHub. Support on XDA Thread. - List folders first - List folders first when using the kernel parameter browser option - Application - Operations - Allow blank values - Commit mode - Guess input type Done - Busybox will be used to ensure compatibility - Use busybox - Currently relying on system\'s sysctl binary - Try to set the best input type for the keyboard based on the value of the parameter Root access not found. You can only edit properties with root access. - Information No documentation available for this parameter Search - Start Up - Allow to apply parameters on start up Applying parameters Applying your parameters Applying in %d seconds… Start Up Notification Applying your parameters - Manage parameters - Manage the parameters that will be applied at startup - Export parameters - Export parameters that you have altered using this application to a JSON file - No parameters found - Restore parameters - Restore your previous parameter backup Failed to import parameters - Invalid or malformed JSON file - Can\'t open file: invalid file type. Can\'t import params: empty file. Can\'t import params: there is a formatting error in the file. - %d parameter(s) applied - Open documentation Edit Remove - Startup delay - Execution will be delayed by %d seconds - Execution will not delayed - Set as favorite - Remove from favorites - No favorites selected + No favorites added Tasker list Primary list Secondary list @@ -80,45 +34,25 @@ Apply on boot list SysctlGUI: Tasker profile #%d applied Select a Tasker list - Removed from Tasker list #%d - Added to Tasker list #%d Select a list to be used with Tasker Once triggered by Tasker, the plugin will immediately reapply all parameters from the selected list with their respective values. Tasker settings - Add to Tasker - Remove from Tasker - Failed to apply params: %s Undo Start Sysctl GUI Run Sysctl GUI at boot Requires root access Root access required - Import parameters - Backup parameters - Backup all current runtime parameters. Please note that not all of these can be reapplied back once restored. - Failed to export parameters Parameter export failed due to an IO error Failed: no parameter found Export options - Parameters successfully exported Import, export or back up kernel parameters - Please wait, this may take some time… Browse Export - Param list Try again Apply Restore Parameter Value - Last applied value - Current value - Selected value could not be applied - Selected value could not be applied. Try using \"echo\" mode. - Dynamic colors - Use dynamic colors (Monet theme) when available - Force dark theme - Force dark theme when available Favorites Presets File creation cancelled or failed @@ -166,4 +100,6 @@ Blank values are currently not allowed Value refused to apply with current commit mode Command execution failed + Suggestions + Favorite Kernel Params diff --git a/common/design/src/main/res/values/colors.xml b/common/design/src/main/res/values/colors.xml index f7e91a3..262fc85 100644 --- a/common/design/src/main/res/values/colors.xml +++ b/common/design/src/main/res/values/colors.xml @@ -2,92 +2,12 @@ @color/md_theme_light_primary @color/md_theme_light_onPrimaryContainer - @color/gray_700 - @color/gray_500 @color/md_theme_light_tertiary @color/md_theme_light_tertiaryContainer - @color/neutral_300 - @color/gray_300 - #f6f7f8 - #e1f0fa - #c2d9e4 - #a2becc - #81a3b4 - #688fa2 - #4e7c90 - #426d7f - #345969 - #274654 - #16313d - - #e6e6ef - #c1c0d9 - #9997be - #736fa5 - #595293 - #413581 - #3b2e79 - #33256e - #2c1c62 - #1f0b4d - - #e0f2f1 - #b3dfda - #82cbc3 - #51b6ab - #2fa699 - #179687 - #15897a - #15897a - #12796a - #0b4d3f - - #80E4A9 - #00C853 - #00B439 - - #FFCB80 - #FF9600 - #FF7900 - - #feebee - #feced2 - #ed9b9a - #e27573 - #ec5751 - #f04837 - #e13f36 - #cf3630 - #c22f29 - #b3251e - - #000000 #DE000000 - #B3000000 - #8A000000 - #80000000 - #61000000 - #12000000 - - #FFFFFF - #DEFFFFFF - #B3FFFFFF - #8AFFFFFF - #80FFFFFF - #61FFFFFF - #12FFFFFF - - #e3eaf5 - #bfccda - #9aaabb - #75899e - #5b7289 - #415b74 - #354e65 - #273c50 #192b3b - #021219 + #FFFFFF #4B5C92 #FFFFFF diff --git a/data/src/main/res/values-pt-rBR/strings.xml b/data/src/main/res/values-pt-rBR/strings.xml index 1e00c7a..53df607 100644 --- a/data/src/main/res/values-pt-rBR/strings.xml +++ b/data/src/main/res/values-pt-rBR/strings.xml @@ -1,12 +1,12 @@ - General + Geral Listar pastas primeiro Listar as pastas primeiro ao usar a opção do navegador de parâmetros do kernel - Tipo de entrada de palpite + Adivinhar tipo de entrada Tentar definir o melhor tipo de entrada para o teclado virtual com base no valor do parâmetro Usar documentação online - Tente usar documentação online ao exibir a descrições dos parâmetros + Tentar a usar documentação online ao exibir a descrições dos parâmetros Tema Forçar Modo Escuro Forçar tema escuro quando disponível From 3f89925bbdc7f934a15fdaf6f861fa44b068e296 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Sun, 17 Aug 2025 15:29:57 -0300 Subject: [PATCH 16/23] refactor: updated app icon --- .../res/drawable/ic_launcher_background.xml | 9 ++++++++- .../main/res/mipmap-anydpi-v26/ic_launcher.xml | 4 ++-- .../res/mipmap-anydpi-v26/ic_launcher_round.xml | 2 +- app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 4686 bytes .../res/mipmap-hdpi/ic_launcher_foreground.webp | Bin 0 -> 4076 bytes .../main/res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 4686 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 2870 bytes .../res/mipmap-mdpi/ic_launcher_foreground.webp | Bin 0 -> 2166 bytes .../main/res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 2870 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 6840 bytes .../mipmap-xhdpi/ic_launcher_foreground.webp | Bin 0 -> 6624 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 6840 bytes app/src/main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 11396 bytes .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin 0 -> 13456 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 11396 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 16688 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.webp | Bin 0 -> 17420 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 16688 bytes 18 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 5314cbc..047116c 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -6,5 +6,12 @@ android:width="108dp" android:height="108dp" /> - + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index db4457e..a267d70 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,6 @@ - - + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index db507df..a267d70 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,6 +1,6 @@ - + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..92ec6267a5a6c699a7754374c926dd1c5f154f66 GIT binary patch literal 4686 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..027253ff5f2cde9d510146988f99d8ca4d43a376 GIT binary patch literal 4076 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..681db35f738c2aeb4cc5a99589f30b3c9ca615ab GIT binary patch literal 2870 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..681db35f738c2aeb4cc5a99589f30b3c9ca615ab GIT binary patch literal 2870 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
    v}UytINe2 z)>mnGzEY$53JDJj^}m`R>!DM-%b6m+9aDe#v7VEIW)!Uz(K5L2jQ}Bp1S0ZLe(%FL zV4InM31{$J5rAk$U%@I7O%YMl!fSPo*6ZIYq(5}axa*dA-z~G*A?2k<%s+|cHM*3<;FF+{hgjSTPO3BOYbLogH@M>K0h&1XM| z`*KR1+6M6_E7d<-rFwO{*xhSdPYjqgg0)jLA_5MFv;SmJ#XE~~7E=6a$I2-v3}%N} zvPYC$v;?d%4@QDBJc6YonzlHq&%}UEGaq%C`h-y#GF#rTVkAQ&;N)Z!mc+|*JxO2$ zf@-`QL2oekoyXEgj*oZ1N+Jv^Vl+6MQG%fY^&<_kM-Lo{8|-Clk7*AmjGP53&J{iG z3Zou~QK+iJeWF~)_*e(53r?(9RO0EW$&G8@k-th1wv+Af3b(NitcYfT#dM zurx*kq2ZV&3v{#KiQ5ol1)qrtSg~G`TrTR1H-C_;fz3xC|626fnplG^&@daJ4`je*ZW)h{Fi{BE<@m>z zQQHD<8BcRHp5Yd1LaK7=Tq+tyrY zN_EglC#?d@q{-p(Jy8#D;Gc6QFE774q4&%W%47SuU8GKbdJ=k_23QYALMA&9N1EY@ z7{r+6`NEwpfV;^3TTxe=hq(Noy*>_dxR8W7!O1J2zM9^Ev_&}mVR{ffk+pvy-AMyn<>tTy1^!?b8sfZ4fQ1dQ8uD@h(G2I99$UK? zwC4^WoDx;G%GBKp>cz7utY<`Va>zdy_!WlsiMI4mQb@(&6BDutG;XYu}v} zK*I%j=?+3C?21~JrW$RD`7nPb)C-3SU9lxN)e0!~4bTnel)zKcv!EShvj7JOF{U=} zyVDwMn^kG8#jAW&2;b@nCw$))vmx(IlNOIyO);F?J^@L~RGFNp+puL1sukl2x~>FJ z6O-dT*M~W#$bytCP|N~8gpjGkFZK)p+IR~JF_w%}UCJ;;=Du2G?zCU)}2i4%kTi7pD-_6ab0XWJNjgSB65g8iX2mxg^&RFF)1~!wXrTM#!_}N+9D$9St5J5QeA_{4u@>juMe+Xo#nzuh;?p8JhB{K3N>sebx>P zwkqNVfcQK9nyD}|<^>&Rs{s-Ku~#T^r#4kV#)%ecy!ai63BVH9q~bIgu_J)>N~ahn znp)zPapAcPkEwE1{joG59k04v2sPs#Peu>|aN>)5AOneThAdE2jEFpU01b)uD!hZw zfRL+zXDy5*q{M+x`B1Noht)UQ)pTf1&1!t#3guJj+E`8fa!3+tcJc^n22*An{i|O+a0c9Iugd6$U|?SSDCxCn1IALBUQulC%~FBGd-M=&IUih_Ww^z-4^5U&}Eqvyho z!iddipP`{tmVYZS6J+Dh4t5*srI|I})0}-5@z}l0@8db7jXRVl1_(qXmx6d?cn8!* zWqW)1-@KmCqTKG@o-1+ZQ*xZth#bn_>8(8FH8o&{6#eStlTUuf3Wx^ZD1xSu7^FZc zeCA2lX@~__HBeD=9b=ALuRh5mA8pY0VUhujeiPr$Q2o~~dh@T9i6|z-akd-f04 zonf*puNSH`&l`bvK8Cb=S^bl>{MR4qI&QW}#{QdLus$-CY#$geDU@Fb&ofqw+iXqiqw8hMyDIKQAa-7L~r4>s3$taA_>SXtY_Pd z$LFVEDF;|TNQuaF{OV?%XsFaptmM{BIqrMjiCHE5lM-{ z#E_sbr?tPCFXef%?hn(EPu8*8xO0tE7`ZR*t*P;UfrL!*>n+||W*S$9-}e7>I@IH3 zs{I{n|9QX}r|Ay(aE4TI6fPnmNFf+g;bacd_~%cFcln3tgYD?O9DR`M^*TkB?}sT@ zhLnp)F%t0%$BIa^e^V4dy$&(|kl{Q^ z-IYe!GQE8CK8yLcKLvj|8SMNt+is1oN^lPe^<|8go+Anf0vw2N#NF2iQm@lS;xO|` zU%`c6InuUVDUoJ50Xx9U&r7 z;K8@R-5-dMDNrLJExpp_X|7{n=% z&mXw{H|umTXx6L!M|r!v+-i^K62O@1Q7wOZSgw`*NyPVg=r?+p zf`sWX6v${`w@B>P9_jo#!!bES25G~>JE?!vG^mF5Q`KFlC?h*du5c%?VJ3@$u5UOGci~=Hxm>7}c)@|LJlO4k= zDCymbZjaMhuHOqf=8f&O{Zs-lJSbsZ96e#ZeapaRztmpMpkXgw0eM@8`vuHao*)W# zfns=KS@Z(3bEI$Mh@5t=>)D)$J}eZ7J}A)Ib-rJZF)RLfE%-l8E&mG^>e|Zb6o()8 z{&-eezv_j&f)rD_J;Hw;BwHGWj|4(^BUH?SWq@cQiuD5Ly(@aPCdyBq&DXZNT*7qe z=_O~j$NxuZu$*kHy^Kk5tykCh zY#puH5AZ1=1f?^s1>Y=K5{PDtr^G)i`FuHHP`k(H3U*Rzw}$#u=l%I4o$~PKp0Z9r z=|`_YVds2&snN<4DKFEc^{NI9dhrU(xPk3HF~9ecuY4tz65ZgwlK@doFak5C#HKi` z+QD}8USS*$LDD-F$@jAuuRjPnYU8e;h!%`!Ck_&;L{K{ti>O zovP5E`{|Uv{-CJ221xi)#<4JRkHg+o)J~7xc!)CYsOW=jj4DIAretkF!DB`iMq}ut zjPm4wCg-cYmO7|I;GM94bE3%F9@?|-uQ_RK3MCyR)U?#luYCI{s=7x;rvlkT?boHX zyJfXI^m(1cdG9J!L5+xJ5v-5=&f4kZ1<)A|kP3t%W47`{xBHo1Tkwx%H%70fZ)<{v z=W99bUJGeY4gPAQu_;PRWePDM@jZ>{&v`BDDa!oNw7*>h893WMLliR`-29)%x$j+pi3qSGjNyx#XpYwS zeGEKdfFndf0E!V(B%P0luI&Zx$a>;AfZ4kiw(FXaE2%$n%Vrnt)r^@ zNf$@pBOwS-JVd}oF_gt(qZoulfk;Y}DuWuHZa`M6k}*B4d3VMp|D(&(EP>ySGMM|2 zX{_<6-CG*%skJhhMBqI3MN-N+FVCqjyo}U3+Mw6NB-CzQ+Mp_vvnt|pAUcLn40GK{!eufeMX2(Y?S7D4xH1i4E<%f`jFelb3&?z z#2ju@?_#Z1*Nb&|n5A!PB1SKl)$Vdv`XHBU3cM^i2dIKc?Nwy-Dk^b#`I%UXP(%#U*9N)g0^Ehs24PL!uUX?3~MT_kUeu|JvIzYpOR zr*|vbUN2y=aW80)H=gq!wR8*gmozZF_5KxK?PY97X`qp8wSDfmSvLNtVR7V5lgiLH zrqdrYO&4F@w%WSG_0w5lwcXm@Ex>5zKE&wP4E&?dZjTS|eTFbFwO@T^yGyT^Da_U% zDI4ie&E!o}9(LpaiRJoCwxf3intM>wnhLg5a-pFeT@4wd&m0n~EZF=k$5A+?Rr&sF z+~Ib3cWAYd2w#$&^6uY$SDe}D*70gCtIazRqip<{s8QCAOgyk&baWx!oD>&-6eeje zZL7JC<*2Q#?kM%sP0f=kh#Rc9Pq#Gooz~pj+s+@)X?3+k>T7P2W(T}mITyATsXoD~V$B>9lyLTXh~ zJ^$BQ*)$tFLfXD%RByaG)SF^_U2rIp2d3W23=#XMvPiJB@+j> zmpCUJt4iD@w*Hyy;-`|<+FCe`vjwJ6zNR+y@tWPcOB$T5F&-~Gqv%D|C|mkoX88*G zv5&^Ymwf+}7Bu~b^a-CXxZd*^we&P9|M+R(ey}EZ`u!SMP9k|$BAV@8Gh5wSTrQ)( z_JDe!d@WRKHwy7!{7_nmeG|ZlR)XU#8)fV2c+`h&C>j{yyJ!1EF2(2FA z@cuI8#WxH~qhI@^G_4lti2jBW3HYR@g&L)y9RDAO4Q%uo-0qaw?efjR?GEYPs?Lvd zd4E1db?WmK8{zI2Z%xf}O+eg1CN_#d%{NUdr!>nhxl@Y|l$5`yWaXbKd~cFGT}fQr zP;)&pi4!TKkc}>BO1ghav$gZlU-XursQUe&hG*+FzE~@Ie>c0`8x*I%gw=CjdX2L= zf!a$?0O~OCulW2aMal0)wjgUSX-h_Ve@^=f@_R-KiPbf=v>(MIiV;37oB2z;;9ptc eJFRhi!a128Pt}9R-qOk){pa%^pa0y(-dF&;$e-i@ literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..cd8dad4ec0a4dbcf026c7bfa31f356fca70f6687 GIT binary patch literal 6840 zcmV;p8b{?)Nk&Gn8UO%SMM6+kP&iDZ8UO$i-1bH<6H#;DLxBNW%yTo&Z2d z=w;j3AQW0X01*K|9)v=-6gZY|A>}DGF0RG0LTL2>pv5%h3A@ETn0gs40N^K`nmYY8 zmgNT@b9GaMAWB?}wjZ7l1w&?#?<- zx0~+iElg;Rx7=Djlxy2oZ7Y55`vlM6=RqTLPk|y4{3>ow?irM8+m_ubbKNJ~qJ}K0 zfC!8h0nsLpx-?(5^zNZWZJQxUdjCHnv#O@6$DUc+wr!hNw!ODIcWc|WZQDCG+bVQd zM*QK>wiPQu1G>OCT3jFX1=Q#NKO;#&8yOonJ*(l`wrxE++qR8owr#C#+q+}Ww0oNQ zrD{8q`4_&6K6ZMosqC?@YWr+o6$RV2RV&Wi_xu02!wBVW@&2Vc7k77ePw@Ki4i#J5 zmTk*Euji8=cb7zS9{`O+Vle|cB<4V65KeS=cX!uMe|`0Nu3+G{k)kX!-}MbZc$%>Y zk~231B&O*pOaukW6vT*`0GAV3>y&c5n$4CT1EW9{R6rScf&hj=6VyOM4>en@^kf&o z0mFg zAJ_$K_-_HSVVjTwQc(l*!O!3e@C{f67$@*2V+Pv~LIC z0TmtKbMV?<*iL~v`8=>bI2)V>Hg2%dW}Zj)PnxETseoV>xcUsXdCtl3Z2}Gi{u5jQ z&iqG1lE!0Otd0f?L7*0z$|UCyOzdqzagrE-Fwg0=JO)*nxSAUZ+Jga%FPgc26jO}N)bYiJoOTC0ust?wP6q4% zW;T#wF}GE@x&?!+DE%FUwJPkW5aweuf@mY}gC(*=+Z~HPT9+BQm5`l&ijbH@%3gz?chw>R``+i^JeE^ z94*ssBBnklMB<$@x8^wdC9~{cMj;gN!{(>Muu=MZ&;SE;&efG0bsPiL#7SnxFlVdr z6%8IjeH3oWwxq+UA*Bc}g|0{+RZ3&iHk<&7LS!7{ZmYmHg_%?lB~;S+XhPj9FyJW7 ze9BVqjhK(F^3yt3ziV2ib3$7Js!H9Ynd)0ZGz5`FW-vW3fZ=9A3S6r`X;MzVdKE>I zBPm6eWyU7GO|gfSuLr7e%5HQ9d>5Oa zg>EBwY1foYms(ZAb(I)0m!bd%r78^Z(+yd*tsO1Er4_yD6+R`Mx72#O zG4gJW7PxQfQuwybRs_c+N%SmBX-tAtzz>Taz1;#XQUu4c_YZ{y3MA%zVylhfj_jJ& zX5y=v==Zi+ND}Xj1D)AQ6Ys|xLo7Pd{rx)5r0W>0G%rBtd*G~yx2a9((o%&+qY=u_ zZ8kPIM>4StWBrn;#cX%;+TG}hl5;?-{~S+B`U*I~XyYSq8Y4j(N})A@3?2Y}e{r}j zXe0N+dYgpKIcJH*`UJdp+@)}X*YqEI))V|H-Y$7unWt4ik~QkcDDtR)0bg6rr6mG5 zxjnoO-UE+ehZ>`4m=BEm)EB`cXYGVv;DICR~-bSt9Z?blae_(<1vgVm#!tJ%-fN=J^uw38j{jiq}`6Du$qzdV| z_L=dF_BC`iHi}@6vO!uJYZosd<406d_!_(!9sx!8_48l|;sE*$)0)cSD28(3ci9!g zuAg6*S=S}l!h(Y0t5gTKj#6l_Vq#PE5K~2{5=xeGH*+cx6|%LG{aV~UycHe*JNARa zp#TB{5Hv*4QiMv{0eYmD*-vK-1vLezLaugyKCr;%3a2qr;#dc3BqZ68N}+CI;3qDu z1!Ym?h}J`{+_PTN4ti)WqK z5@HSWPCs0Agy<-*yzb=ioR+zk%v&@YgE){RJiY98Z=JTqG|EhhzyT_u3?!4ph(#R0 zj4=qy!rIGmO%S&kqohCZ$>b&3Hwl&@Xv#lU0ogtUMwcVQ9MKNY-oDL zsOOi4XHL!7pHP|y76Br(j;4t-J0N2ni&fg=rLJqGA4CmG+IeAnPo)_xko1pf} z(K=M>W=}NFR*Uy=n6of+c309 zjPrM9hOEH91ME*1x{iH-T$!lJ|J&EWmDpM13wl{t5f{Nr!-h4rUOQa&ygrI1S@_#V z+gk70Z_gX=yfms$d*#!yd4aJWLW_ni$@;h>Fnn_F zlqJD*e6opJP1~B|muAv7Lx!?H&sKeU-oS6S`y31@rBN!BU4Vh%Q|=tIjrZQ$IKvvs z$O*rcF?&6(t#}<0Nv1%S-EA5OV0o_B*MS6B9A21dLB=NBPk1SB*)*7Gdkc9tB+bu= zxhQ$y8uVPm1Zse9TBTB4qQwH3P7bVSvD>va$H;P+fZ28tmNn}E*V-0#9nivBOJ{6t z47=wI#V{}H;?6Q09l#?NQK;wTy0Hm zI7e^BB1yyJ6SibM-YS_WQn-sWfD0pEZGjhnSEM4U_Nu``OA(>iNr+1w6K_O$=LZoo z=S_Ul)^XpXoxU}1M(x}Yz^iqpaakpW2H|+lM-3<2X7NfQL5cu~+Fe4`o+6Z)EFu^} zNm2+MP5ae+ROS7EB=_;C;;c~#BBr7gbr6%2acv<=YUASP2WUz)Qcla`(A5o~;6aAQ z@SukAC|deA4TVV3F_jB@j3!CuOFZXD$`dVj5f;q(NMf?;acz~{=@@JHp$#J- zfoe=KZ(<}8Fv0V8X!F?XWR)PT{hS$q^Rn2kOs1;};+=7;Wecno(`2Vzj|HK} zObpdJP!Yk0ABLh}8+>#4-iB)7oMSXCk#s&lbTyvv z1pX+npc8v|j*l=bW1-W0N^D zJ;7Df>U+Bvx*Ys`G^*1ilDw9019aoqYp}jVhm05w9g)NO_v;ffv1m%;8I0(9RUtvK z0{{ppHh^dVjfJ>N{P@JkVt$^6Qea86dNFr12PpC9qF`bXV9I1ij@C$>BO=$2gVF&MqE;aiq6K&#AHW8F1G)(XS zKvK-1w#kl-Fg_7BfQkI&c_j~LoV$^Bx=KJ7mVGc3Gu?C*S$8XE@iDk@@^4+b;XqH! z5pjX14#&2(ju&=R3|+Gf#JeJ?cHUoqX1>d1B}RB8cn$!7nSc3h*|ek)Y}Ui3#hMVn zE{{snEkqa+cVgR8CZR-&h=H_23gF`Rdw`5^GOPsTypxXkts@YUybh`L3y*n~)$1kD z8z$oNf=(epYX3Zh7Sym9T%4&w4`4xPeQGZXO7j4_@?@6u;>!9|xrERfX(SB>K!H3uu(9vP<0&>3L=XO-MpPxHt7w}wj8xTMYOwKk06pzx zuXS4l(x-@_q_03h=$hYmIFd%bm6^1aS;J#ZA3b~0b1$6q!ixj%e7JTpyF|vJhZWhi zOawz2n4nDJ006MHywXP9wUsQj)PVHCu-ydHVKyk#*?-3L#N*ZruBCID+l(JRNX{WUNAEjUVQZyx*X}bS&`zzn&4VE{` zePkg6N%D+RgNJCqjjnI=VrA@3j*EJ0JU(5qrfyA@&8F`6UhDbnQw<+J{qE||`BqF} z>0dh_lN9==Bb`6Yu0p6uKLNz|p4%@&$8r%8BQ1y{J%G7-OZBS$EdQyG)9<_?9aqi6xrV(lIE9VJujvk<^3iby!D5u zSMke#W>5Ex+MR7z0>uf#hU4a!ueh(g69$4Y%NvkDYyaE*gFp}@I?`s$@m~L^?B27Q z-Cv}mWxzcG{G711pS-a{q;V<0-fy_*3#=>R%`K8a6B_VDF$7pOlL5d_jZW%jyV{IC zpVh|9gj8J84+r=5F&McMSnj!>hQAmSZt(tmJq7bg;+&4qv($dp;zwzhmePqpRmrgV zUAZ4ZT^Po*5E?E}%A$Yt?FG^q-6k8Adh`FKdCyg&$HGb~FdyI+rAt-oq0zg|`L{V>`-**UR`93=^B-WS1#^_S3?0!2+mB*g$Fkx6RFpPzC6@ZpS+@Z`F`1~x# zdF=(L8W{gb!RIZjG@@Z5f(skPZ9ME}6b6g<5eFJ_oC7UQ@e{xVgZ1ZMifrWFFg*Ta1R@>tFS=Q44m+@8}biL`(b5)D#8ZL^e z3#Y5zUtv>#T4Gj;+5Rdozg}ly@Tspk0&3AW9K7=bP(6ukr*+`RvF5q4%2(l-i+}{!)xE2`5Jy?0lKK z^W(jeg$ovfnPoi3f?jvO|7XX6?L))5F8If^56{T_Hhm~O%KPG^LUY2 zEWu6%tC&qTmJ&Ie$FIznZc_lmL+7%7&&1a*EPwg#AwNFU&%KJAXK%NEz8k1Jx4-B&7Xg=T@axfuPJ=CAGNq! zl)NX`aV}@D&5E6EaDW<*RDZtO8>@R`iIX@K!-)l-o~?cR&fdR1IO_bq6xWJuf<{Q1 zl`$o^ew%OX{lOF5JMKitqn|tdgbb9g)YAbakVKJs@4WYka|Yw9-EP6|d*R!E1kaAC zhNqOHxqPw^Wkd;`0fVSXeSAzZU*OQa4Lq6{&iR{lxY_%3Pv zPtkBKeKJ=bpTM+pu;mz@S@i#v>VGdizB_sC`Ar`MYgOquQ7(F**FhP|9enSzU%1-W zKXTks6lZY2K8R(Sihr{Bt;=rx@jI?_A4;J0LxN&ziiw{>z@()XdwALEik>DPM}AAn zKv>7xMG55hxUO5Q&{~C1*`XSFB;Es{V2TTS+EH!Elx4AY_`i1ZOULa*+q+C?KLxQcYoS?dxZ>Web3W-$+lmr=go`DPt1g#bH)=2pNGL= zDzseCj^#}DOw(Paw(iY%B0_EnzXV8iF{SiALb>Wvw-7{j4WKW(5N z?-)3|zux%f;sT(8rifxHbkqgET_QeZq#^u7l9;3TrF zbYa!jT8r%qlL29!>oT$d9LSbuyefngVulj@nD%@8fEAw?d?-~#3>*B3D+u87GyI9k zY^XIALgyF7${M$Q0Jj2P|FipiIS#lIuCP_ruuw4w9-jaLc$x5m08HfocrXoLuoD^s z;RytA6@ZxWOcuxVK7`~%@zho#18zAE2hf(ep={U2-%_6L5S#f)oUqv=uUU$}rP>A(T)_>0eT07v0e56K6y;nb=H zN?TQ!o?e))iXcqS&C$z%zy|etDeU(Zp4x{OV+hsk^wtKV<;Ae6ZNc)>NBhG@9w0fe zf%FXOwQGGEz+n3U_kJpF)GP8^9WgkgdvRVPz`Z5*{+7)3|`X*LcS~K zsF$J78A!l3-e%b##qb~{f^D6wF6JgDC&OfVqQSEUd!43hFqt&6hNrK+YA!Y?Od=zT z{uG;!2j_-s@v?=YM2?LpP>f{%Ux6J^G~mHPTc;xZx}WR8YEVsDi$UY zWC)|lHhMbCn~ZT-G4C>$#hh&ykzncOhU3rt^OeWj?ACJEG(aTBMp|8jg5vlQ9)ky- z+t=%zb9;uW6i8q!FllQox27Eo;b2gpzg-$C-KGx}A?4>Crzv z*G|6UVrgzKiz8&p8Z!4)07-Dr@g^zW2FjE^m^1kyuaT$WRDNlcVE+G zGcr;X!3semO&FQb2B6b#FE~NQ4NozM(k`{E5DkWbu-U9R^yZ@v?(x!3AFKew7xpx= z!D|D=G5!lc>%4tv7uV`~w;YtN`u;NYUVVPVrMn)u*L7~bVh76_CeWA!5fi~7KoEAw zU`$GdZCP5lN-)G~fu+hYWHGp5{r3Nd@Alb8_s{$LKfJ91BYV5Hlw0j2hQ|C)3?Sve zow*m)Wv*?gai3C?@hhJ z)%q8%wO3!yGgoX)UU3;)x?S1}s+1aEkYhlsY=tV#^0kMyocaeZ9o-MJ^2Cm%2OW!l zcAGW6tO$zLFN)&xwYB`u56$Dh(wsfnJdy;`1pd5z{(e*nUtB8{vJJK@E;dVOytu}t zw8l|d^?sC>jdG}EeGi7a8SXYM5ZYxKu<`M|9H~P^@b{y6(if{aJAShNwIk1)zyZJ7 z&X@SORI;=nw>~aaf+AVABlo2vCnQD5>VgvwKs2lEEjN45=B6By%Kw|Z2@0LI4CBH?HT}3 zH5-fDGRZT~mf0+me>G(=5&A@&)jXtXQwM`-B2v*JF<@X&xKhB%H=YKtxOcKXkpQ!C)D+b5#z62V`31!c{>Du(H$}l!YSD)}atZ zK_DPynT0D*C_tvNRo3cIIdsX?kG4&UY}>Z=S@y}y%u|{gid+CH#jMVp;?!mVW(Am; zNn?qbxyAoH5$0wV*|JU0%;ZM0?b=GO^QHGcA`9}52>+XC&Pa|VNwy-7{_LOtVjwy~ zapkx(lXPg?Hnu(U-21%0W81cE+h*sqsjMnpx}>sE+O}=m{P8~b9te`;#%J&}`ebZR@Dx8u~@J_hZ|(?Otu8*>)?= zNHP8pBuQ@DtPu`-2QZpg1?U0Z_WwWR$p63R>s9V*Bk4&y8r#`@+qP}nIAxyF6NN#kEbP6QDhEG z3i2>_v*+oyZPT{3ZOgTdG1oTIIp?x%+gHf8L&h(;!slmP2-&u6U)Q#^FvsXE7`Sbu zD97Ac?A`qUqW|x{i5M`8Kmy2q*!5-rB=oFv2q?3Mc0WKL6i8@G=P(cEz$};zGob=# zun^|M0{9>PUFt!Ffc}tNBA@{0gXLf?SRdAfHDEPZ7M6s?VKxj<8bBWyVLto~f5NZu z4SWTk!pHCh{Nh{yI^@GapuqaDJ?sQq!rHJr=x!k!mjQIc&+r+%2`|FS@G;xCV!Mi5veQf)X&~^pxhpWLr%*_Z?7y+ya=fG(V zDViAa5fM%#N#*Wlq{D;Js73%d8`f+Xq234z8e-#kI0Ce2q8@!~xrcFE07f@7qXIZ$ zcisz0CP2|uZBD~oiut;*;!cHf^ z?guhN!*J@~@EpcQ8SKd6uyA z3TfIU5_kfiKQN5~LoNCiu6=$RPRV|rX)U{y1O;!85JgWa-Y z^0FO|gGHHIOq@DWAf<@UJPmPo%8n$+5ZJ9!q&gwe7QC{!jIk+ImW}I;`n4r3-^z1eW$U6Y&{nuar-XOmdMlZ$^UpxA z5H48TW4OGo&qqzSKWG4qS|~Ib$@cxVulF_pZ3pr*Uwxm_|#l$fGvGm`NW{1q1@ zeG^I=bzDg4tX*d032KN0eLeBiSI=%cL5cy~9Zv!S&>|sDTnz$5I~R*->m#wDa1|8k zuZXnL&8S}?hKLkk5TTc~(1Z?Rn*jgR{Vfer62*&=k}sk+G2+?kP_763zyF}Q4h zqI(_0GpO6*An)uBU~s;PW2a#IwrobyaY<8K2ZvH0sz?bGP%;hSI7D){lEDzIo8Lgc z9SVB??6^<-0RwbGvXYvGRn(amQrE*ZdeJF~L%)2wDxRqW*A<7(GV?_k(1fnf2S;4L z+D4ZV<<)$<mCm1;1XW5pH;7X-&|xFAlHvn= zpKtOb4#EIjD1YHPe#$N~3&4Jn7 zrpjiVzI<+?3aXILd3tS)`4)i2(6sL$O2`vb5PGcnq^NEjbQhRN(#2o6CEm~7q3Kvx3;Kx37)%w2x^IK;0| z_&nzke!M=o_OrkcQnp}-vy2of6z()cFY_8BJSxShPzepuwW<$J^;h{>b%Q;8he000 z0Axrp6gz6{v~Wvpd+9+BxWk_#09D0eONy|329|~v zT%&J62ov$keOlEAwOcTX=i%_jsH{>h=JE!|R-T#ZddEWl^ zkLi=v8z_x3`=q9y)XS&6a@UuQ5Bp7fY0Q!T`Dp?V84wc+4$-I)!D&MVKzK4@`7jurA#W7lSEbMi0W*L{?NV-Uzwwm5KK4{1vkhP2 zz@WJw2uaEo%o*~voD~M78(;n(jv-67y38lquKU z44DdL0_H6F!cw33RyX&2@xA!LCA-a}LhvXekP0crFy^k6hU_@BcsmE@CRc!JWQCR$ zqC~lBKPqwq3fui*~i^aDCS9`r1~FQQ5iWYwP#Ym&s0rohb}LNYP7BSM zFS%W##Ki*0B9k$aBsPh1^n8|uyOVp~{@t72{n58NWXK+&@m5J;(v5ra0Nrv7YbQ`e9QVYN!FmhDSPW7vJk<1Bee&BDtF4 z&N5fB7FW6`%j~H5h_oJK46|+YR3^_JTOUvEduLr6rsA*@oUPF%xlVx7#)MjAs zAh}x2(cI?b#@{<}DP_1C;G8m2>x$mL!Pr7t6gXEhV+4NMn#~8zHISebp+i8Pnrn!S zbRBBR5#kIDTxnfTQU;mkkfdK5)|=GTwlW?vrVUhMCb6r0yBb>zV^{N=yPV4rc`_Zp zt{gi9;Q$wyigUKPmlAmwBeZ)WAXH3hFJ+pmA@8FVdA>?OE?+K9kn4~yj*MD^l2pOY z@v*YikL}OJ5B3YM9r)VU5IdbJ(r@xsae?=7FJTjyw|K``H_;Z{@E8-1+$eW&m&3Lt~w$4IHVKD%*TE_Zm zX3?b?QUYUo@2h95RFT^ZaL)7W1-P`Zs4q{yMs4J%sQuhE;dUW}pH$P)Pxi|*A$^lF zjt0ri-S-R__>XY4RFO3{j6zneXRU?EA(CgEC;;M8FD4+0)-nm=4K%_LVqAIzL2#c& zNdnp8sm-VXYq-EqRHmDG%vq4;NJY~^?aWzVOBy9aO57{oz)a(%Hk>2}BS9A%XaJmZ zs0JX_EKFesIUE4axu7bw@}anP}_l)+{khvM5-Hw9PPq6nmgBJR^9}Mpb-FBqn+yR|J^UlGGlZ2&n+K&>(wf zPLdMGSG3%A8Mj1*0BD@&dR74nHxZd75O17*^h7IdJXfNm;m~{uj^m-+LL0|?~GKlKAr=$AQX|ic!(OEAfU2D9g61$NU)NrNm5V%F|jLiYr2o>)9|)WxJL}011O@nSs#>1Q|D}z!2~h9|ylggyKnF zo=zwOqjZDl-s0z=vza*$pCIM&Ad5CN6x%i`E(buDC}rjh z=fI+QVfNdUVVVH}LX+s=dL-I7yV}<0Ye__!tvn(V+{rvZhl31o0h?}e#*0ldBq&l@imvpDL^?Z& z3caakIb~!?lVc8@6|Q3#pyNRWS;hs(6J;0z2av}2+Vzj_yWvY=-ze2ak}gt0sz5IK za!E=m7#o}?Uzo5~#q|8+HW7)kj)P5{1pmiBw*%OV6qGUMcn2XI079#L=Wx0u4ZzTe zl3}9zZeXKew-*VGs7x*FLrjV81e8vYh;kmWD5Da=#$XA6(h4b9#qvRYSj+>knIlXZ z7l3=X4L6--MH-n=COB(~NFdY!4J;7j`<;BhdZ7oZgpwjtWnPLfU3@%%8W}VkDo~;T zEf5S!0>qCRJQP)X(S`_cff9WvxBvz#jlWnOON1c}%^6M#h^}^e`FZ-H<5+oVVrwLl zOjjbvPc;=pP56k31t9`ASwKRd6=}4F-7pY6Mj^5G* zFm!1@PM>fBLl;TTjMtxy$8j^EN6pY)yE19~EV+Tfh#f&$e>{LH+wkL1V~rqy7l}mJ z@=CH8t8p7f+AWLVS~jEvTc(U1WF5O<0Bgj_fpDN5T+&SHwXQM4CB7N}2sgR9HfR;3X~n#fz$AXf&+7he6nwMzgd^1<6QIdoTtM$LNC5 z07#=GfEtLLjHAI&jp@%NxvfUfH(*p>P-q{gv7pa>Ked^aAY^DT4IJ0 zN06Ci5kgbEF~Y~1*_uAXFX?-k zBT^b-N?3-4QkF9u>5%A-B&v@rvSEcKa&&Q#0p3jt0|5{WFc`(kw5AB*Ck^dr{;|(N zM6_|-NQL%`pFdtrwr@OEYl>u*6(Il-=Z8yTQZ_{dATZ{!7-E1Hjc|;$_Y5#yYEoL% zhF}){N&jTeR_~;>Oj(Qk3b%7V&vPHwah}6`k|!e@*c1<=hJjH@Uje3hU18++@3Xhx~b z77$|bHu_N=dr&(`Eib~iFu}+97GLI#ta1xWJP%Jp6$bUCwlRUi8AJTJlY<(kH1pyO zujbDsuBJhZr>Bviw(MR5;XK3|U!z{qK*~cl>}j@k8#2uog5g zxt0KdPb@lVes)3|(u#Win+EMe=kJ|__Y6s@jb#<0pe_4epiUo}Uht9e9l4YGqRnTM zH$^fb>A-N@><`r4VWgFbP|?yrQCuMg4Xu$ifZ+{ns$AJ>;f({atL^`N{LjXXtQiD} z5&i(k+3_!P(`8?s=$#%RJM58T#w5nL#-u=l)MDGyOLVA3#n0>^eHIaaRjrqviL+F(5r>WP!-hcMWPmk}3)jeA&oJ0p~8FXVYJ79EQ6hwAIKA8^V z)#qL(RF5nbB9lwOBi*sBxY5WSy*ow2odyf1Z`MEsLn`YPr(nfmCQdVhO)F@Kx|N2d z%F0^iWRd2Z$lBP|!*BlmtK9p$)XKVHv^BYZ<+AsGz5h+&1%Ql}Zq}){zj)$5ANauZ z#tO`sTm&+JR2Xt=TW{;V3Tlm3JTLr+5yp4>zdh|^pNLH4nAGRZ`8%fl{apQYw)>O1 zbn!Y4O|^ML=DMCQn}J)@0XiF8N;pe_CCG$0yZOxjpYZzmBg*$D)cJnT-i7{G|C2pF zqbyDO*eFMs#>+fSVBjgo6m;4)hdsF{dl@#dM&`?eYSyO{;+)F@WlGFufFm*|7&NH$6B=MbeCFle&S9=GYA$d5IVz>6@$!I z-yDEKG)x-fVPiNf_gmj=SImM56M(aX&eOf8vi|R)GY|LX%cLq$3NV(ypwpV)y$+V2 zNIpv4;zKtv!sPJF{V%&6dy_>WvV|3;{&9Tr{PWSTZwhbTI{5mWI@m~ES`<`L!K(9J zuioAT1>g=Lf{1j)dWtS4xeb7GqBkoDCY~CA0+T>rjn#$4cILS|DhtqpzKJ3tgqhsj zdvfDm|AdL9htT5mc@{OOANKw64Yo@WqVVWLlA|4W_|E?Mz}vewe0vpNPvQ*1TlRiy zW&HAC#={Yk2Dn5I3B=L?PIWEBZV_7ADPc`uUdqAsh1tJe_w_IfqTrL1SW%}Q5yoBF z|JB>itqtnsjJ90w^AG|ix8BYlQ+ODQAV8>LCIE_6#=3&Tz5bVP*duZvAQX2n`t0!7 z1(I-rLyXJ$H!5&LlZ5KF?}3;9`R>P`yYDX+t5BeDjwuf0oXc!6q!_Hn@xA^I!v`Y& z*ZV7}hP54#0uVmL?;#YVp3sJ5 z!c6U?T*_a5^NWvL4|Lnr;&I!kRywhw(g4uxtNJl{Xv z`D;=jimZ6U5+sVXo0;h{G9kfs%hc0(_9;k+MmBhRrr)3-ju?s1P}*y`{Nr~&7(O}w zk}{GOnHnn;fgv{I#P0uH?e)gPa69At32KKCOn!a4e-+xXM`0ps=G+T2K@p{^=Gt(y zv7HO8xnq|$WlaSsB*CBf2S6%FrFy03UlId!TP%9$)34sV+1%s_W#D;9Lh&8(5pl!zrzbCQv%~ z0%j1*w3btj?Ed$6ecN<^dK)W~WC(>mWd!4&?#H*f?Xsb4l@LV6axfFXEoNHld%NM) zJ_k^_!MLnk58u#1`8zfwzYr~QqwT+K$L34#5xJ}O7#>~z^*d)zpE>dO=MRmC^uQFV zEhrh&VL%$nUEV8y_xu)&6UNX_w1^L4VDcnp!;E*kJ?;t498t@fdA$?uq78Yqg?{=S zwc9m2oj{AWcId~=oj;%d^NV|LE+>-=B829(Ca%Hx`1f${v%4SZ>w~WKL7 zuioBw{^}8HXHR>}-Cb{K2?vQLgA?tDG6}W)=VSKiK0gFgHD+Nj;VP|x;l<4Q9`&`Z zmwItm2YAW?pdt<_w4eZ%TIomk@mWlv5ZWV)9uUcQ>+sKd?e0(Y+Y>)3$CIP<;zx#% zB0(FcaSg7;aWt_!T@aeoiQpOp5sa2=fU!M3_2jXq{{Hfd|9|G;&GExE_jb{Q0&s{{ zD~O289F>(1?z;8C9>*RfpUW&1NMJHRf#HeFhRJ*WJ?&>W*e6gDLAFp9m;x|yL+ln> zgBEOo(heznA*Hb*^qu=&`kPdFm8%Goq?;%b1GKP+Rk#QO17cG8iXFDxQ-;>>BX2{b z;1+eZbM@}zJJVN>u!oH}!agj43KR@XNI}LRq9dgNjzvFA*GGf~ip?@- zKs=#&Fi-_d%d-wGN+{AU+5`Vx)FD3dV1bs|XOAjh-!FT2a(~m;T5l1gwZRD*qpDF# zPctwzPa5Y+ZWvy{tiLfy|KV|RjHiq+M2bl$K*;#uG_IQWLw|X5`gjEQqqUL}F{Bou z3(R>7Adn2f{m_vUfU|JZM6h5r3ImXfgqTUdAJRyy*h;JfK;UE%I?5cWUNwn4Ig)>PYQW$Cv(Q0$3kj%}A((aJ z=j(&Y>9u4A-h@=5Y#}f(CA8Yvd}cd6(W<)tA9=Ma%=g5_R-A3s*@3)RgDc55!BV;n z138gH>A}pu-Zm%1tbiCd*8WMKN{_rrs|u(@)rZH!epM1dx3%GI-O+V~#bO{&@J*-9 z5hiXAz#!7F=Fo@|08)O9w~I61%j7^vXh7kdw>3DeO0kv9#PHg#^f+(d_IbZ+n1*gU zOAcqC!XUIief-=Lo*CIna%#DVke2#5+{Txx%dXXOt5{b`-IZ#tUF^?T{Y`UudEwt# z**9|D>St8W#Y6c)<6k!vfMJTIU^|WeZqVnC`BWXts9)?(`m+(Mw2G!g@fV5M8aVo4 zt#k+R+xaJlynf-yT+gr;>^=bZQ5&Nk6HuCa%Jl#Ypr)Fw z)IC@KpC8X1LW2$pq!*KbZo`1uKuEjp}ra+45Z$A&Iwv3F266i{;=`n~%C# z)?I1*@m~%9`Y&4df7ZPHH|sX_bP1j>m3u@0hAqxRjr?jm_RIUe?T7adjmLdTDnz{T z34kgVD6}V^oL*5sd-Ukl!j!S6zKq=`j;I9TOZ2tU2l2j1uNPaOaE=O7oD+aHcA}+- z7{Vv_+QDs~uZn1=2pk6)fQwT|};Q3CD01#SX#yp8sDiLB0!laHq( z{lEE_f4BaJe>3&F|FZ8_|Ecl%f6BKn$sU?tf3ozif8hV~pFI5Ef9+|GXT0C>6Fho@ zR8xBOq0c_GP$N*rv(ah$(K$K?Yf{e*(10luXVsrqpd5k20K=+mm_^y>ATbC z|Fv$n_WtS6{lmkq9EzzkE@L5$!5lzYuROr1!!yx0epGnnkN)K~XWT4ZKXmTJ zBaO4i9U4wtJ|*8;45V4s6?1lAJ-scHAgbg8*PIU}{ZgQV66BmH0Hys39G8oZE3WrsxBh{@zs*12w5?^i)Zb~~Ax59w{9x6mMQ z#2*nW&|Z;3XqBbjbt>fcD^MUq0)1X|impv^<*uVWL7b*VlI-b1Y z29&Y8x&
    &$dYC1s>oAt5JP6Vx;z0s%sJLKBpgXlbVeQZ+|FX_IOUAnypW0m=*@ zL#aIWkVj$vmx7jF9z(Q8ghNDtvNd`Krszvtv&Mr(A>y%BkZ?}YAb`;e;1y>B`@ejQ zl;;rWAXn(J5waQKDr$uS>43-VBNj94NVjK#2cm)@Bp4OIkN^xY;-!((dZR~3L_!h9 zx~35T2+yTNv`By!f+2C^LE1YcS}yX~ntM+lvydv3ynU6sgB*Oxke=f*3x-Vy18B*L z44{z^nAv@qa}<$fX)u6d(kBqFtAd1Yu7D7>HV+Yq7J@|r7GPX}t>NjeW<+^(H{*Mkmvv;?KyP|v=+cPW)zD* zQ6DJsLaJ-F-z+NN&+3 z5G{xkl|}}0$TTJu#4}FByBG#!9AIHX7AsC=sI^$Z%CQw{Zppop9n^sS0nt}|Q5U`f z0j+1>4Yv{!J25()SqliqWA7idI(B?8o+e@z@)5Whh$ni;!nOCrT?2;v-dkZJRT_-J z*>|Brgs(u_ot29bO|^C1FAbq`mIMs4gM#aB#%nZ-&p85$$Kn)3;nUll z8bpRIve=Nt!pgwvr8ehfXxRY)?fVdD5p^#)CXV%byy?8wv#b)dH0)wUwj_vfAk)M& z7^DTzd&&ezNBcAarhW1l_JW`dz<|iGVmHz-)2vd*(cF3C^!4>P*h`LR&@wdn9_JCsG zOF&8G#OJ(rzc)u#*6E(6D<-2V4N(#iV>Ba38JjgFuC^i^rfpp5BX7($0*vJh&8%k9 zZOVMJaH{+Nzkd4v|3IrF@$8}3-Tze*$ZQhT$fP|!rg{jOByV2dw+^qY)6AK!IvF)% z*(NbYF-|iV##c*LVq^(DBGiPc!y^1?;tJ1}W?8Dy@zcNm)%ni4dp@R>eMW~N^dtM; zC8sk~oILFs^oaRS^QHIRoHhrW=0sV8pt5I(nvg{a5d#aj#sJ6dF+#=yj~a(N>xwum zlQ7i`DNWuqR?aNn*{;5*`N@B7voBVi^GJQqG4u)*n%f4hy+=S!C2;bTN7uJ;@^jxE zzW01%a2zR{L*_0D;mYs4ygR%8|5Vqn1}T`;OJWe(^Ma>)NGQweBN~XWw-# z!wANi4Wa)(ma1k()t&1;N{>iw59O&3=RErEwsW{{ug;lSGj*xP2Otf$?}@=uZ{Iv(eEBm~w)3cv_mnq`Y!E*k>a zVN=>L*aRwI8~=*GVp{E6o95}2?XH_$n-9-^=%JN=Qhs@~zIzm!(f=3+z5ajxP7`F$ z9UZ3je47bg9$n9ikRiDPK8||K&&PS^`n+g%*V!|>PTsRUy4vHAvXn<4rNxIS<7RB-mq!{W6TM%){`TWd!w5pP{7w1g5%Kq{ z@4gtpM0HFXCKl-h2E)VjC0`jZ;*6EC=|_F3R%^;3R%Tx z29UCBJR7u-mfhLTL3J+Up*iRBGxoy(oO2^z4$iq;p{hCLc-122!MM~p%`>h@I_jt& SDE2+DP*tY|bUtpEV-*0s$E-^L literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..1bcf674e948e36f063330070873812fa9afa3f20 GIT binary patch literal 13456 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 yHVZh+lEEjN45=B6By%Kw|Z2@0LI4CBH?HT}3 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 SDE2+DP*tY|bUtpEV-*0s$E-^L literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..aae64e280f59379ed4ac93952429ffb6de3f2aff GIT binary patch literal 16688 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

    $v#By$3)IH9$qQ+@!Bj|N8ZHy=@PsA4@HcF*jVn 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 L!7eA#>3!S*wWsO- literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..0a1c7b789d240b46282769fed41bd5b9a48d3e89 GIT binary patch literal 17420 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 z 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 PdH?7ApZ9;>|9N`?vQ()F literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..aae64e280f59379ed4ac93952429ffb6de3f2aff GIT binary patch literal 16688 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

    $v#By$3)IH9$qQ+@!Bj|N8ZHy=@PsA4@HcF*jVn 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 L!7eA#>3!S*wWsO- literal 0 HcmV?d00001 From 1d6f2e9f5b902ffe142d5b6747efab862c3383e0 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Sun, 17 Aug 2025 17:28:16 -0300 Subject: [PATCH 17/23] feature: added support for landscape orientation --- .../sysctlgui/ui/main/AppNavHost.kt | 6 +- .../sysctlgui/ui/main/MainActivity.kt | 1 - .../sysctlgui/ui/main/MainNavBar.kt | 48 +--- .../sysctlgui/ui/main/MainNavRail.kt | 77 +++++ .../sysctlgui/ui/main/MainScreen.kt | 35 ++- .../ui/main/TopLevelRouteProvider.kt | 46 +++ .../ui/params/browse/ParamBrowseScreen.kt | 33 ++- .../ui/params/browse/ParamBrowseViewModel.kt | 21 +- .../params/edit/EditParamLandscapeContent.kt | 267 ++++++++++++++++++ .../ui/params/edit/EditParamScreen.kt | 74 +++-- .../ui/presets/ImportPresetScreen.kt | 152 +++++++++- .../sysctlgui/ui/presets/PresetsScreen.kt | 63 +++-- .../sysctlgui/ui/search/SearchScreen.kt | 68 +++-- .../sysctlgui/ui/settings/SettingsScreen.kt | 64 +++-- .../sysctlgui/ui/user/UserParamsScreen.kt | 45 +-- .../sysctlgui/design/utils/UiUtils.kt | 12 + 16 files changed, 843 insertions(+), 169 deletions(-) create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavRail.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/TopLevelRouteProvider.kt create mode 100644 app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamLandscapeContent.kt create mode 100644 common/design/src/main/java/com/androidvip/sysctlgui/design/utils/UiUtils.kt diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt index a058f77..e64c1f7 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt @@ -5,7 +5,6 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView @@ -24,12 +23,13 @@ import com.androidvip.sysctlgui.ui.user.UserParamsScreen @Composable internal fun AppNavHost( - innerPadding: PaddingValues, + modifier: Modifier = Modifier, + innerPadding: PaddingValues, // Handled in the outer scope, this is for inner scaffolds navController: NavHostController, startDestination: UiRoute = UiRoute.BrowseParams ) { NavHost( - modifier = Modifier.padding(innerPadding), + modifier = modifier, navController = navController, startDestination = startDestination, enterTransition = { scaleIn(initialScale = 0.9f) + fadeIn() }, diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt index 08cc9cd..01a668d 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainActivity.kt @@ -40,7 +40,6 @@ class MainActivity : ComponentActivity() { updateEdgeToEdgeConfiguration(forceDark) } - // TODO: Landscape Support SysctlGuiTheme( darkTheme = forceDark || isSystemInDarkTheme(), contrastLevel = themeState.contrastLevel, diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt index b493c5b..b2115d6 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavBar.kt @@ -1,15 +1,6 @@ package com.androidvip.sysctlgui.ui.main import androidx.compose.animation.AnimatedContent -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Build -import androidx.compose.material.icons.outlined.FavoriteBorder -import androidx.compose.material.icons.outlined.Home -import androidx.compose.material.icons.outlined.Settings -import androidx.compose.material.icons.rounded.Build -import androidx.compose.material.icons.rounded.Favorite -import androidx.compose.material.icons.rounded.Home -import androidx.compose.material.icons.rounded.Settings import androidx.compose.material3.Icon import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem @@ -17,7 +8,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.ui.res.stringResource +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewDynamicColors import androidx.compose.ui.tooling.preview.PreviewLightDark @@ -27,45 +18,12 @@ import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -import com.androidvip.sysctlgui.R -import com.androidvip.sysctlgui.core.navigation.TopLevelRoute -import com.androidvip.sysctlgui.core.navigation.UiRoute import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme @Composable internal fun MainNavBar(navController: NavHostController = rememberNavController()) { - val browseParamsTitle = stringResource(R.string.browse) - val presetsTitle = stringResource(R.string.presets) - val favoritesTitle = stringResource(R.string.favorites) - val settingsTitle = stringResource(R.string.settings) - val topLevelRoutes = remember { - listOf( - TopLevelRoute( - name = browseParamsTitle, - route = UiRoute.BrowseParams, - selectedIcon = Icons.Rounded.Home, - unselectedIcon = Icons.Outlined.Home - ), - TopLevelRoute( - name = presetsTitle, - route = UiRoute.Presets, - selectedIcon = Icons.Rounded.Build, - unselectedIcon = Icons.Outlined.Build - ), - TopLevelRoute( - name = favoritesTitle, - route = UiRoute.Favorites, - selectedIcon = Icons.Rounded.Favorite, - unselectedIcon = Icons.Outlined.FavoriteBorder - ), - TopLevelRoute( - name = settingsTitle, - route = UiRoute.Settings, - selectedIcon = Icons.Rounded.Settings, - unselectedIcon = Icons.Outlined.Settings - ) - ) - } + val context = LocalContext.current + val topLevelRoutes = remember { TopLevelRouteProvider(context) } NavigationBar { val navBackStackEntry by navController.currentBackStackEntryAsState() diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavRail.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavRail.kt new file mode 100644 index 0000000..e16feb2 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainNavRail.kt @@ -0,0 +1,77 @@ +package com.androidvip.sysctlgui.ui.main + +import androidx.compose.animation.AnimatedContent +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationRail +import androidx.compose.material3.NavigationRailItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.navigation.NavDestination.Companion.hasRoute +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph.Companion.findStartDestination +import androidx.navigation.NavHostController +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme + +@Composable +internal fun MainNavRail(navController: NavHostController = rememberNavController()) { + val context = LocalContext.current + val topLevelRoutes = remember { TopLevelRouteProvider(context) } + + NavigationRail { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentDestination = navBackStackEntry?.destination + + topLevelRoutes.forEach { route -> + val selected = currentDestination + ?.hierarchy + ?.any { it.hasRoute(route.route::class) } == true + + NavigationRailItem( + icon = { + AnimatedContent(targetState = selected) { selectedState -> + Icon( + imageVector = if (selectedState) { + route.selectedIcon + } else { + route.unselectedIcon + }, + contentDescription = route.name, + ) + } + }, + label = { + Text( + text = route.name, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + selected = selected, + onClick = { + navController.navigate(route.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + ) + } + } +} + +@Composable +@PreviewLightDark +private fun MainNavbarPreview() { + SysctlGuiTheme(dynamicColor = true) { + MainNavRail() + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt index 7005d0a..613c15e 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainScreen.kt @@ -2,12 +2,18 @@ package com.androidvip.sysctlgui.ui.main import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandHorizontally import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkHorizontally import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarDuration @@ -17,6 +23,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.PreviewDynamicColors import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -24,6 +31,7 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController import com.androidvip.sysctlgui.core.navigation.UiRoute import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.design.utils.isLandscape import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @@ -60,6 +68,7 @@ private fun MainScreenContent( startDestination: UiRoute = UiRoute.BrowseParams ) { val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current + val isLandscape = isLandscape() Scaffold( topBar = { @@ -82,7 +91,7 @@ private fun MainScreenContent( }, bottomBar = { AnimatedVisibility( - visible = state.showNavBar, + visible = state.showNavBar && !isLandscape, enter = expandVertically() + slideInVertically { it / 2 } + fadeIn(), exit = shrinkVertically() + slideOutVertically { it / 2 } + fadeOut(), label = "BottomBar" @@ -94,11 +103,25 @@ private fun MainScreenContent( SnackbarHost(snackbarHostState) }, content = { innerPadding -> - AppNavHost( - innerPadding = innerPadding, - navController = navController, - startDestination = startDestination - ) + Row(Modifier.padding(innerPadding)) { + if (isLandscape) { + AnimatedVisibility( + visible = state.showNavBar, + enter = expandHorizontally() + slideInHorizontally { it / 2 } + fadeIn(), + exit = shrinkHorizontally() + slideOutHorizontally { it / 2 } + fadeOut(), + label = "NavRail" + ) { + MainNavRail(navController = navController) + } + } + + AppNavHost( + modifier = Modifier.weight(1f), + innerPadding = innerPadding, + navController = navController, + startDestination = startDestination + ) + } } ) } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/TopLevelRouteProvider.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/TopLevelRouteProvider.kt new file mode 100644 index 0000000..cb5bb7f --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/TopLevelRouteProvider.kt @@ -0,0 +1,46 @@ +package com.androidvip.sysctlgui.ui.main + +import android.content.Context +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Build +import androidx.compose.material.icons.outlined.FavoriteBorder +import androidx.compose.material.icons.outlined.Home +import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material.icons.rounded.Build +import androidx.compose.material.icons.rounded.Favorite +import androidx.compose.material.icons.rounded.Home +import androidx.compose.material.icons.rounded.Settings +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.core.navigation.TopLevelRoute +import com.androidvip.sysctlgui.core.navigation.UiRoute + +object TopLevelRouteProvider { + operator fun invoke(context: Context): List> { + return listOf( + TopLevelRoute( + name = context.getString(R.string.browse), + route = UiRoute.BrowseParams, + selectedIcon = Icons.Rounded.Home, + unselectedIcon = Icons.Outlined.Home + ), + TopLevelRoute( + name = context.getString(R.string.presets), + route = UiRoute.Presets, + selectedIcon = Icons.Rounded.Build, + unselectedIcon = Icons.Outlined.Build + ), + TopLevelRoute( + name = context.getString(R.string.favorites), + route = UiRoute.Favorites, + selectedIcon = Icons.Rounded.Favorite, + unselectedIcon = Icons.Outlined.FavoriteBorder + ), + TopLevelRoute( + name = context.getString(R.string.settings), + route = UiRoute.Settings, + selectedIcon = Icons.Rounded.Settings, + unselectedIcon = Icons.Outlined.Settings + ) + ) + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt index ec7f5f4..faea61b 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseScreen.kt @@ -18,8 +18,11 @@ 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.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh @@ -50,11 +53,13 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.design.utils.isLandscape import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.domain.models.ParamDocumentation import com.androidvip.sysctlgui.models.UiKernelParam @@ -151,7 +156,7 @@ private fun ParamBrowseScreenContent( isRefreshing: Boolean, onRefresh: () -> Unit ) { - val listState = rememberLazyListState() + val listState = rememberLazyGridState() val pullRefreshState = rememberPullRefreshState(refreshing = isRefreshing, onRefresh = onRefresh) var headerVisible by remember { mutableStateOf(backEnabled) } @@ -196,25 +201,32 @@ private fun ParamBrowseScreenContent( .pullRefresh(pullRefreshState), ) { val spacerHeight by animateDpAsState(if (finalHeaderVisible) 56.dp else 0.dp) - LazyColumn( + val columns = if (isLandscape()) 2 else 1 + LazyVerticalGrid( + columns = GridCells.Fixed(columns), state = listState, modifier = Modifier.fillMaxSize() ) { - item { Spacer(modifier = Modifier.height(spacerHeight)) } + item(span = { GridItemSpan(columns) }) { + Spacer(modifier = Modifier.height(spacerHeight)) + } items( - count = params.size, - key = { index -> params[index].name } - ) { index -> + items = params, + key = { param -> param.name } + ) { param -> ParamFileRow( modifier = Modifier.animateItem(), - param = params[index], + param = param, + showFavoriteIcon = true, onParamClicked = onParamClicked, ) } if (documentation != null) { - item { Spacer(modifier = Modifier.height(56.dp)) } + item(span = { GridItemSpan(columns) }) { + Spacer(modifier = Modifier.height(56.dp)) + } } } @@ -302,6 +314,7 @@ private fun InfoItem( @Composable @PreviewLightDark +@Preview(device = "spec:parent=pixel_5,orientation=landscape") internal fun ParamBrowseScreenContentPreview() { fun mapFilesToParams(files: Array?): List { return files?.map { file -> diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt index 1a36e1a..30109b2 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/browse/ParamBrowseViewModel.kt @@ -5,6 +5,7 @@ import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.domain.repository.AppPrefs import com.androidvip.sysctlgui.domain.usecase.GetParamDocumentationUseCase import com.androidvip.sysctlgui.domain.usecase.GetParamsFromFilesUseCase +import com.androidvip.sysctlgui.domain.usecase.GetUserParamsUseCase import com.androidvip.sysctlgui.helpers.UiKernelParamMapper import com.androidvip.sysctlgui.models.UiKernelParam import com.androidvip.sysctlgui.utils.BaseViewModel @@ -20,9 +21,12 @@ import java.io.File class ParamBrowseViewModel( private val getParamsFromFiles: GetParamsFromFilesUseCase, private val getParamDocumentation: GetParamDocumentationUseCase, + private val getUserParams: GetUserParamsUseCase, private val appPrefs: AppPrefs, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : BaseViewModel() { + private val userParams = mutableListOf() + override fun createInitialState() = ParamBrowseState() init { @@ -31,6 +35,11 @@ class ParamBrowseViewModel( val startingDirectory = File(Consts.PROC_SYS) val params = getParams(startingDirectory) + runCatching { + userParams.clear() + userParams.addAll(getUserParams()) + } + setState { copy( params = params, @@ -64,7 +73,9 @@ class ParamBrowseViewModel( viewModelScope.launch { setState { copy(loading = true) } runCatching { - val newParamsDeferred = async { getParams(File(parentParam.path)) } + val newParamsDeferred = async { + getParams(File(parentParam.path)) + } val directoryDocumentationDeferred = async { runCatching { getParamDocumentation(parentParam) }.getOrNull() } @@ -119,7 +130,13 @@ class ParamBrowseViewModel( rootAwareFile.listFiles()?.toList() ?: emptyList() } - val params = getParamsFromFiles(fileList).map(UiKernelParamMapper::map) + val params = getParamsFromFiles(fileList).map { fileParam -> + UiKernelParamMapper.map(fileParam).copy( + isFavorite = userParams + .filter { it.isFavorite } + .any { it.name == fileParam.name } + ) + } if (appPrefs.listFoldersFirst) { params.sortedByDescending { it.isDirectory } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamLandscapeContent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamLandscapeContent.kt new file mode 100644 index 0000000..7a40198 --- /dev/null +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamLandscapeContent.kt @@ -0,0 +1,267 @@ +package com.androidvip.sysctlgui.ui.params.edit + +import android.content.ClipData +import android.widget.Toast +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AssistChip +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.platform.ClipEntry +import androidx.compose.ui.platform.LocalClipboard +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.domain.enums.CommitMode +import com.androidvip.sysctlgui.domain.models.ParamDocumentation +import com.androidvip.sysctlgui.models.UiKernelParam +import com.androidvip.sysctlgui.ui.components.ErrorContainer +import com.androidvip.sysctlgui.utils.performHapticFeedbackForToggle +import kotlinx.coroutines.launch +import org.intellij.lang.annotations.Language + +@Composable +internal fun EditParamLandscapeContent( + state: EditParamViewState, + showError: Boolean, + errorMessage: String, + onDocsReadMorePressed: () -> Unit, + onValueApply: (String) -> Unit, + onFavoriteToggle: (Boolean) -> Unit, + onTaskerClicked: (Boolean) -> Unit, + onErrorAnimationEnd: () -> Unit, + taskerListNameResolver: (Int) -> String = { "List #$it" }, +) { + val param = state.kernelParam + val view = LocalView.current + val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() + val clipboardManager = LocalClipboard.current + val copyParamContentToClipboard = { + val clipData = ClipData.newPlainText( + context.getString(R.string.kernel_params), + "${param.lastNameSegment}=${param.value} (${param.path})" + ) + val clipEntry = ClipEntry(clipData) + coroutineScope.launch { + clipboardManager.setClipEntry(clipEntry) + } + Toast.makeText( + context, + context.getString(R.string.copied_to_clipboard), + Toast.LENGTH_SHORT + ).show() + } + + Row { + Column( + modifier = Modifier + .weight(1f) + .background(MaterialTheme.colorScheme.background) + .verticalScroll(rememberScrollState()) + ) { + Text( + text = param.lastNameSegment, + style = MaterialTheme.typography.displayMedium, + modifier = Modifier + .combinedClickable( + enabled = true, + indication = null, + interactionSource = remember { MutableInteractionSource() }, + onClick = { + Toast.makeText( + context, + context.getString(R.string.long_press_to_copy), + Toast.LENGTH_SHORT + ).show() + }, + onLongClick = copyParamContentToClipboard + ) + .padding(start = 16.dp, end = 16.dp, top = 24.dp), + maxLines = 3, + color = MaterialTheme.colorScheme.onBackground, + overflow = TextOverflow.Ellipsis + ) + + Row( + modifier = Modifier.padding( + horizontal = 16.dp, + vertical = if (param.isTaskerParam) 0.dp else 24.dp + ), + verticalAlignment = Alignment.CenterVertically + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = param.name, + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(vertical = 8.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + Text( + text = param.path, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + + Spacer(modifier = Modifier.width(8.dp)) + + if (state.taskerAvailable) { + TaskerButton( + isTaskerParam = param.isTaskerParam, + onToggle = { newState -> + performHapticFeedbackForToggle(newState, view) + onTaskerClicked(newState) + }, + modifier = Modifier.scale(0.85f) + ) + } + + FavoriteButton( + isFavorite = param.isFavorite, + onFavoriteClick = { newState -> + performHapticFeedbackForToggle(newState, view) + onFavoriteToggle(newState) + }, + modifier = Modifier.scale(0.85f) + ) + } + + AnimatedVisibility(visible = param.isTaskerParam && state.taskerAvailable) { + val listName = taskerListNameResolver(param.taskerList) + AssistChip( + onClick = { onTaskerClicked(true) }, + modifier = Modifier.padding(16.dp), + label = { Text(text = stringResource(R.string.tasker_list_format, listName)) }, + leadingIcon = { + Icon( + painter = painterResource(R.drawable.ic_tasker), + contentDescription = stringResource(R.string.tasker_list), + tint = MaterialTheme.colorScheme.tertiary + ) + } + ) + } + } + + Column( + modifier = Modifier + .weight(1f) + .background(MaterialTheme.colorScheme.surfaceContainer) + .verticalScroll(rememberScrollState()) + ) { + ParamValueContent( + modifier = Modifier.padding(16.dp), + param = param, + keyboardType = state.keyboardType, + onValueApply = onValueApply + ) + + AnimatedVisibility( + visible = showError && errorMessage.isNotEmpty(), + modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + ) { + ErrorContainer(message = errorMessage, onAnimationEnd = onErrorAnimationEnd) + } + + ParamDocs( + modifier = Modifier.padding(16.dp), + documentation = state.documentation, + onReadMorePressed = onDocsReadMorePressed + ) + } + } +} + +@Composable +@Preview(device = "spec:parent=pixel_5,orientation=landscape") +private fun EditParamContentPreview() { + + @Language("html") + val htmlDocs = """ +

    Correctable memory errors are very common on servers. + Soft-offline is kernel’s solution for memory pages having + (excessive) corrected memory errors.

    + +

    For different types_of page, soft-offline has different behaviors / costs.

    +
      +
    • For a raw error page, soft-offline migrates the in-use page’s content to a new raw page.
    • +
    • For a page that is part of a transparent hugepage, soft-offline splits the transparent hugepage into raw pages, then migrates only the raw error page. As a result, user is transparently backed by 1 less hugepage, impacting memory access performance.
    • +
    • For a page that is part of a HugeTLB hugepage, soft-offline first migrates the entire HugeTLB hugepage, during which a free hugepage will be consumed as migration target. Then the original hugepage is dissolved into raw pages without compensation, reducing the capacity of the HugeTLB pool by 1.
    • +
    • It is user’s call to choose between reliability (staying away from fragile physical memory) vs performance / capacity implications in transparent and HugeTLB cases.
    • +
    + """.trimIndent() + .replace( + "", + "" + ) + .replace("", "") + + var showError by remember { mutableStateOf(true) } + + SysctlGuiTheme(dynamicColor = true) { + Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { + + val state = EditParamViewState( + kernelParam = UiKernelParam( + name = "vm.enable_soft_offline", + path = "/proc/sys/vm/enable_soft_offline", + value = "1", + taskerList = 1, + isTaskerParam = false, + isFavorite = false + ), + taskerAvailable = true, + keyboardType = KeyboardType.Number, + documentation = ParamDocumentation( + title = "vm.enable_soft_offline", + documentationText = "", + documentationHtml = htmlDocs, + url = "url" + ), + ) + EditParamLandscapeContent( + state = state, + showError = showError, + errorMessage = "Sysctl command for 'wm.swappiness' executed, " + + "but output did not confirm the change. Output: 'Access denied'. " + + "Try using '${CommitMode.ECHO}' mode.", + onValueApply = {}, + onTaskerClicked = {}, + onDocsReadMorePressed = {}, + onFavoriteToggle = {}, + onErrorAnimationEnd = { showError = false } + ) + } + } +} diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt index c6bc4ac..016586d 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/params/edit/EditParamScreen.kt @@ -75,6 +75,7 @@ import androidx.core.view.ViewCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.design.utils.isLandscape import com.androidvip.sysctlgui.domain.enums.CommitMode import com.androidvip.sysctlgui.domain.models.ParamDocumentation import com.androidvip.sysctlgui.models.UiKernelParam @@ -154,26 +155,51 @@ fun EditParamScreen( } } - EditParamContent( - state = state.value, - showError = showError, - errorMessage = errorMessage, - onValueApply = { - viewModel.onEvent(EditParamViewEvent.ApplyPressed(it)) - }, - onTaskerClicked = { - showSelectTaskerListDialog = it - viewModel.onEvent(EditParamViewEvent.TaskerTogglePressed(it, selectedOptionIndex)) - }, - onDocsReadMorePressed = { - viewModel.onEvent(EditParamViewEvent.DocumentationReadMoreClicked) - }, - onFavoriteToggle = { - viewModel.onEvent(EditParamViewEvent.FavoriteTogglePressed(it)) - }, - onErrorAnimationEnd = { showError = false }, - taskerListNameResolver = { listId -> taskerListOptions.getOrNull(listId).orEmpty() } - ) + if (isLandscape()) { + EditParamLandscapeContent( + state = state.value, + showError = showError, + errorMessage = errorMessage, + onValueApply = { + viewModel.onEvent(EditParamViewEvent.ApplyPressed(it)) + }, + onTaskerClicked = { + showSelectTaskerListDialog = it + viewModel.onEvent(EditParamViewEvent.TaskerTogglePressed(it, selectedOptionIndex)) + }, + onDocsReadMorePressed = { + viewModel.onEvent(EditParamViewEvent.DocumentationReadMoreClicked) + }, + onFavoriteToggle = { + viewModel.onEvent(EditParamViewEvent.FavoriteTogglePressed(it)) + }, + onErrorAnimationEnd = { showError = false }, + taskerListNameResolver = { listId -> + taskerListOptions.getOrNull(listId).orEmpty() + } + ) + } else { + EditParamContent( + state = state.value, + showError = showError, + errorMessage = errorMessage, + onValueApply = { + viewModel.onEvent(EditParamViewEvent.ApplyPressed(it)) + }, + onTaskerClicked = { + showSelectTaskerListDialog = it + viewModel.onEvent(EditParamViewEvent.TaskerTogglePressed(it, selectedOptionIndex)) + }, + onDocsReadMorePressed = { + viewModel.onEvent(EditParamViewEvent.DocumentationReadMoreClicked) + }, + onFavoriteToggle = { + viewModel.onEvent(EditParamViewEvent.FavoriteTogglePressed(it)) + }, + onErrorAnimationEnd = { showError = false }, + taskerListNameResolver = { listId -> taskerListOptions.getOrNull(listId).orEmpty() } + ) + } SingleChoiceDialog( showDialog = showSelectTaskerListDialog, @@ -344,7 +370,7 @@ private fun EditParamContent( } @Composable -private fun ParamValueContent( +fun ParamValueContent( modifier: Modifier = Modifier, param: UiKernelParam, keyboardType: KeyboardType, @@ -416,7 +442,7 @@ private fun ParamValueContent( } @Composable -fun EditableParamValue( +internal fun EditableParamValue( isEditing: Boolean, paramValue: String, editedValue: String, @@ -462,7 +488,7 @@ fun EditableParamValue( } @Composable -private fun ParamDocs( +internal fun ParamDocs( modifier: Modifier = Modifier, documentation: ParamDocumentation?, onReadMorePressed: () -> Unit, @@ -517,7 +543,7 @@ private fun ParamDocs( } @Composable -private fun DocumentationContent( +internal fun DocumentationContent( documentation: ParamDocumentation, onReadMorePressed: () -> Unit ) { diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/ImportPresetScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/ImportPresetScreen.kt index 00a2e1d..1184ab9 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/ImportPresetScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/ImportPresetScreen.kt @@ -58,6 +58,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.design.utils.isLandscape import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.ui.main.MainViewEvent import com.androidvip.sysctlgui.ui.main.MainViewModel @@ -122,15 +123,27 @@ fun ImportPresetScreen( ) { targetState -> when (targetState) { IncomingPresetsScreenState.Idle -> { - IncomingPresetsContent( - paramsToImport = state.paramsToImport, - onImportPressed = { - viewModel.onEvent(PresetsViewEvent.ConfirmImportPressed) - }, - onCancelPressed = { - viewModel.onEvent(PresetsViewEvent.CancelImportPressed) - } - ) + if (isLandscape()) { + IncomingPresetsLandscapeContent( + paramsToImport = state.paramsToImport, + onImportPressed = { + viewModel.onEvent(PresetsViewEvent.ConfirmImportPressed) + }, + onCancelPressed = { + viewModel.onEvent(PresetsViewEvent.CancelImportPressed) + } + ) + } else { + IncomingPresetsContent( + paramsToImport = state.paramsToImport, + onImportPressed = { + viewModel.onEvent(PresetsViewEvent.ConfirmImportPressed) + }, + onCancelPressed = { + viewModel.onEvent(PresetsViewEvent.CancelImportPressed) + } + ) + } } IncomingPresetsScreenState.Loading -> { @@ -266,6 +279,127 @@ private fun IncomingPresetsContent( } } +@Composable +private fun IncomingPresetsLandscapeContent( + paramsToImport: List, + onImportPressed: () -> Unit, + onCancelPressed: () -> Unit +) { + Row( + modifier = Modifier.fillMaxSize(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) { + itemsIndexed( + items = paramsToImport, + key = { index, item -> item.name } + ) { index, item -> + Row( + modifier = Modifier + .height(IntrinsicSize.Min) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .width(36.dp) + .fillMaxHeight() + .background(MaterialTheme.colorScheme.surfaceContainerHigh) + ) { + Text( + text = "${index + 1}", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.End, + modifier = Modifier + .fillMaxWidth() + .padding(4.dp) + ) + } + + val text = buildAnnotatedString { + withStyle( + SpanStyle( + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary + ) + ) { + append(item.name) + } + append("=") + withStyle( + SpanStyle( + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.tertiary + ) + ) { + append(item.value) + } + } + Text( + text = text, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + fontFamily = FontFamily.Monospace, + fontWeight = FontWeight.Medium, + modifier = Modifier + .weight(1f) + .padding(4.dp) + ) + } + } + } + + Column( + modifier = Modifier + .fillMaxHeight() + .weight(1f), + verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.parameters_found_format, paramsToImport.size), + style = MaterialTheme.typography.headlineMedium, + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + ) + + Row( + modifier = Modifier.padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally) + ) { + OutlinedButton( + modifier = Modifier.weight(1f), + onClick = onCancelPressed + ) { + Text(text = stringResource(android.R.string.cancel)) + } + + OutlinedButton( + modifier = Modifier.weight(1f), + onClick = onImportPressed, + enabled = paramsToImport.isNotEmpty(), + colors = ButtonDefaults.outlinedButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Text(text = stringResource(R.string.import_text)) + } + } + } + } +} + @Composable private fun LoadingIndicator() { Column( diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt index 5b4d1ea..49462bd 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/presets/PresetsScreen.kt @@ -36,12 +36,13 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.PreviewDynamicColors +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.design.utils.isLandscape import com.androidvip.sysctlgui.ui.components.ErrorContainer import com.androidvip.sysctlgui.ui.main.MainViewEvent import com.androidvip.sysctlgui.ui.main.MainViewModel @@ -136,18 +137,22 @@ private fun PresetsScreenContent( .padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - ImportCards( - onClick = onImportPressed, - title = stringResource(R.string.import_text), - description = stringResource(R.string.import_presets_description), - iconRes = R.drawable.ic_import - ) - ImportCards( - onClick = onExportPressed, - title = stringResource(R.string.export), - description = stringResource(R.string.export_presets_description), - iconRes = R.drawable.ic_export - ) + if (isLandscape()) { + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + ImportCards( + onImportPressed = onImportPressed, + onExportPressed = onExportPressed, + modifier = Modifier.weight(1f) + ) + } + } else { + ImportCards( + onImportPressed = onImportPressed, + onExportPressed = onExportPressed, + modifier = Modifier.fillMaxWidth(1f) + ) + } + Spacer(modifier = Modifier.weight(1f)) @@ -163,8 +168,34 @@ private fun PresetsScreenContent( } @Composable -private fun ImportCards(onClick: () -> Unit, title: String, description: String, iconRes: Int) { - Card(onClick = onClick, modifier = Modifier.fillMaxWidth()) { +private fun ImportCards( + onImportPressed: () -> Unit, + onExportPressed: () -> Unit, + modifier: Modifier = Modifier +) { + ImportCard( + onClick = onImportPressed, + modifier = modifier, + title = stringResource(R.string.import_text), + description = stringResource(R.string.import_presets_description), + iconRes = R.drawable.ic_import + ) + ImportCard( + modifier = modifier, + onClick = onExportPressed, + title = stringResource(R.string.export), + description = stringResource(R.string.export_presets_description), + iconRes = R.drawable.ic_export + ) +} + +@Composable +private fun ImportCard( + modifier: Modifier = Modifier, + onClick: () -> Unit, title: String, + description: String, iconRes: Int +) { + Card(onClick = onClick, modifier = modifier) { Row( modifier = Modifier .padding(16.dp) @@ -200,7 +231,7 @@ private fun ImportCards(onClick: () -> Unit, title: String, description: String, @Composable @PreviewLightDark -@PreviewDynamicColors +@Preview(device = "spec:parent=pixel_5,orientation=landscape") private fun PresetsScreenPreview() { SysctlGuiTheme(dynamicColor = true) { Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt index d376049..cea7c9d 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/search/SearchScreen.kt @@ -15,9 +15,11 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.rounded.Clear @@ -52,6 +54,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.design.utils.isLandscape import com.androidvip.sysctlgui.models.SearchHint import com.androidvip.sysctlgui.models.UiKernelParam import com.androidvip.sysctlgui.ui.main.MainViewEvent @@ -262,10 +265,18 @@ private fun SearchViewContent( ) { val historyHints = searchHints.filter { it.isFromHistory } val suggestionHints = searchHints.filter { !it.isFromHistory } + val columnsCount = if (isLandscape()) 2 else 1 + val hintItemColumns = GridCells.Fixed(columnsCount) - LazyColumn(modifier = Modifier.fillMaxWidth()) { + LazyVerticalGrid( + columns = hintItemColumns, + modifier = Modifier.fillMaxWidth() + ) { if (historyHints.isNotEmpty()) { - item(key = "history_header") { + item( + key = "history_header", + span = { GridItemSpan(maxLineSpan) } + ) { Text( text = stringResource(R.string.recent_searches), style = MaterialTheme.typography.titleSmall, @@ -273,6 +284,7 @@ private fun SearchViewContent( color = MaterialTheme.colorScheme.primary, modifier = Modifier .animateItem() + .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 12.dp) ) } @@ -307,22 +319,37 @@ private fun SearchViewContent( } .animateItem() .fillMaxWidth() - .padding(horizontal = 16.dp) + .padding(horizontal = 8.dp, vertical = 4.dp) ) } + if (suggestionHints.isNotEmpty()) { - item { HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) } + item( + key = "history_suggestion_divider", + span = { GridItemSpan(maxLineSpan) } + ) { + HorizontalDivider( + modifier = Modifier + .padding(vertical = 8.dp) + .padding(horizontal = 16.dp) + ) + } } } if (suggestionHints.isNotEmpty()) { - item { + item( + key = "suggestions_header", + span = { GridItemSpan(maxLineSpan) } + ) { Text( text = stringResource(R.string.suggestions), style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Bold, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp) + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp) ) } items(suggestionHints, key = { "hint:${it.hint}" }) { hintItem -> @@ -338,13 +365,16 @@ private fun SearchViewContent( onSearchActiveChange(false) } .fillMaxWidth() - .padding(horizontal = 16.dp) + .padding(horizontal = 8.dp, vertical = 4.dp) // Padding for grid spacing ) } } if (searchHints.isEmpty() && searchQuery.isBlank()) { - item { + item( + key = "empty_search_suggestions", + span = { GridItemSpan(maxLineSpan) } + ) { Box( modifier = Modifier .fillMaxWidth() @@ -364,21 +394,25 @@ private fun SearchResultsContent( searchQuery: String, onParamClicked: (UiKernelParam) -> Unit ) { + val columns = if (isLandscape()) 2 else 1 if (searchResults.isNotEmpty()) { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(top = 8.dp) + LazyVerticalGrid( + columns = GridCells.Fixed(columns), + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(bottom = 8.dp) ) { itemsIndexed(searchResults) { index, param -> ParamRow( - modifier = Modifier.animateItem(), + modifier = Modifier + .animateItem() + .fillMaxWidth() + .padding(4.dp), param = param, showFullName = true, onParamClicked = onParamClicked ) - if (index < searchResults.lastIndex) { + if (columns == 1 && index < searchResults.lastIndex) { HorizontalDivider() } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt index 792d189..26df95b 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt @@ -9,7 +9,10 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -20,12 +23,14 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.androidvip.sysctlgui.data.Prefs import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.design.utils.isLandscape import com.androidvip.sysctlgui.domain.enums.CommitMode import com.androidvip.sysctlgui.domain.enums.SettingItemType import com.androidvip.sysctlgui.domain.models.AppSetting @@ -112,34 +117,52 @@ private fun SettingsScreenContent( onValueChanged: (AppSetting<*>, Any) -> Unit ) { val groupedSettings = settings.groupBy { it.category } - LazyColumn(modifier = Modifier.fillMaxWidth()) { - groupedSettings.forEach { (category, settings) -> - item { + val columns = if (isLandscape()) 2 else 1 + + LazyVerticalGrid( + columns = GridCells.Fixed(columns), + modifier = Modifier.fillMaxWidth() + ) { + groupedSettings.forEach { (category, categorySettings) -> + item(span = { GridItemSpan(columns) }) { Text( text = category, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary, - modifier = Modifier.padding( - top = 8.dp, - bottom = 8.dp, - start = 56.dp, - end = 16.dp, - ) + modifier = Modifier + .fillMaxWidth() + .padding( + top = 8.dp, + bottom = 8.dp, + start = if (columns > 1) 16.dp else 56.dp, + end = 16.dp, + ) ) } - items(settings.size) { - val appSetting = settings[it] + + items( + items = categorySettings, + key = { setting -> setting.key } + ) { appSetting -> + val itemModifier = if (columns > 1) { + Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 4.dp) + } else { + Modifier.fillMaxWidth() + } + when (appSetting.type) { SettingItemType.Text -> { HeaderComponent( - modifier = Modifier.fillMaxWidth(), + modifier = itemModifier, appSetting = appSetting, onClick = onNavigateToUserParams ) } SettingItemType.List -> { TextSettingComponent( - modifier = Modifier.fillMaxWidth(), + modifier = itemModifier, appSetting = appSetting, onValueChange = { newValue -> onValueChanged(appSetting, newValue) @@ -148,7 +171,7 @@ private fun SettingsScreenContent( } SettingItemType.Switch -> { SwitchSettingComponent( - modifier = Modifier.fillMaxWidth(), + modifier = itemModifier, appSetting = appSetting, onValueChange = { newValue -> onValueChanged(appSetting, newValue) @@ -157,7 +180,7 @@ private fun SettingsScreenContent( } SettingItemType.Slider -> { SliderSettingComponent( - modifier = Modifier.fillMaxWidth(), + modifier = itemModifier, appSetting = appSetting, onValueChange = { newValue -> onValueChanged(appSetting, newValue) @@ -172,6 +195,7 @@ private fun SettingsScreenContent( @Composable @PreviewLightDark +@Preview(device = "spec:parent=pixel_5,orientation=landscape") internal fun SettingsScreenPreview() { SysctlGuiTheme { val settings = listOf( @@ -184,6 +208,14 @@ internal fun SettingsScreenPreview() { description = "List folders first when using the kernel parameter browser option", type = SettingItemType.Switch, ), + AppSetting( + key = Prefs.GuessInputType.key, + value = true, + category = "General", + title = "Guess input type", + description = "Description", + type = SettingItemType.Switch, + ), /////////// THEME SETTINGS //////////// diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt index e32aa7c..75bca1d 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt @@ -13,10 +13,11 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SwipeToDismissBox @@ -36,11 +37,12 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewScreenSizes import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme +import com.androidvip.sysctlgui.design.utils.isLandscape import com.androidvip.sysctlgui.domain.models.KernelParam import com.androidvip.sysctlgui.models.UiKernelParam import com.androidvip.sysctlgui.ui.main.MainViewEffect @@ -95,7 +97,10 @@ fun UserParamsScreen( is UserParamsViewEffect.ShowUndoSnackBar -> { mainViewModel.onEvent( MainViewEvent.ShowSnackbarRequested( - message = context.getString(R.string.favorite_param_deleted_format, effect.param.name), + message = context.getString( + R.string.favorite_param_deleted_format, + effect.param.name + ), actionLabel = context.getString(R.string.undo) ) ) @@ -123,7 +128,9 @@ private fun FavoritesScreenContent( onParamClicked: (UiKernelParam) -> Unit, onParamDeleteRequested: (UiKernelParam) -> Unit ) { - val listState = rememberLazyListState() + val isLandscape = isLandscape() + val gridState = rememberLazyGridState() + val columns = if (isLandscape) 2 else 1 AnimatedVisibility(visible = loading, enter = fadeIn(), exit = fadeOut()) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { @@ -156,15 +163,15 @@ private fun FavoritesScreenContent( return } - LazyColumn( - state = listState, + LazyVerticalGrid( + columns = GridCells.Fixed(columns), + state = gridState, modifier = Modifier.fillMaxSize() ) { - items( - count = favoriteParams.size, - key = { index -> favoriteParams[index].name } - ) { index -> - val param = favoriteParams[index] + itemsIndexed( + items = favoriteParams, + key = { _, param -> param.name } + ) { index, param -> var showParam by remember { mutableStateOf(true) } val dismissState = rememberSwipeToDismissBoxState() @@ -211,23 +218,21 @@ private fun FavoritesScreenContent( } ) { ParamFileRow( - modifier = Modifier.background(MaterialTheme.colorScheme.background), + modifier = Modifier + .background(MaterialTheme.colorScheme.background) + .fillMaxWidth(), param = param, - showFavoriteIcon = false, + showFavoriteIcon = true, onParamClicked = onParamClicked ) } } - - if (index < favoriteParams.lastIndex && showParam) { - HorizontalDivider() - } } } } @Composable -@Preview +@PreviewScreenSizes private fun FavoriteScreenContentPreview() { val params = listOf( UiKernelParam( diff --git a/common/design/src/main/java/com/androidvip/sysctlgui/design/utils/UiUtils.kt b/common/design/src/main/java/com/androidvip/sysctlgui/design/utils/UiUtils.kt new file mode 100644 index 0000000..13018c5 --- /dev/null +++ b/common/design/src/main/java/com/androidvip/sysctlgui/design/utils/UiUtils.kt @@ -0,0 +1,12 @@ +package com.androidvip.sysctlgui.design.utils + +import android.content.res.Configuration +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.ui.platform.LocalConfiguration + +@Composable +@ReadOnlyComposable +fun isLandscape(): Boolean { + return LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE +} From f3ae9096bf2d15784cf8a6cf8705114906b0090b Mon Sep 17 00:00:00 2001 From: Lennoard Date: Sun, 17 Aug 2025 21:52:57 -0300 Subject: [PATCH 18/23] feature: added "about" headers --- .../sysctlgui/ui/main/AppNavHost.kt | 4 +- .../sysctlgui/ui/main/MainTopBar.kt | 2 +- .../sysctlgui/ui/main/MainViewState.kt | 2 +- .../sysctlgui/ui/settings/SettingsScreen.kt | 79 +++++++++++++++++-- .../ui/settings/SettingsViewModel.kt | 32 +++++++- .../ui/settings/components/HeaderComponent.kt | 29 ++++--- .../components/TextSettingComponent.kt | 27 ++++--- .../ui/settings/model/SettingsViewEvent.kt | 5 ++ .../sysctlgui/ui/user/UserParamsScreen.kt | 6 ++ app/src/main/res/values-pt-rBR/strings.xml | 3 + app/src/main/res/values/strings.xml | 3 + .../com/androidvip/sysctlgui/data/Prefs.kt | 2 +- .../sysctlgui/data/di/DataModule.kt | 1 + .../sysctlgui/data/repository/AppPrefsImpl.kt | 4 +- .../repository/AppSettingsRepositoryImpl.kt | 69 ++++++++++++++-- data/src/main/res/drawable/ic_code.xml | 5 ++ data/src/main/res/drawable/ic_group.xml | 5 ++ data/src/main/res/drawable/ic_language.xml | 5 ++ data/src/main/res/values-pt-rBR/strings.xml | 10 +++ data/src/main/res/values/strings.xml | 10 +++ .../sysctlgui/domain/models/AppSetting.kt | 10 ++- 21 files changed, 274 insertions(+), 39 deletions(-) create mode 100644 data/src/main/res/drawable/ic_code.xml create mode 100644 data/src/main/res/drawable/ic_group.xml create mode 100644 data/src/main/res/drawable/ic_language.xml diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt index e64c1f7..f41e419 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/AppNavHost.kt @@ -84,8 +84,8 @@ internal fun AppNavHost( composable { SettingsScreen( - onNavigateToUserParams = { - navController.navigate(UiRoute.UserParams) + onNavigate = { route -> + navController.navigate(route) } ) } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainTopBar.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainTopBar.kt index c82019a..ae10541 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainTopBar.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainTopBar.kt @@ -65,7 +65,7 @@ fun MainTopBar( private fun MainTopBarPreview() { SysctlGuiTheme { MainTopBar( - title = "SysctlGUI", + title = "Sysctl GUI", showBack = false, showSearch = true, onSearchPressed = {}, diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt index 5c634e7..bc6443c 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/main/MainViewState.kt @@ -4,7 +4,7 @@ import androidx.compose.material3.SnackbarResult import com.androidvip.sysctlgui.data.repository.CONTRAST_LEVEL_NORMAL data class MainViewState( - val topBarTitle: String = "SysctlGUI", + val topBarTitle: String = "Sysctl GUI", val showTopBar: Boolean = true, val showNavBar: Boolean = true, val showBackButton: Boolean = false, diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt index 26df95b..2bf4142 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsScreen.kt @@ -3,16 +3,23 @@ package com.androidvip.sysctlgui.ui.settings import android.Manifest import android.content.pm.PackageManager import android.os.Build +import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Info +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -21,13 +28,18 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.core.navigation.UiRoute import com.androidvip.sysctlgui.data.Prefs import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme import com.androidvip.sysctlgui.design.utils.isLandscape @@ -38,11 +50,13 @@ import com.androidvip.sysctlgui.ui.main.MainViewEvent import com.androidvip.sysctlgui.ui.main.MainViewModel import com.androidvip.sysctlgui.ui.main.MainViewState import com.androidvip.sysctlgui.ui.settings.components.HeaderComponent +import com.androidvip.sysctlgui.ui.settings.components.SettingsComponentColumn import com.androidvip.sysctlgui.ui.settings.components.SliderSettingComponent import com.androidvip.sysctlgui.ui.settings.components.SwitchSettingComponent import com.androidvip.sysctlgui.ui.settings.components.TextSettingComponent import com.androidvip.sysctlgui.ui.settings.model.SettingsViewEffect import com.androidvip.sysctlgui.ui.settings.model.SettingsViewEvent +import com.androidvip.sysctlgui.utils.browse import org.koin.androidx.compose.koinViewModel internal const val DISABLED_ALPHA = 0.38f @@ -51,7 +65,7 @@ internal const val DISABLED_ALPHA = 0.38f internal fun SettingsScreen( mainViewModel: MainViewModel = koinViewModel(), viewModel: SettingsViewModel = koinViewModel(), - onNavigateToUserParams: () -> Unit + onNavigate: (UiRoute) -> Unit ) { val state = viewModel.uiState.collectAsStateWithLifecycle() val context = LocalContext.current @@ -97,13 +111,27 @@ internal fun SettingsScreen( } } } + + is SettingsViewEffect.OpenBrowser -> { + context.browse(effect.url) + } + + is SettingsViewEffect.Navigate -> { + onNavigate(effect.route) + } + + is SettingsViewEffect.ShowToast -> { + Toast.makeText(context, effect.message, Toast.LENGTH_SHORT).show() + } } } } SettingsScreenContent( settings = state.value.settings, - onNavigateToUserParams = onNavigateToUserParams, + onSettingHeaderClicked = { appSetting -> + viewModel.onEvent(SettingsViewEvent.SettingHeaderClicked(appSetting)) + }, onValueChanged = { appSetting, newValue -> viewModel.onEvent(SettingsViewEvent.SettingValueChanged(appSetting, newValue)) } @@ -113,7 +141,7 @@ internal fun SettingsScreen( @Composable private fun SettingsScreenContent( settings: List> = emptyList(), - onNavigateToUserParams: () -> Unit, + onSettingHeaderClicked: (AppSetting<*>) -> Unit, onValueChanged: (AppSetting<*>, Any) -> Unit ) { val groupedSettings = settings.groupBy { it.category } @@ -157,7 +185,7 @@ private fun SettingsScreenContent( HeaderComponent( modifier = itemModifier, appSetting = appSetting, - onClick = onNavigateToUserParams + onClick = { onSettingHeaderClicked(appSetting) } ) } SettingItemType.List -> { @@ -190,6 +218,47 @@ private fun SettingsScreenContent( } } } + + item(span = { GridItemSpan(columns) }) { + Box(contentAlignment = Alignment.Center) { + Row( + modifier = Modifier.padding(all = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(space = 16.dp) + ) { + + Icon( + imageVector = Icons.Rounded.Info, + contentDescription = null, + tint = MaterialTheme.colorScheme.onBackground, + modifier = Modifier.size(24.dp) + ) + + SettingsComponentColumn( + title = stringResource(R.string.about), + description = stringResource(R.string.sysctl_help), + modifier = Modifier.fillMaxWidth() + ) + } + } + } + + item(span = { GridItemSpan(columns) }) { + Text( + text = "Developed with ❤️ by Lennoard Silva\nAndroid enthusiast 🤖", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding( + top = 64.dp, + bottom = 32.dp, + start = 24.dp, + end = 24.dp, + ) + ) + } } } @@ -257,7 +326,7 @@ internal fun SettingsScreenPreview() { Box(modifier = Modifier.background(MaterialTheme.colorScheme.background)) { SettingsScreenContent( settings = settings, - onNavigateToUserParams = {}, + onSettingHeaderClicked = {}, onValueChanged = { _, _ -> } ) } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsViewModel.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsViewModel.kt index 0968098..412eb38 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsViewModel.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/SettingsViewModel.kt @@ -3,8 +3,16 @@ package com.androidvip.sysctlgui.ui.settings import android.content.SharedPreferences import androidx.core.content.edit import androidx.lifecycle.viewModelScope +import com.androidvip.sysctlgui.R +import com.androidvip.sysctlgui.core.navigation.UiRoute import com.androidvip.sysctlgui.data.Prefs +import com.androidvip.sysctlgui.domain.StringProvider import com.androidvip.sysctlgui.domain.enums.SettingItemType +import com.androidvip.sysctlgui.domain.models.KEY_CONTRIBUTORS +import com.androidvip.sysctlgui.domain.models.KEY_DELETE_HISTORY +import com.androidvip.sysctlgui.domain.models.KEY_MANAGE_PARAMS +import com.androidvip.sysctlgui.domain.models.KEY_SOURCE_CODE +import com.androidvip.sysctlgui.domain.models.KEY_TRANSLATIONS import com.androidvip.sysctlgui.domain.usecase.GetAppSettingsUseCase import com.androidvip.sysctlgui.ui.settings.model.SettingsViewEffect import com.androidvip.sysctlgui.ui.settings.model.SettingsViewEvent @@ -14,7 +22,8 @@ import kotlinx.coroutines.launch class SettingsViewModel( private val sharedPreferences: SharedPreferences, - private val getSettings: GetAppSettingsUseCase + private val getSettings: GetAppSettingsUseCase, + private val stringProvider: StringProvider ) : BaseViewModel() { init { @@ -66,6 +75,27 @@ class SettingsViewModel( loadSettings() } } + + is SettingsViewEvent.SettingHeaderClicked<*> -> { + when (event.appSetting.key) { + KEY_MANAGE_PARAMS -> { + setEffect { SettingsViewEffect.Navigate(UiRoute.UserParams) } + } + + KEY_DELETE_HISTORY -> { + sharedPreferences.edit { remove(Prefs.SearchHistory.key) } + setEffect { + SettingsViewEffect.ShowToast(stringProvider.getString(R.string.history_deleted)) + } + } + + KEY_SOURCE_CODE, KEY_CONTRIBUTORS, KEY_TRANSLATIONS -> { + setEffect { + SettingsViewEffect.OpenBrowser(event.appSetting.value as String) + } + } + } + } } } diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/HeaderComponent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/HeaderComponent.kt index 2c7c59a..a19637d 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/HeaderComponent.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/HeaderComponent.kt @@ -4,12 +4,16 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import com.androidvip.sysctlgui.domain.models.AppSetting @@ -17,22 +21,29 @@ import com.androidvip.sysctlgui.domain.models.AppSetting fun HeaderComponent( modifier: Modifier = Modifier, appSetting: AppSetting<*>, - onClick: () -> Unit, - icon: @Composable (() -> Unit)? = null + onClick: () -> Unit ) { - Box(modifier = modifier.clickable(onClick = onClick)) { + Box( + modifier = modifier.clickable(onClick = onClick), + contentAlignment = Alignment.Center + ) { Row( modifier = Modifier.padding(all = 16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(space = 16.dp) ) { - Box( - modifier = Modifier - .align(Alignment.CenterVertically) - .size(24.dp) - ) { - icon?.invoke() + if (appSetting.iconResource != null) { + Box(modifier = Modifier.align(Alignment.CenterVertically)) { + Icon( + painter = painterResource(appSetting.iconResource!!), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(24.dp) + ) + } + } else { + Spacer(modifier = Modifier.size(24.dp)) } SettingsComponentColumn( diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/TextSettingComponent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/TextSettingComponent.kt index 328dbeb..ecf396d 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/TextSettingComponent.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/components/TextSettingComponent.kt @@ -5,12 +5,14 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -20,9 +22,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp +import com.androidvip.sysctlgui.R import com.androidvip.sysctlgui.design.theme.SysctlGuiTheme import com.androidvip.sysctlgui.domain.enums.SettingItemType import com.androidvip.sysctlgui.domain.models.AppSetting @@ -31,8 +35,7 @@ import com.androidvip.sysctlgui.domain.models.AppSetting fun TextSettingComponent( modifier: Modifier = Modifier, appSetting: AppSetting<*>, - onValueChange: (String) -> Unit, - icon: @Composable (() -> Unit)? = null + onValueChange: (String) -> Unit ) { var expanded by remember { mutableStateOf(false) } @@ -47,12 +50,17 @@ fun TextSettingComponent( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(space = 16.dp) ) { - Box( - modifier = Modifier - .align(Alignment.CenterVertically) - .size(24.dp) - ) { - icon?.invoke() + if (appSetting.iconResource != null) { + Box(modifier = Modifier.align(Alignment.CenterVertically)) { + Icon( + painter = painterResource(appSetting.iconResource!!), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.size(24.dp) + ) + } + } else { + Spacer(modifier = Modifier.size(24.dp)) } SettingsComponentColumn( @@ -101,6 +109,7 @@ private fun TextSettingComponentPreview() { description = "Description", value = "sysctl", category = "", + iconResource = R.drawable.ic_history, values = listOf("sysctl", "echo"), type = SettingItemType.List ), diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/model/SettingsViewEvent.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/model/SettingsViewEvent.kt index 21a61b8..75e2859 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/model/SettingsViewEvent.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/settings/model/SettingsViewEvent.kt @@ -1,13 +1,18 @@ package com.androidvip.sysctlgui.ui.settings.model +import com.androidvip.sysctlgui.core.navigation.UiRoute import com.androidvip.sysctlgui.domain.models.AppSetting sealed interface SettingsViewEvent { class SettingValueChanged(val appSetting: AppSetting, val newValue: Any) : SettingsViewEvent + class SettingHeaderClicked(val appSetting: AppSetting) : SettingsViewEvent } sealed interface SettingsViewEffect { object RequestNotificationPermission : SettingsViewEffect + class OpenBrowser(val url: String) : SettingsViewEffect + class Navigate(val route: UiRoute) : SettingsViewEffect + class ShowToast(val message: String) : SettingsViewEffect } data class SettingsViewState( diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt index 75bca1d..bd5b529 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/ui/user/UserParamsScreen.kt @@ -65,6 +65,11 @@ fun UserParamsScreen( ) { val context = LocalContext.current val state by viewModel.uiState.collectAsStateWithLifecycle() + val appBarTitle = if (state.userParams.all { it.isFavorite }) { + stringResource(R.string.app_name) + } else { + stringResource(R.string.startup_params) + } LaunchedEffect(Unit) { viewModel.onEvent(UserParamsViewEvent.ScreenLoaded(filterPredicate)) @@ -72,6 +77,7 @@ fun UserParamsScreen( mainViewModel.onEvent( MainViewEvent.OnSateChangeRequested( MainViewState( + topBarTitle = appBarTitle, showTopBar = true, showNavBar = true, showBackButton = false, diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 41b8666..bfdbbcf 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -102,4 +102,7 @@ Falha na execução do comando Sugestões Parâmetros favoritos + Histórico deletado + Parâmetros da inicialização + Sysctl é usado para modificar parâmetros do kernel em tempo de execução. Os parâmetros disponíveis são aqueles listados em /proc/sys e o objetivo principal deste aplicativo é fornecer uma maneira gráfica de editá-los. \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e8c6fdf..2a5ce33 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -102,4 +102,7 @@ Command execution failed Suggestions Favorite Kernel Params + History deleted + Startup params + Sysctl is used to modify kernel parameters at runtime. The parameters available are those listed under /proc/sys and this app\'s main purpose is to provide a graphical way to edit them. diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/Prefs.kt b/data/src/main/java/com/androidvip/sysctlgui/data/Prefs.kt index 94c6827..02b2ddc 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/Prefs.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/Prefs.kt @@ -4,7 +4,7 @@ enum class Prefs(val key: String) { ListFoldersFirst("list_folders_first"), GuessInputType("guess_input_type"), CommitMode("commit_mode"), - ALLOW_BLANK("allow_blank_values"), + AllowBlankValues("allow_blank_values"), UseBusybox("use_busybox"), RunOnStartup("run_on_start_up"), StartupDelay("startup_delay"), diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt b/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt index 29155ea..268600b 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt @@ -60,6 +60,7 @@ val repositoryModule = module { AppSettingsRepositoryImpl( context = androidContext(), sharedPreferences = get(), + isTaskerInstalled = get(), rootUtils = get() ) } diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppPrefsImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppPrefsImpl.kt index 1ef8f3f..d21206f 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppPrefsImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppPrefsImpl.kt @@ -40,9 +40,9 @@ class AppPrefsImpl(private val prefs: SharedPreferences) : AppPrefs { prefs.edit { putString(Prefs.CommitMode.key, value) } } override var allowBlankValues: Boolean - get() = prefs.getBoolean(Prefs.ALLOW_BLANK.key, false) + get() = prefs.getBoolean(Prefs.AllowBlankValues.key, false) set(value) { - prefs.edit { putBoolean(Prefs.ALLOW_BLANK.key, value) } + prefs.edit { putBoolean(Prefs.AllowBlankValues.key, value) } } override var useBusybox: Boolean get() = prefs.getBoolean(Prefs.UseBusybox.key, false) diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt index 68d8269..c0d87ca 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/repository/AppSettingsRepositoryImpl.kt @@ -9,7 +9,13 @@ import com.androidvip.sysctlgui.data.utils.RootUtils import com.androidvip.sysctlgui.domain.enums.CommitMode import com.androidvip.sysctlgui.domain.enums.SettingItemType import com.androidvip.sysctlgui.domain.models.AppSetting +import com.androidvip.sysctlgui.domain.models.KEY_CONTRIBUTORS +import com.androidvip.sysctlgui.domain.models.KEY_DELETE_HISTORY +import com.androidvip.sysctlgui.domain.models.KEY_MANAGE_PARAMS +import com.androidvip.sysctlgui.domain.models.KEY_SOURCE_CODE +import com.androidvip.sysctlgui.domain.models.KEY_TRANSLATIONS import com.androidvip.sysctlgui.domain.repository.AppSettingsRepository +import com.androidvip.sysctlgui.domain.usecase.IsTaskerInstalledUseCase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext @@ -18,9 +24,11 @@ class AppSettingsRepositoryImpl( private val context: Context, private val sharedPreferences: SharedPreferences, private val rootUtils: RootUtils, + private val isTaskerInstalled: IsTaskerInstalledUseCase, private val ioContext: CoroutineContext = Dispatchers.IO ) : AppSettingsRepository { override suspend fun getAppSettings(): List> = withContext(ioContext) { + val isTaskerInstalled = isTaskerInstalled() val usingDynamicColors = sharedPreferences.getBoolean(Prefs.DynamicColors.key, false) val supportsDynamicColors = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S val currentCommitMode = sharedPreferences.getString( @@ -54,6 +62,14 @@ class AppSettingsRepositoryImpl( description = context.getString(R.string.prefs_online_docs_description), type = SettingItemType.Switch, ), + AppSetting( + key = KEY_DELETE_HISTORY, + value = Unit, + category = context.getString(R.string.prefs_category_general), + title = context.getString(R.string.pref_search_history_title), + description = context.getString(R.string.pref_search_history_description), + type = SettingItemType.Text, + ), /////////// THEME SETTINGS //////////// @@ -112,12 +128,21 @@ class AppSettingsRepositoryImpl( type = SettingItemType.Switch, ), AppSetting( - key = Prefs.ALLOW_BLANK.key, - value = sharedPreferences.getBoolean(Prefs.ALLOW_BLANK.key, false), + key = Prefs.AllowBlankValues.key, + value = sharedPreferences.getBoolean(Prefs.AllowBlankValues.key, false), category = context.getString(R.string.prefs_category_operations), title = context.getString(R.string.prefs_allow_blank_values_title), type = SettingItemType.Switch, ), + AppSetting( + key = Prefs.ShowTaskerToast.key, + enabled = isTaskerInstalled, + value = sharedPreferences.getBoolean(Prefs.ShowTaskerToast.key, isTaskerInstalled), + category = context.getString(R.string.prefs_category_operations), + title = context.getString(R.string.prefs_show_tasker_toast_title), + description = context.getString(R.string.prefs_show_toast_description), + type = SettingItemType.Switch, + ), /////////// STARTUP SETTINGS //////////// @@ -129,6 +154,14 @@ class AppSettingsRepositoryImpl( description = context.getString(R.string.prefs_run_on_startup_description), type = SettingItemType.Switch, ), + AppSetting( + key = KEY_MANAGE_PARAMS, + value = Unit, + category = context.getString(R.string.prefs_category_startup), + title = context.getString(R.string.prefs_manage_parameters_title), + description = context.getString(R.string.prefs_manage_parameters_description), + type = SettingItemType.Text, + ), AppSetting( key = Prefs.StartupDelay.key, value = sharedPreferences.getInt(Prefs.StartupDelay.key, 0), @@ -138,12 +171,33 @@ class AppSettingsRepositoryImpl( type = SettingItemType.Slider, values = (0..10).toList(), ), + + /////////// OTHERS //////////// AppSetting( - key = "", - value = Unit, - category = context.getString(R.string.prefs_category_startup), - title = context.getString(R.string.prefs_manage_parameters_title), - description = context.getString(R.string.prefs_manage_parameters_description), + key = KEY_SOURCE_CODE, + value = SOURCE_CODE_URL, + category = context.getString(R.string.prefs_category_others), + title = context.getString(R.string.pref_source_code_title), + description = context.getString(R.string.pref_source_code_description), + iconResource = R.drawable.ic_code, + type = SettingItemType.Text, + ), + AppSetting( + key = KEY_CONTRIBUTORS, + value = "$SOURCE_CODE_URL/graphs/contributors", + category = context.getString(R.string.prefs_category_others), + title = context.getString(R.string.pref_contributors_title), + description = "free-bots, mikropsoft (Holi)", + iconResource = R.drawable.ic_group, + type = SettingItemType.Text, + ), + AppSetting( + key = KEY_TRANSLATIONS, + value = "$SOURCE_CODE_URL/blob/develop/TRANSLATING.md", + category = context.getString(R.string.prefs_category_others), + title = context.getString(R.string.pref_translations_title), + description = context.getString(R.string.pref_translations_description), + iconResource = R.drawable.ic_language, type = SettingItemType.Text, ) ) @@ -153,3 +207,4 @@ class AppSettingsRepositoryImpl( const val CONTRAST_LEVEL_NORMAL = 1 const val CONTRAST_LEVEL_MEDIUM = 2 const val CONTRAST_LEVEL_HEIGH = 3 +const val SOURCE_CODE_URL = "https://github.com/Lennoard/SysctlGUI" diff --git a/data/src/main/res/drawable/ic_code.xml b/data/src/main/res/drawable/ic_code.xml new file mode 100644 index 0000000..369840b --- /dev/null +++ b/data/src/main/res/drawable/ic_code.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/data/src/main/res/drawable/ic_group.xml b/data/src/main/res/drawable/ic_group.xml new file mode 100644 index 0000000..e6b060c --- /dev/null +++ b/data/src/main/res/drawable/ic_group.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/data/src/main/res/drawable/ic_language.xml b/data/src/main/res/drawable/ic_language.xml new file mode 100644 index 0000000..643d3fc --- /dev/null +++ b/data/src/main/res/drawable/ic_language.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/data/src/main/res/values-pt-rBR/strings.xml b/data/src/main/res/values-pt-rBR/strings.xml index 53df607..73809c7 100644 --- a/data/src/main/res/values-pt-rBR/strings.xml +++ b/data/src/main/res/values-pt-rBR/strings.xml @@ -27,4 +27,14 @@ Atraso em segundos antes de reaplicar os parâmetros na inicialização Gerenciar parâmetros Gerenciar os parâmetros que serão aplicados na inicialização + Mostrar toast do Tasker + Mostrar uma mensagem quando um parâmetro do Tasker for aplicado + Histórico de pesquisa + Toque para limpar o histórico de pesquisa + Outros + Código fonte + Confira o código fonte no GitHub! + Contribuidores + Traduções + Ajude a traduzir este aplicativo \ No newline at end of file diff --git a/data/src/main/res/values/strings.xml b/data/src/main/res/values/strings.xml index 1d0666e..2b01f67 100644 --- a/data/src/main/res/values/strings.xml +++ b/data/src/main/res/values/strings.xml @@ -27,4 +27,14 @@ Delay in seconds before applying parameters on startup Manage parameters Manage the parameters that will be applied at startup + Show Tasker toast + Show toast when a Tasker parameter is applied + Search history + Tap to clear search history + Others + Source code + Check the sauce code on GitHub! + Contributors + Translations + Help translate this application diff --git a/domain/src/main/java/com/androidvip/sysctlgui/domain/models/AppSetting.kt b/domain/src/main/java/com/androidvip/sysctlgui/domain/models/AppSetting.kt index 7754d4a..e2a5cdb 100644 --- a/domain/src/main/java/com/androidvip/sysctlgui/domain/models/AppSetting.kt +++ b/domain/src/main/java/com/androidvip/sysctlgui/domain/models/AppSetting.kt @@ -18,6 +18,7 @@ import com.androidvip.sysctlgui.domain.enums.SettingItemType * @property category The group or section this setting belongs to, used for organization in the UI. * @property type Defines how the setting is presented and interacted with in the UI (e.g., switch, list). * @property values An optional list of possible values for the setting, typically used for dropdowns or selection lists. + * @property iconResource An optional resource ID for an icon that can be displayed with the setting. */ data class AppSetting( val key: String, @@ -27,5 +28,12 @@ data class AppSetting( val description: String? = null, val category: String, val type: SettingItemType, - val values: List? = null + val values: List? = null, + val iconResource: Int? = null ) + +const val KEY_MANAGE_PARAMS = "manageParams" +const val KEY_DELETE_HISTORY = "deleteHistory" +const val KEY_SOURCE_CODE = "sauce" +const val KEY_CONTRIBUTORS = "contributors" +const val KEY_TRANSLATIONS = "translations" \ No newline at end of file From 384f7503b9146150a0fce852883311f64937231f Mon Sep 17 00:00:00 2001 From: Lennoard Date: Mon, 18 Aug 2025 00:03:19 -0300 Subject: [PATCH 19/23] release: [3.0.0] versionCode 17 Signed-off-by: Lennoard --- README.md | 41 +++++++++--- TRANSLATING.md | 62 +++++++++++++++++++ app/build.gradle.kts | 11 ++-- app/proguard-kt.pro | 13 ---- app/proguard-rules.pro | 35 +++++------ .../com/androidvip/sysctlgui/SysctlGuiApp.kt | 4 +- common/design/proguard-rules.pro | 7 ++- common/utils/build.gradle.kts | 2 +- common/utils/proguard-rules.pro | 6 +- data/consumer-rules.pro | 1 + data/proguard-rules.pro | 3 +- .../sysctlgui/data/di/DataModule.kt | 8 +-- gradle/libs.versions.toml | 2 + 13 files changed, 137 insertions(+), 58 deletions(-) create mode 100644 TRANSLATING.md delete mode 100644 app/proguard-kt.pro diff --git a/README.md b/README.md index 884dd9b..e2a4226 100644 --- a/README.md +++ b/README.md @@ -23,20 +23,43 @@ A GUI application for Android sysctl to edit kernel variables ## Features - Browse filesystem for specific kernel parameters - Select parameters from a searchable list -- Information about known parameters +- Show documentation for known parameters - Load parameters from a configuration file - Reapply parameters at startup - Mark parameters as favorite for easy access ## Technologies -- MVI / MVVM for user params -- [Jetpack Compose](https://developer.android.com/jetpack/compose) Material 3 UI -- [Jetpack Data Binding](https://developer.android.com/topic/libraries/data-binding) -- [Jetpack View Binding](https://developer.android.com/topic/libraries/view-binding) -- Lifecycle-aware Kotlin Coroutines -- Kotlin Flows -- Dependency injection with [Koin](https://insert-koin.io/) +This project utilizes a modern Android development stack, leveraging a comprehensive suite of libraries and tools: + +- **Core & Architecture:** + - Architectural Patterns: MVI, reactive and maintainable. + - [Kotlin](https://kotlinlang.org/): For modern, concise, and safe programming. + - Android Jetpack: + - [Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle) + - [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) + - [Navigation Component](https://developer.android.com/guide/navigation) + - [Room](https://developer.android.com/training/data-storage/room): For local data persistence. + - [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager): For deferrable, asynchronous tasks. +- **UI Development:** + - [Jetpack Compose](https://developer.android.com/jetpack/compose): For building native UIs with a declarative approach. + - Compose Material 3 & Material Components + - [Jetpack Glance](https://developer.android.com/develop/ui/compose/glance): For creating App Widgets with Jetpack Compose. +- **Asynchronous Programming:** + - [Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) & [Flows](https://kotlinlang.org/docs/flow.html): For efficient and structured background tasks and reactive data streams. +- **Utilities:** + - [Koin](https://insert-koin.io/): Dependency injection framework for Kotlin. + - [Ktor Client](https://ktor.io/docs/client-reference.html): For making HTTP requests (used for parameter documentation). + - [Libsu](https://github.com/topjohnwu/libsu): For interacting with root services. + - [Jsoup](https://jsoup.org/): For parsing HTML (used for parameter documentation). + - [Kotlinx Serialization](https://github.com/Kotlin/kotlinx.serialization): For JSON serialization/deserialization. + +## Contributing + +We welcome contributions to SysctlGUI! + +### Translations +If you'd like to help translate the app into other languages, please see the [translation guide](TRANSLATING.md) for instructions on how to get started. Your contributions will help make SysctlGUI accessible to a wider audience. ## Download @@ -47,7 +70,7 @@ A GUI application for Android sysctl to edit kernel variables This project is licensed under the terms of the MIT license. -> Copyright (c) 2019-2024 Lennoard. +> Copyright (c) 2019-2025 Lennoard. > > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: > diff --git a/TRANSLATING.md b/TRANSLATING.md new file mode 100644 index 0000000..b0758f5 --- /dev/null +++ b/TRANSLATING.md @@ -0,0 +1,62 @@ +# Translating SysctlGUI + +Thank you for your interest in translating SysctlGUI! Your contributions help make the app accessible to a wider audience. + +## How to Contribute + +1. **Fork the Repository:** Start by forking the [SysctlGUI repository](https://github.com/your-github-username/SysctlGUI) (replace `your-github-username/SysctlGUI` with the actual repository URL) to your own GitHub account. +2. **Clone Your Fork:** Clone your forked repository to your local machine. + ```bash + git clone https://github.com/YOUR_USERNAME/SysctlGUI.git + cd SysctlGUI + ``` +3. **Create a New Branch:** Create a new branch for your translation. + ```bash + git checkout -b translate-yourlanguage + ``` +4. **Translate the Files:** + * **String Resources:** These are the primary files for translation. + * `app/src/main/res/values/strings.xml` + * `app/src/main/res/values/params_info.xml` + * `data/src/main/res/values/strings.xml` + + To translate these files, create a new `values-xx` directory in the same `res` folder, where `xx` is the ISO 639-1 code for the language you are translating to (e.g., `values-es` for Spanish, `values-de` for German). Then, copy the original `strings.xml` or `params_info.xml` into this new directory and translate the string values within the XML tags. + + **Example (strings.xml for Spanish):** + Create `app/src/main/res/values-es/strings.xml` and translate the content, preserving special format tags (%s, %1$s, etc) + ```xml + + SysctlGUI + + Undo + Selected file: %s + + Deshacer + Archivo seleccionado: %s + + ``` + + * **Raw Text Files (Optional):** If you feel brave, you can also translate the `.txt` files located in `data/src/main/res/raw/`. + * When translating these files, it is **crucial** to respect their original format. These files often have a specific structure that the app relies on. + * Translate the text content, but leave any special characters, newlines, or formatting intact. + * Place the translated `.txt` files in a new `raw-xx` directory within `data/src/main/res/` (e.g., `data/src/main/res/raw-es/` for Spanish). + +5. **Commit Your Changes:** Commit your translated files with a clear commit message. + ```bash + git add . + git commit -m "Add translation to [Your Language]" + ``` +6. **Push to Your Fork:** Push your changes to your forked repository. + ```bash + git push origin translate-yourlanguage + ``` +7. **Create a Pull Request:** Go to the original SysctlGUI repository on GitHub and create a new Pull Request from your forked branch. Provide a clear description of your changes. + +## Important Notes + +* Ensure your translations are accurate and natural-sounding in the target language. +* Do not translate resource names (e.g., `app_name` in ``). Only translate the text content between the XML tags. +* For `.txt` files, preserving the exact original formatting is critical for the app to function correctly with the translated content. +* If you are unsure about any part of the translation process, feel free to open an issue on the main repository to ask for clarification. + +Thank you for your contribution! diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2e4c304..f94244f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,8 +17,8 @@ android { applicationId = AppConfig.appId minSdk = AppConfig.minSdkVersion targetSdk = AppConfig.targetSdkVersion - versionCode = 16 - versionName = "2.2.2" + versionCode = 17 + versionName = "3.0.0" vectorDrawables.useSupportLibrary = true androidResources { localeFilters += listOf("en", "de", "pt-rBR", "tr") @@ -31,6 +31,7 @@ android { ) } } + multiDexEnabled = true } signingConfigs { @@ -58,8 +59,7 @@ android { signingConfig = signingConfigs.getByName("release") proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro", - "proguard-kt.pro" + "proguard-rules.pro" ) } } @@ -117,6 +117,7 @@ dependencies { implementation(libs.androidx.navigation.compose) implementation(libs.androidx.window) implementation(libs.androidx.work.runtime.ktx) + implementation(libs.androidx.multidex) // Lifecycle implementation(libs.androidx.lifecycle.runtime.ktx) @@ -130,4 +131,4 @@ dependencies { implementation(libs.koin) implementation(libs.koin.compose) implementation(libs.bundles.libsu) -} +} \ No newline at end of file diff --git a/app/proguard-kt.pro b/app/proguard-kt.pro deleted file mode 100644 index b7f8b24..0000000 --- a/app/proguard-kt.pro +++ /dev/null @@ -1,13 +0,0 @@ --dontusemixedcaseclassnames - --dontwarn kotlin.** --keepclassmembers class **$WhenMappings { - ; -} - --assumenosideeffects class kotlin.jvm.internal.Intrinsics { - static void checkParameterIsNotNull(java.lang.Object, java.lang.String); -} - -## Useless option for dex --dontpreverify \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 8393bb1..28e9833 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -20,21 +20,20 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile --keep class android.support.v7.widget.SearchView { *; } --keep class androidx.appcompat.widget.SearchView { *; } --keep class android.widget.SearchView { *; } - --keepclassmembers enum * { *; } - -# GSON config --keep public class com.google.gson.** --keep public class com.google.gson.** {public private protected *;} --keepattributes *Annotation*,Signature - -# Gson specific classes --keep class sun.misc.Unsafe { *; } - -# Application classes that will be serialized/deserialized over Gson --keep class com.androidvip.sysctlgui.data.models.KernelParam { *; } --keep class com.androidvip.sysctlgui.data.models.RoomKernelParam { *; } --keep class com.androidvip.sysctlgui.domain.models.DomainKernelParam { *; } \ No newline at end of file +-keepnames class androidx.lifecycle.ViewModel +-keepclassmembers class * extends androidx.lifecycle.ViewModel { (...); } +-keepclassmembers class * implements androidx.lifecycle.LifecycleObserver { (...); } +-keepclassmembers class * implements androidx.lifecycle.LifecycleOwner { (...); } +-keepclassmembers class androidx.lifecycle.Lifecycle$State { *; } +-keepclassmembers class androidx.lifecycle.Lifecycle$Event { *; } +-keep class * implements androidx.lifecycle.LifecycleOwner { public (...); } +-keep class * implements androidx.lifecycle.LifecycleObserver { public (...); } + +-keepclassmembers class com.androidvip.sysctlgui.ui.main.MainViewModel { + static void (); +} + +-keep class org.koin.core.instance.InstanceFactory { *; } +-keep class * extends org.koin.core.module.Module +-keep class org.koin.core.registry.ScopeRegistry { *; } +-keep class org.koin.android.scope.AndroidScopeComponent diff --git a/app/src/main/kotlin/com/androidvip/sysctlgui/SysctlGuiApp.kt b/app/src/main/kotlin/com/androidvip/sysctlgui/SysctlGuiApp.kt index 1b909b8..0fb9ede 100644 --- a/app/src/main/kotlin/com/androidvip/sysctlgui/SysctlGuiApp.kt +++ b/app/src/main/kotlin/com/androidvip/sysctlgui/SysctlGuiApp.kt @@ -1,6 +1,6 @@ package com.androidvip.sysctlgui -import android.app.Application +import androidx.multidex.MultiDexApplication import com.androidvip.sysctlgui.data.di.dataModules import com.androidvip.sysctlgui.di.presentationModule import com.androidvip.sysctlgui.domain.di.domainModule @@ -10,7 +10,7 @@ import org.koin.android.ext.android.get import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin -class SysctlGuiApp : Application() { +class SysctlGuiApp : MultiDexApplication() { override fun onCreate() { super.onCreate() diff --git a/common/design/proguard-rules.pro b/common/design/proguard-rules.pro index 481bb43..8396c29 100644 --- a/common/design/proguard-rules.pro +++ b/common/design/proguard-rules.pro @@ -18,4 +18,9 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +-keep class com.androidvip.sysctlgui.design.theme.ColorKt { *; } +-keep class com.androidvip.sysctlgui.design.theme.ThemeKt { *; } +-keep class com.androidvip.sysctlgui.design.theme.TypeKt { *; } +-keep class com.androidvip.sysctlgui.design.utils.UiUtilsKt { *; } diff --git a/common/utils/build.gradle.kts b/common/utils/build.gradle.kts index 532ccf1..68576a4 100644 --- a/common/utils/build.gradle.kts +++ b/common/utils/build.gradle.kts @@ -15,7 +15,7 @@ android { } buildTypes { - release { + getByName("release") { isMinifyEnabled = !AppConfig.devCycle proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), diff --git a/common/utils/proguard-rules.pro b/common/utils/proguard-rules.pro index 481bb43..fa38585 100644 --- a/common/utils/proguard-rules.pro +++ b/common/utils/proguard-rules.pro @@ -18,4 +18,8 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + +-keep class com.androidvip.sysctlgui.utils.BaseViewModel { *; } +-keep class com.androidvip.sysctlgui.utils.ContextUtilsKt { *; } +-keep class com.androidvip.sysctlgui.utils.MiscKt { *; } diff --git a/data/consumer-rules.pro b/data/consumer-rules.pro index e69de29..2e8e82c 100644 --- a/data/consumer-rules.pro +++ b/data/consumer-rules.pro @@ -0,0 +1 @@ +-keep class com.androidvip.sysctlgui.data.models.KernelParamDTO { *; } diff --git a/data/proguard-rules.pro b/data/proguard-rules.pro index fbcf9ce..ff875af 100644 --- a/data/proguard-rules.pro +++ b/data/proguard-rules.pro @@ -20,5 +20,4 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile -# Application classes that will be serialized/deserialized over Gson --keep class com.androidvip.sysctlgui.data.models.RoomKernelParam { *; } \ No newline at end of file +-keep class com.androidvip.sysctlgui.data.models.KernelParamDTO { *; } \ No newline at end of file diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt b/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt index 268600b..7f2c886 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/di/DataModule.kt @@ -1,6 +1,5 @@ package com.androidvip.sysctlgui.data.di -import android.util.Log import androidx.preference.PreferenceManager import com.androidvip.sysctlgui.data.db.ParamDatabase import com.androidvip.sysctlgui.data.db.ParamDatabaseManager @@ -25,9 +24,6 @@ import com.androidvip.sysctlgui.domain.repository.PresetRepository import com.androidvip.sysctlgui.domain.repository.UserRepository import io.ktor.client.HttpClient import io.ktor.client.engine.android.Android -import io.ktor.client.plugins.logging.LogLevel -import io.ktor.client.plugins.logging.Logger -import io.ktor.client.plugins.logging.Logging import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import org.koin.android.ext.koin.androidApplication @@ -91,14 +87,14 @@ val networkModule = module { socketTimeout = 5000 } - install(Logging) { + /*install(Logging) { logger = object : Logger { override fun log(message: String) { Log.v("KtorHttpClient", message) } } level = LogLevel.BODY - } + }*/ } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b362bae..b18e73f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,7 @@ composeBom = "2025.07.00" appcompat = "1.7.1" materialIconsCoreCompose = "1.7.8" material = "1.12.0" +multidex = "2.0.1" navigationCompose = "2.9.3" preference = "1.2.1" room = "2.7.2" @@ -33,6 +34,7 @@ androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" } androidx-material = { module = "androidx.compose.material:material", version.ref = "composeMaterial" } androidx-material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "materialIconsCoreCompose" } +androidx-multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } androidx-preference = { module = "androidx.preference:preference", version.ref = "preference" } androidx-window = { module = "androidx.window:window", version.ref = "window" } From 70d704f43e152f519de31957eb18e1a6f04e33b1 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Tue, 19 Aug 2025 14:33:07 -0300 Subject: [PATCH 20/23] chore: created community standard files Signed-off-by: Lennoard --- CONTRIBUTING.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 49 ++++++++++++++++++++++++++++--------------------- 2 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..25628e3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing to SysctlGUI + +First off, thank you for considering contributing to SysctlGUI! Your help is greatly appreciated. Whether it's reporting a bug, discussing improvements, or contributing code, every bit of effort makes this project better. + +This document provides a set of guidelines for contributing to SysctlGUI. These are mostly guidelines, not strict rules. Use your best judgment, and feel free to propose changes to this document in a pull request. + +## How Can I Contribute? + +There are many ways to contribute, and we welcome them all! + +### Reporting Bugs + +If you find a bug, please make sure it hasn't already been reported by searching the [GitHub Issues](https://github.com/Lennoard/SysctlGUI/issues). + +When you are creating a bug report, please include as many details as possible: + +* **A clear and descriptive title** for the issue +* **Steps to reproduce the bug** in a clear, step-by-step list +* **Describe the expected behavior** and what you observed instead +* **Provide details about your environment:** + * App version + * Android version and ROM (e.g., Android 13, LineageOS 20) + * Device model + * Root solution (e.g., Magisk, KernelSU) +* **Include logs** (like a Logcat) or screenshots if they are relevant + +### Suggesting Enhancements + +If you have an idea for a new feature or an improvement to an existing one, please open an issue to start a discussion. + +* **Use a clear and descriptive title** for the issue +* **Provide a detailed description** of the enhancement and why it would be useful +* **Explain the problem it solves** or the workflow it improves +* **Include mockups or screenshots** if they help illustrate your idea + + +### Pull Requests + +Code contributions are always welcome. If you can contribute with code, please do so. + + +### Translations +If you are fluent in another language, your help with translations would be invaluable. +Please see the [translation guide](https://github.com/Lennoard/SysctlGUI/blob/develop/TRANSLATING.md) for detailed instructions on how to contribute translations. + + + +# License +By contributing to SysctlGUI, you agree that your contributions will be licensed under the [License](https://github.com/Lennoard/SysctlGUI/blob/develop/LICENSE) that covers the project. diff --git a/README.md b/README.md index e2a4226..8f27172 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,30 @@

    - -

    - -

    - - - - +

    # SysctlGUI +SysctlGUI is a Android application designed for power users, developers, and enthusiasts who want to +fine-tune their device's performance by directly editing kernel parameters. +It provides a user interface for the sysctl command-line utility, +making advanced kernel tweaking accessible and manageable. + +***Important: This app requires root access to work.*** + [![Codacy Badge](https://app.codacy.com/project/badge/Grade/d91bf38325aa4bb6b6cb67136f72f1f1)](https://www.codacy.com/gh/Lennoard/SysctlGUI/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Lennoard/SysctlGUI&utm_campaign=Badge_Grade) -![](https://img.shields.io/github/languages/top/Lennoard/SysctlGUI) -![](https://img.shields.io/github/contributors/Lennoard/SysctlGUI) -![](https://img.shields.io/github/downloads/Lennoard/SysctlGUI/total) -![](https://img.shields.io/github/v/release/Lennoard/SysctlGUI) -![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/Lennoard/SysctlGUI/latest/master) +[![CodeQL Advanced](https://github.com/Lennoard/SysctlGUI/actions/workflows/codeql.yml/badge.svg)](https://github.com/Lennoard/SysctlGUI/actions/workflows/codeql.yml) +[![Build Status](https://app.bitrise.io/app/03e8fa82-8168-4a7f-9005-b8e5d056417f/status.svg?token=3qlQfgAkEgxvr0JnvyzT_Q&branch=develop)](https://app.bitrise.io/app/03e8fa82-8168-4a7f-9005-b8e5d056417f) -A GUI application for Android sysctl to edit kernel variables +![](https://img.shields.io/github/contributors/Lennoard/SysctlGUI) +![](https://img.shields.io/github/downloads/Lennoard/SysctlGUI/total) +![](https://img.shields.io/github/v/release/Lennoard/SysctlGUI) +![GitHub commits since latest release (by date)](https://img.shields.io/github/commits-since/Lennoard/SysctlGUI/latest/develop) ## Features -- Browse filesystem for specific kernel parameters -- Select parameters from a searchable list -- Show documentation for known parameters -- Load parameters from a configuration file -- Reapply parameters at startup -- Mark parameters as favorite for easy access +- **Parameter Management:** Easily browse the filesystem or search a comprehensive list to find kernel parameters, with in-app documentation to help you understand their impact. +- **Persistent Tweaks:** Automatically reapply your chosen settings on every boot. +- **Configuration Profiles:** Save and load sets of parameters from configuration files, making it simple to switch between different performance profiles or share your setup. +- **Favorites System:** Mark frequently used parameters for quick and easy access. ## Technologies @@ -56,11 +54,20 @@ This project utilizes a modern Android development stack, leveraging a comprehen ## Contributing -We welcome contributions to SysctlGUI! +Contributions are always welcomed. Please see the [contributing guide](CONTRIBUTING.md) for more details on how to contribute with this project. ### Translations If you'd like to help translate the app into other languages, please see the [translation guide](TRANSLATING.md) for instructions on how to get started. Your contributions will help make SysctlGUI accessible to a wider audience. +## Screenshots + +

    + + + + +

    + ## Download Get it on IzzyOnDroid From ae4abf565c813fb77541d39a8f75532ab4314a75 Mon Sep 17 00:00:00 2001 From: Lennoard Date: Tue, 19 Aug 2025 14:34:40 -0300 Subject: [PATCH 21/23] bugfix: database name Signed-off-by: Lennoard --- .../java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt b/data/src/main/java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt index 5188801..3909de2 100644 --- a/data/src/main/java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt +++ b/data/src/main/java/com/androidvip/sysctlgui/data/models/KernelParamDTO.kt @@ -40,4 +40,4 @@ data class KernelParamDTO( } } -internal const val PARAMS_TABLE_NAME = "roomKernelParam" +internal const val PARAMS_TABLE_NAME = "RoomKernelParam" // For compatibility with older versions From 40eb9415e54c0fe7a0444675c71d78e9feaaf79f Mon Sep 17 00:00:00 2001 From: Lennoard Date: Tue, 19 Aug 2025 16:13:50 -0300 Subject: [PATCH 22/23] docs: update README with Tasker integration and new screenshots Signed-off-by: Lennoard --- README.md | 15 +++++++++------ img/labs_badge.png | Bin 16523 -> 0 bytes img/ss01.png | Bin 0 -> 109987 bytes img/ss02.png | Bin 0 -> 132534 bytes img/ss03.png | Bin 0 -> 231458 bytes img/ss04.png | Bin 0 -> 155780 bytes 6 files changed, 9 insertions(+), 6 deletions(-) delete mode 100644 img/labs_badge.png create mode 100644 img/ss01.png create mode 100644 img/ss02.png create mode 100644 img/ss03.png create mode 100644 img/ss04.png diff --git a/README.md b/README.md index 8f27172..0782b08 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ making advanced kernel tweaking accessible and manageable. - **Persistent Tweaks:** Automatically reapply your chosen settings on every boot. - **Configuration Profiles:** Save and load sets of parameters from configuration files, making it simple to switch between different performance profiles or share your setup. - **Favorites System:** Mark frequently used parameters for quick and easy access. +- **Tasker Integration:** Automate the application of kernel parameters in response to specific events using [Tasker](https://tasker.joaoapps.com/). SysctlGUI provides a Tasker plugin, allowing you to trigger parameter application based on a wide range of conditions/states. ## Technologies @@ -59,14 +60,16 @@ Contributions are always welcomed. Please see the [contributing guide](CONTRIBUT ### Translations If you'd like to help translate the app into other languages, please see the [translation guide](TRANSLATING.md) for instructions on how to get started. Your contributions will help make SysctlGUI accessible to a wider audience. + ## Screenshots +
    + +| | | | | +|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:-------------------------------------------------------:| +|Screenshot 1 |Screenshot 2 |Screenshot 3 | Screenshot 4 | + +
    -

    - - - - -

    ## Download diff --git a/img/labs_badge.png b/img/labs_badge.png deleted file mode 100644 index 3a068bc54542a945f2fdcc836ce636023f06df6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16523 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

    Ba9+!9K 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ⅅ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⪼2_ebje(O;6B#+GWYGqJ|%c#f4nE?!UOPJ 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%0A^xo+1zk$F*EN{=Lpv_ZI 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

    qNi2*2 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 literal 0 HcmV?d00001 From 9fe927d96be938df3f0c30928a31b4e1d37b0f8a Mon Sep 17 00:00:00 2001 From: Lennoard Date: Tue, 19 Aug 2025 16:13:50 -0300 Subject: [PATCH 23/23] docs: update README with Tasker integration and new screenshots Signed-off-by: Lennoard --- README.md | 2 +- img/icon.png | Bin 0 -> 27467 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 img/icon.png diff --git a/README.md b/README.md index 0782b08..665bf89 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

    - +

    # SysctlGUI diff --git a/img/icon.png b/img/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4b35a944b527890b5fe29831744650bfe92b416d GIT binary patch literal 27467 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