Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions benchmark/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
7 changes: 7 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
7 changes: 7 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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" }

Expand All @@ -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" }
Expand All @@ -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" }
Expand Down
28 changes: 12 additions & 16 deletions snitcher/api/snitcher.api
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <init> (IJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (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;
Expand All @@ -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;
}
Expand All @@ -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 <init> (ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()I
Expand All @@ -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;
}
Expand All @@ -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 <init> ()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 {
Expand Down
8 changes: 2 additions & 6 deletions snitcher/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ internal object SnitcherInstaller {
}
Thread.setDefaultUncaughtExceptionHandler(snitcher)
internalSnitcher.value = snitcher
isInstalled = true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ internal fun SnitcherPrimaryButton(

Text(
text = text,
color = SnitcherTheme.colors.textHighEmphasis,
color = Color.White,
fontSize = 16.sp,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
)

/**
Expand Down
Loading