diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 4236b18..d21d8e3 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -35,7 +35,7 @@ jobs: java-version: 17 - uses: gradle/actions/setup-gradle@v6 - name: API check - run: ./gradlew checkKotlinAbi + run: ./gradlew apiCheck build: runs-on: ubuntu-latest diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts index 15b6c9e..ee4fb0f 100644 --- a/benchmark/build.gradle.kts +++ b/benchmark/build.gradle.kts @@ -4,6 +4,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed plugins { id(libs.plugins.android.test.get().pluginId) + id(libs.plugins.kotlin.android.get().pluginId) id(libs.plugins.baseline.profile.get().pluginId) } diff --git a/build.gradle.kts b/build.gradle.kts index b181a0f..84167e5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,12 +16,19 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false + alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.baseline.profile) apply false alias(libs.plugins.nexus.plugin) alias(libs.plugins.spotless) alias(libs.plugins.dokka) + alias(libs.plugins.kotlin.binary.compatibility) +} + +apiValidation { + // only the published library takes part in the binary compatibility validation. + ignoredProjects += listOf("demo", "benchmark") } subprojects { diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index 1fa6c7d..c0bec22 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -18,6 +18,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget @Suppress("DSL_SCOPE_VIOLATION") plugins { id(libs.plugins.android.application.get().pluginId) + id(libs.plugins.kotlin.android.get().pluginId) id(libs.plugins.compose.compiler.get().pluginId) id(libs.plugins.baseline.profile.get().pluginId) } diff --git a/gradle.properties b/gradle.properties index ec8cfd9..909adc4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -31,6 +31,13 @@ org.gradle.configureondemand=true # AndroidX Migration https://developer.android.com/jetpack/androidx/migrate android.useAndroidX=true +# Built-in Kotlin of AGP 9 does not expose the Android compilations to the binary compatibility +# validator nor to the ABI validation of the Kotlin Gradle plugin, which silently turns apiDump and +# apiCheck into no-ops. Keep the Kotlin Android plugin, which also requires the legacy DSL, until +# https://youtrack.jetbrains.com/issue/KT-83410 ships. +android.builtInKotlin=false +android.newDsl=false + # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f4077e2..d5f6093 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,15 +3,16 @@ agp = "9.4.0" dokka = "2.2.0" nexusPlugin = "0.37.0" kotlin = "2.4.20" +kotlinBinaryCompatibility = "0.18.2" kotlinSerialization = "1.11.0" jvmTarget = "11" androidxComposeBom = "2026.09.00" androidxComposeConstraintLayout = "1.1.2" androidxActivity = "1.13.0" +androidxCore = "1.19.0" androidxTest = "1.7.0" androidxDataStore = "1.2.1" coroutines = "1.11.0" -accompanist = "0.36.0" baselineProfiles = "1.4.1" uiAutomator = "2.4.0" spotless = "8.10.2" @@ -23,10 +24,12 @@ androidxMacroBenchmark = "1.5.0" android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } android-test = { id = "com.android.test", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } nexus-plugin = { id = "com.vanniktech.maven.publish", version.ref = "nexusPlugin" } +kotlin-binary-compatibility = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "kotlinBinaryCompatibility" } spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } baseline-profile = { id = "androidx.baselineprofile", version.ref = "androidxMacroBenchmark" } @@ -37,6 +40,7 @@ androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-toolin androidx-compose-material = { group = "androidx.compose.material", name = "material" } androidx-compose-material-iconsExtended = { group = "androidx.compose.material", name = "material-icons-extended" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" } androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" } androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } androidx-compose-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout-compose", version.ref = "androidxComposeConstraintLayout" } @@ -45,7 +49,6 @@ androidx-datastore-core = { group = "androidx.datastore", name = "datastore-pref kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinSerialization" } kotlinx-serialization-protobuf = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-protobuf", version.ref = "kotlinSerialization" } coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" } -accompanist-system-ui = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist" } # unit test androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTest" } diff --git a/snitcher/api/snitcher.api b/snitcher/api/snitcher.api index 376671a..136a823 100644 --- a/snitcher/api/snitcher.api +++ b/snitcher/api/snitcher.api @@ -25,6 +25,7 @@ public final class com/skydoves/snitcher/TraceStrategy : java/lang/Enum { } public final class com/skydoves/snitcher/installer/SnitcherInstaller { + public static final field $stable I public static final field INSTANCE Lcom/skydoves/snitcher/installer/SnitcherInstaller; public final fun getInternalSnitcher ()Lkotlinx/coroutines/flow/MutableStateFlow; public static final fun install (Lcom/skydoves/snitcher/Snitcher;)V @@ -35,7 +36,6 @@ public final class com/skydoves/snitcher/installer/SnitcherInstaller { public final class com/skydoves/snitcher/model/SnitcherException : java/io/Serializable { public static final field $stable I public static final field Companion Lcom/skydoves/snitcher/model/SnitcherException$Companion; - public synthetic fun (IJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V public final fun component1 ()J public final fun component2 ()Ljava/lang/String; @@ -55,17 +55,16 @@ public final class com/skydoves/snitcher/model/SnitcherException : java/io/Seria public final fun getThrowable ()Ljava/lang/Throwable; public fun hashCode ()I public fun toString ()Ljava/lang/String; - public static final synthetic fun write$Self (Lcom/skydoves/snitcher/model/SnitcherException;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } -public final class com/skydoves/snitcher/model/SnitcherException$$serializer : kotlinx/serialization/internal/GeneratedSerializer { +public final synthetic class com/skydoves/snitcher/model/SnitcherException$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field $stable I public static final field INSTANCE Lcom/skydoves/snitcher/model/SnitcherException$$serializer; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/skydoves/snitcher/model/SnitcherException; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/skydoves/snitcher/model/SnitcherException; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/skydoves/snitcher/model/SnitcherException;)V + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/skydoves/snitcher/model/SnitcherException;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } @@ -77,7 +76,6 @@ public final class com/skydoves/snitcher/model/SnitcherException$Companion { public final class com/skydoves/snitcher/model/SnitcherStackTraceElement { public static final field $stable I public static final field Companion Lcom/skydoves/snitcher/model/SnitcherStackTraceElement$Companion; - public synthetic fun (ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()I @@ -92,17 +90,16 @@ public final class com/skydoves/snitcher/model/SnitcherStackTraceElement { public final fun getMethodName ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; - public static final synthetic fun write$Self (Lcom/skydoves/snitcher/model/SnitcherStackTraceElement;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } -public final class com/skydoves/snitcher/model/SnitcherStackTraceElement$$serializer : kotlinx/serialization/internal/GeneratedSerializer { +public final synthetic class com/skydoves/snitcher/model/SnitcherStackTraceElement$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field $stable I public static final field INSTANCE Lcom/skydoves/snitcher/model/SnitcherStackTraceElement$$serializer; - public fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/skydoves/snitcher/model/SnitcherStackTraceElement; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/skydoves/snitcher/model/SnitcherStackTraceElement; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/skydoves/snitcher/model/SnitcherStackTraceElement;)V + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/skydoves/snitcher/model/SnitcherStackTraceElement;)V public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } @@ -117,9 +114,8 @@ public final class com/skydoves/snitcher/ui/AppRestoreScreenKt { public final class com/skydoves/snitcher/ui/ComposableSingletons$ExceptionTraceActivityKt { public static final field INSTANCE Lcom/skydoves/snitcher/ui/ComposableSingletons$ExceptionTraceActivityKt; - public static field lambda-1 Lkotlin/jvm/functions/Function2; public fun ()V - public final fun getLambda-1$snitcher_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda$-98219935$SnitcherDemo_snitcher_release ()Lkotlin/jvm/functions/Function2; } public class com/skydoves/snitcher/ui/ExceptionTraceActivity : androidx/activity/ComponentActivity { diff --git a/snitcher/build.gradle.kts b/snitcher/build.gradle.kts index 4682872..7c5d4ad 100644 --- a/snitcher/build.gradle.kts +++ b/snitcher/build.gradle.kts @@ -18,6 +18,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget @Suppress("DSL_SCOPE_VIOLATION") plugins { id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.kotlin.android.get().pluginId) id(libs.plugins.kotlin.serialization.get().pluginId) id(libs.plugins.compose.compiler.get().pluginId) id(libs.plugins.nexus.plugin.get().pluginId) @@ -64,11 +65,6 @@ android { } kotlin { - @OptIn(org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation::class) - abiValidation { - referenceDumpDir.set(layout.projectDirectory.dir("api")) - } - compilerOptions { jvmTarget.set(JvmTarget.fromTarget(libs.versions.jvmTarget.get())) freeCompilerArgs.add("-Xexplicit-api=strict") @@ -95,7 +91,7 @@ dependencies { implementation(libs.androidx.compose.runtime) implementation(libs.androidx.compose.material) implementation(libs.androidx.compose.material.iconsExtended) - implementation(libs.accompanist.system.ui) + implementation(libs.androidx.core.ktx) implementation(libs.androidx.datastore) implementation(libs.androidx.datastore.core) diff --git a/snitcher/src/main/kotlin/com/skydoves/snitcher/Snitcher.kt b/snitcher/src/main/kotlin/com/skydoves/snitcher/Snitcher.kt index 661f338..d4e1e12 100644 --- a/snitcher/src/main/kotlin/com/skydoves/snitcher/Snitcher.kt +++ b/snitcher/src/main/kotlin/com/skydoves/snitcher/Snitcher.kt @@ -207,8 +207,7 @@ public class Snitcher( SnitcherInstaller.internalSnitcher.collectLatest { snitcher -> snitcher?.dataStore?.data?.filterNotNull()?.collectLatest { _exception.value = it.snitcherException - _launcher.value = it.launcher - ?: throw IllegalArgumentException("launcher package name must not be null") + _launcher.value = it.launcher.orEmpty() } } } diff --git a/snitcher/src/main/kotlin/com/skydoves/snitcher/installer/SnitcherInstaller.kt b/snitcher/src/main/kotlin/com/skydoves/snitcher/installer/SnitcherInstaller.kt index 3014f51..4658452 100644 --- a/snitcher/src/main/kotlin/com/skydoves/snitcher/installer/SnitcherInstaller.kt +++ b/snitcher/src/main/kotlin/com/skydoves/snitcher/installer/SnitcherInstaller.kt @@ -54,6 +54,7 @@ internal object SnitcherInstaller { } Thread.setDefaultUncaughtExceptionHandler(snitcher) internalSnitcher.value = snitcher + isInstalled = true } } } diff --git a/snitcher/src/main/kotlin/com/skydoves/snitcher/model/SnitcherStackTraceElement.kt b/snitcher/src/main/kotlin/com/skydoves/snitcher/model/SnitcherStackTraceElement.kt index f165ea2..6d7a426 100644 --- a/snitcher/src/main/kotlin/com/skydoves/snitcher/model/SnitcherStackTraceElement.kt +++ b/snitcher/src/main/kotlin/com/skydoves/snitcher/model/SnitcherStackTraceElement.kt @@ -28,7 +28,7 @@ public data class SnitcherStackTraceElement( @JvmSynthetic internal fun StackTraceElement.toSnitcherElement(): SnitcherStackTraceElement = SnitcherStackTraceElement( - fileName = "", + fileName = fileName.orEmpty(), lineNumber = lineNumber, className = className, methodName = methodName, diff --git a/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/ExceptionTraceActivity.kt b/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/ExceptionTraceActivity.kt index cd62884..0a76137 100644 --- a/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/ExceptionTraceActivity.kt +++ b/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/ExceptionTraceActivity.kt @@ -18,6 +18,7 @@ package com.skydoves.snitcher.ui import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import com.skydoves.snitcher.Snitcher @@ -35,6 +36,7 @@ public open class ExceptionTraceActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() setContent { val exception: SnitcherException? by Snitcher.exception.collectAsState() diff --git a/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/ExceptionTraceScreen.kt b/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/ExceptionTraceScreen.kt index 1de69d5..5eb0c9d 100644 --- a/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/ExceptionTraceScreen.kt +++ b/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/ExceptionTraceScreen.kt @@ -15,6 +15,7 @@ */ package com.skydoves.snitcher.ui +import android.content.ClipData import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.os.Build @@ -25,6 +26,7 @@ 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.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -40,14 +42,14 @@ import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.RestartAlt import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ClipboardManager -import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.ClipEntry +import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -59,6 +61,7 @@ import com.skydoves.snitcher.extensions.versionCode import com.skydoves.snitcher.model.SnitcherException import com.skydoves.snitcher.ui.theme.SnitcherStatusBarColor import com.skydoves.snitcher.ui.theme.SnitcherTheme +import kotlinx.coroutines.launch @Composable public fun ExceptionTraceScreen(launcher: String, snitcherException: SnitcherException) { @@ -76,11 +79,16 @@ private fun ExceptionTraceScreenContent( snitcherException: SnitcherException, ) { val context = LocalContext.current + val scope = rememberCoroutineScope() + val clipboard = LocalClipboard.current val scrollState = rememberScrollState() val packageInfo = remember { context.packageInfo() } + val stacktraceLabel = stringResource(id = R.string.snitcher_debug_crash_screen_stacktrace) + val copiedMessage = stringResource(id = R.string.snitcher_debug_crash_screen_copied) Column( modifier = Modifier + .fillMaxSize() .background(SnitcherTheme.colors.background) .systemBarsPadding() .verticalScroll(scrollState) @@ -139,20 +147,23 @@ private fun ExceptionTraceScreenContent( Box(modifier = Modifier.fillMaxWidth()) { Text( modifier = Modifier.align(Alignment.CenterStart), - text = stringResource(id = R.string.snitcher_debug_crash_screen_stacktrace), + text = stacktraceLabel, color = SnitcherTheme.colors.primary, fontWeight = FontWeight.Bold, fontSize = 16.sp, ) - val clipboardManager: ClipboardManager = LocalClipboardManager.current Icon( modifier = Modifier .align(Alignment.CenterEnd) .clickable { - clipboardManager.setText(AnnotatedString(snitcherException.message)) - context.toast("copied!") + scope.launch { + clipboard.setClipEntry( + ClipEntry(ClipData.newPlainText(stacktraceLabel, snitcherException.stackTrace)), + ) + context.toast(copiedMessage) + } }, imageVector = Icons.Filled.ContentCopy, tint = SnitcherTheme.colors.textHighEmphasis, diff --git a/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/SnitcherPrimaryButton.kt b/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/SnitcherPrimaryButton.kt index da1294b..daec7d8 100644 --- a/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/SnitcherPrimaryButton.kt +++ b/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/SnitcherPrimaryButton.kt @@ -73,7 +73,7 @@ internal fun SnitcherPrimaryButton( Text( text = text, - color = SnitcherTheme.colors.textHighEmphasis, + color = Color.White, fontSize = 16.sp, ) } diff --git a/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/theme/SnitcherColor.kt b/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/theme/SnitcherColor.kt index 7d03899..6b5faed 100644 --- a/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/theme/SnitcherColor.kt +++ b/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/theme/SnitcherColor.kt @@ -39,7 +39,7 @@ public data class SnitcherColor( public fun defaultColors(): SnitcherColor = SnitcherColor( primary = Color(0XFF28a9f1), background = Color.White, - textHighEmphasis = Color.White, + textHighEmphasis = Color(0XFF121212), ) /** diff --git a/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/theme/Statusbar.kt b/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/theme/Statusbar.kt index 92ec152..41ea395 100644 --- a/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/theme/Statusbar.kt +++ b/snitcher/src/main/kotlin/com/skydoves/snitcher/ui/theme/Statusbar.kt @@ -15,23 +15,39 @@ */ package com.skydoves.snitcher.ui.theme -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.graphics.Color -import com.google.accompanist.systemuicontroller.rememberSystemUiController +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat +import com.skydoves.snitcher.extensions.findActivity +/** + * Adjusts the system bar icons to the given [color], which is the color drawn behind the system + * bars by the Snitcher screens. The previous appearance is restored when this leaves composition. + */ @Composable internal fun SnitcherStatusBarColor(color: Color = SnitcherTheme.colors.background) { - val systemUiController = rememberSystemUiController() - val useDarkIcons = !isSystemInDarkTheme() + val view = LocalView.current + if (view.isInEditMode) { + return + } + + val darkIcons = color.luminance() > 0.5f + DisposableEffect(view, darkIcons) { + val window = view.context.findActivity()?.window + ?: return@DisposableEffect onDispose { } + val controller = WindowCompat.getInsetsController(window, view) + val statusBars = controller.isAppearanceLightStatusBars + val navigationBars = controller.isAppearanceLightNavigationBars - DisposableEffect(systemUiController, useDarkIcons) { - systemUiController.setStatusBarColor( - color = color, - darkIcons = useDarkIcons, - ) + controller.isAppearanceLightStatusBars = darkIcons + controller.isAppearanceLightNavigationBars = darkIcons - onDispose {} + onDispose { + controller.isAppearanceLightStatusBars = statusBars + controller.isAppearanceLightNavigationBars = navigationBars + } } } diff --git a/snitcher/src/main/res/values/strings.xml b/snitcher/src/main/res/values/strings.xml index e2572d1..e0736ae 100644 --- a/snitcher/src/main/res/values/strings.xml +++ b/snitcher/src/main/res/values/strings.xml @@ -21,4 +21,5 @@ Restore App Debug on IDE Stacktrace + Copied!