diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 900278f..715bf65 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -17,7 +17,7 @@ $ ./gradlew spotlessApply
Then dump binary API of this library that is public in sense of Kotlin visibilities and ensures that the public binary API wasn't changed in a way that make this change binary incompatible.
```bash
-./gradlew apiDump
+./gradlew updateKotlinAbi
```
Please correct any failures before requesting a review.
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
deleted file mode 100644
index d21d8e3..0000000
--- a/.github/workflows/android.yml
+++ /dev/null
@@ -1,57 +0,0 @@
-name: Android CI
-
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
-
-jobs:
- lint:
- name: Spotless check
- runs-on: ubuntu-latest
- steps:
- - name: Check out code
- uses: actions/checkout@v7
- - name: Set up JDK
- uses: actions/setup-java@v6
- with:
- distribution: zulu
- java-version: 17
- - uses: gradle/actions/setup-gradle@v6
- - name: spotless
- run: ./gradlew spotlessCheck
-
- api_check:
- name: API check
- runs-on: ubuntu-latest
- steps:
- - name: Check out code
- uses: actions/checkout@v7
- - name: Set up JDK
- uses: actions/setup-java@v6
- with:
- distribution: zulu
- java-version: 17
- - uses: gradle/actions/setup-gradle@v6
- - name: API check
- run: ./gradlew apiCheck
-
- build:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v7
- - name: set up JDK
- uses: actions/setup-java@v6
- with:
- distribution: zulu
- java-version: 17
-
- - uses: gradle/actions/setup-gradle@v6
- - name: Make Gradle executable
- run: chmod +x ./gradlew
-
- - name: Build with Gradle
- run: |
- ./gradlew --scan --stacktrace \
- assemble -x :benchmark:pixel6api31Setup -x :benchmark:pixel6api31NonMinifiedReleaseAndroidTest -x :benchmark:collectNonMinifiedReleaseBaselineProfile
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..7e9fab3
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,104 @@
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ spotless:
+ name: Spotless check
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v7
+ - name: Set up JDK
+ uses: actions/setup-java@v6
+ with:
+ distribution: zulu
+ java-version: 17
+ - uses: gradle/actions/setup-gradle@v6
+ - name: Spotless
+ run: ./gradlew spotlessCheck
+
+ build:
+ name: Build and test
+ # every target is built in one job, on a mac, because the apple targets cannot be built anywhere else.
+ runs-on: macos-latest
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v7
+ - name: Set up JDK
+ uses: actions/setup-java@v6
+ with:
+ distribution: zulu
+ java-version: 17
+ - uses: gradle/actions/setup-gradle@v6
+ - name: Cache the Kotlin/Native toolchain
+ uses: actions/cache@v6
+ with:
+ path: ~/.konan
+ key: konan-${{ runner.os }}-${{ hashFiles('gradle/libs.versions.toml') }}
+ restore-keys: konan-${{ runner.os }}-
+
+ - name: Make Gradle executable
+ run: chmod +x ./gradlew
+
+ - name: Android library, demo and benchmark
+ run: |
+ ./gradlew --stacktrace \
+ :snitcher:assemble :demo:assembleDebug :benchmark:assembleBenchmarkRelease
+
+ - name: Desktop library and demo
+ run: ./gradlew --stacktrace :snitcher:desktopJar :demo-desktop:assemble
+
+ - name: iOS frameworks
+ run: |
+ ./gradlew --stacktrace \
+ :snitcher:linkDebugFrameworkIosSimulatorArm64 \
+ :snitcher:linkReleaseFrameworkIosArm64
+
+ - name: Binary compatibility
+ run: ./gradlew checkKotlinAbi
+
+ - name: Tests
+ run: |
+ ./gradlew --stacktrace \
+ :snitcher:testAndroidHostTest \
+ :snitcher:desktopTest \
+ :snitcher:iosSimulatorArm64Test
+
+ ios_sample:
+ name: iOS sample
+ runs-on: macos-latest
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v7
+ - name: Set up JDK
+ uses: actions/setup-java@v6
+ with:
+ distribution: zulu
+ java-version: 17
+ - uses: gradle/actions/setup-gradle@v6
+ - name: Cache the Kotlin/Native toolchain
+ uses: actions/cache@v6
+ with:
+ path: ~/.konan
+ key: konan-${{ runner.os }}-${{ hashFiles('gradle/libs.versions.toml') }}
+ restore-keys: konan-${{ runner.os }}-
+
+ - name: Install xcodegen
+ run: brew install xcodegen
+
+ - name: Generate the Xcode project
+ working-directory: demo-ios
+ run: xcodegen generate
+
+ - name: Build the sample
+ run: |
+ xcodebuild -project demo-ios/SnitcherDemo.xcodeproj \
+ -scheme SnitcherDemo \
+ -sdk iphonesimulator \
+ -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
+ build
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 9416cda..8400bf9 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -8,7 +8,8 @@ on:
jobs:
publish:
name: Release build and publish
- runs-on: ubuntu-latest
+ # the apple targets can only be built on a mac.
+ runs-on: macos-latest
steps:
- name: Check out code
uses: actions/checkout@v7
diff --git a/.gitignore b/.gitignore
index 885180f..75b6dcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,3 +58,8 @@ google-services.json
freeline.py
freeline/
freeline_project_description.json
+
+# Xcode
+xcuserdata/
+*.xcworkspace/
+DerivedData/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 91a3b23..3e69f3e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -11,7 +11,7 @@ Ensure your change is properly formatted by running:
Then dump binary API of this library that is public in sense of Kotlin visibilities and ensures that the public binary API wasn't changed in a way that make this change binary incompatible.
```gradle
-./gradlew apiDump
+./gradlew updateKotlinAbi
```
Please correct any failures before requesting a review.
diff --git a/README.md b/README.md
index 30f71db..881bc5c 100644
--- a/README.md
+++ b/README.md
@@ -3,24 +3,30 @@
-
+
-π¦ Snitcher captures global crashes, enabling easy redirection to the exception tracing screen for swift recovery.
+π¦ Snitcher captures global crashes on Android, desktop, and iOS, and redirects to the exception tracing screen for swift recovery.
-
-
+
+
+
+
+
+
## What is Snitcher?
Snitcher offers versatile advantages such as aiding in debugging crashes during development, facilitating easy sharing of exceptions by your QA team, enhancing user experiences with recovery screens instead of abrupt closures, and enabling global exception tracing and customized launch behaviors tailored to your specific needs. You have the complete freedom to customize the crash tracing screens according to your build types and preferences, reporting to the Firebase's Crashlytics with displaying the exception screen, including options like launching a designated Activity, sending messages to your BroadcastReceiver, or any other desired actions.
+The screens, the theme, and the captured model are shared across every platform with Compose Multiplatform, and each platform installs the crash capture that its runtime allows.
+
## Documentation
For comprehensive details about Snitcher, please refer to the **[complete documentation available here](https://skydoves.github.io/snitcher/)**.
@@ -31,19 +37,49 @@ For comprehensive details about Snitcher, please refer to the **[complete docume
### Gradle
-Add the dependency below to your **module**'s `build.gradle` file:
+Add the dependency below to your **module**'s `build.gradle.kts` file:
-```gradle
+```kotlin
dependencies {
- implementation "com.github.skydoves:snitcher:1.0.3"
+ implementation("com.github.skydoves:snitcher:2.0.0")
}
```
+In a multiplatform module, add it to the source set that needs it:
+
+```kotlin
+kotlin {
+ sourceSets {
+ commonMain.dependencies {
+ implementation("com.github.skydoves:snitcher:2.0.0")
+ }
+ }
+}
+```
+
+## What each platform can capture
+
+| | Android | Desktop (JVM) | iOS |
+| --- | --- | --- | --- |
+| Uncaught JVM exceptions | yes | yes | not applicable |
+| Unhandled Kotlin exceptions | yes | yes | yes |
+| Uncaught `NSException` | not applicable | not applicable | yes |
+| Unix signals, such as a Swift trap or a memory error | no | no | no |
+| When the screen is shown | right after the crash, in a trace activity | right after the crash, in a window | on the next launch |
+| Can restart the app | yes | no, the process keeps running | no, iOS does not let an app relaunch itself |
+
+On iOS an unhandled Kotlin exception terminates the process, so the crash is recorded and displayed on the next launch. Snitcher records it and leaves the termination to the runtime, exactly as it would happen without Snitcher.
+
## Usage
-Installing Snitcher is a breeze; it hooks into global exceptions, replacing application closure with informative exception tracing screens. You can seamlessly install Snitcher using the following example:
+### Android
+
+Install Snitcher in your `Application` class. Snitcher becomes the default uncaught exception handler, persists the crash, and launches the exception tracing activity.
```kotlin
+import com.skydoves.snitcher.Snitcher
+import com.skydoves.snitcher.install
+
class App : Application() {
override fun onCreate() {
@@ -54,91 +90,94 @@ class App : Application() {
}
```
-It's recommended to install Snitcher on your **Application** class or your on initialization solution, such as [App Startup](https://developer.android.com/topic/libraries/app-startup).
+`install` is an extension of the `Snitcher` object that lives in each platform source set, so it needs that import.
-### Tracing Global Exceptions
+### Desktop
-You can trace the global exceptions by providing `exceptionHandler` lambda parameter. This can be highly beneficial if you intend to gather and report exceptions to other platforms, such as [Firebase Crashlyrics](https://firebase.google.com/docs/crashlytics).
+Install Snitcher before your `application { }` block, and place `SnitcherTraceWindow` next to your own window. The JVM keeps running after an uncaught exception, so the crash window opens right away.
```kotlin
-Snitcher.install(
- application = this,
- exceptionHandler = { exception: SnitcherException ->
- Firebase.crashlytics.log(exception.stackTrace) // or exception.message,
+fun main() {
+ Snitcher.install()
+
+ application {
+ Window(onCloseRequest = ::exitApplication) {
+ App()
+ }
+
+ SnitcherTraceWindow()
}
-)
+}
```
-The `exceptionHandler` gives you `SnitcherException`, encompassing the exception message, stack traces, package name, and thread information. Additionally, it enables you to recover the original `Throwable` instance with the `SnitcherException.throwable` extension.
+### iOS
-```kotlin
-Snitcher.install(
- application = this,
- exceptionHandler = { exception: SnitcherException ->
- val message: String = exception.message
- val stackTrace: String = exception.stackTrace
- val throwable: Throwable = exception.throwable
- val threadName: String = exception.threadName
+Install Snitcher when your app starts. The runtime terminates the process after a crash, so present the screen on the launch that follows it.
- // do somethings
- }
-)
-```
+```swift
+import Snitcher
-### Custom Exception Trace Screen
+@main
+struct SampleApp: App {
-Snitcher provides ready-to-use exception tracing screens (such as `ExceptionTraceActivity`, built with the `ExceptionTraceScreen` Composable), giving you the flexibility to extensively tailor these screens according to your preferences, and even design your own distinct tracing interfaces.
+ init() {
+ Snitcher.shared.install()
+ }
-```kotlin
-Snitcher.install(
- application = this,
- traceActivity = ExceptionTraceActivity::class
-)
+ var body: some Scene {
+ WindowGroup { ContentView() }
+ }
+}
```
-If you don't specify the `traceActivity` parameter, the default value will be `ExceptionTraceActivity`. You can tailor the launched activity by modifying the `traceActivity` parameter to match your preferred choice. The example below demonstrates the construction of a customized trace Activity:
+```swift
+struct ContentView: View {
-```kotlin
-class MyExceptionTraceActivity : ComponentActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContent {
- val exception by Snitcher.exception.collectAsState()
- val launcher by Snitcher.launcher.collectAsState()
-
- SnitcherTheme {
- if (exception != null) {
- if (BuildConfig.DEBUG) {
- // implement your own exception trace screen
- ExceptionTraceScreen(
- launcher = launcher,
- snitcherException = exception!!,
- )
- } else {
- // implement your own app restore screen
- AppRestoreScreen(launcher = launcher)
- }
+ @State private var showsCrash = Snitcher.shared.exception.value != nil
+
+ var body: some View {
+ MyContent()
+ .fullScreenCover(isPresented: $showsCrash) {
+ SnitcherScreen {
+ Snitcher.shared.clear()
+ showsCrash = false
}
+ .ignoresSafeArea()
}
- }
}
}
+
+struct SnitcherScreen: UIViewControllerRepresentable {
+ let onRestore: () -> Void
+
+ func makeUIViewController(context: Context) -> UIViewController {
+ SnitcherViewControllerKt.snitcherViewController(onRestore: onRestore)
+ }
+
+ func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
+}
```
-As demonstrated in the example above, Snitcher provides access to the `SnitcherException` and the package name of the launcher Activity. This information can be utilized to construct highly customized trace screens that align with your specific needs. Snitcher conveniently provides this information through [StateFlow](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/)s, allowing you to observe these values without the need for cumbersome intent handling when initiating the trace Activity.
+The [demo-ios](demo-ios) module is a complete sample, and [demo-desktop](demo-desktop) and [demo](demo) cover the other two platforms.
+
+### Tracing global exceptions
+
+Every installer takes an `exceptionHandler`, which is useful to report a crash to another platform, such as [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics).
```kotlin
-val exception: SnitcherException? by Snitcher.exception.collectAsState()
-val launcher: String by Snitcher.launcher.collectAsState()
+Snitcher.install(
+ application = this,
+ exceptionHandler = { exception: SnitcherException ->
+ Firebase.crashlytics.log(exception.stackTrace)
+ },
+)
```
-> **Note**: Following any app crashes, you can readily observe the exception details across various components at any time and from any location.
+`SnitcherException` carries the exception name, the message, the whole stack trace, and the thread information. On Android and on the desktop you can restore the original throwable with `SnitcherException.toThrowable()`.
-### Custom Launcher (Restore) Activity
+### Launcher (restore) Activity
-Furthermore, you can customize the launcher (restore activity), specifying which Activity should be executed upon restoration within the trace activity. If you don't specify a `launcher` activity, the most recent Activity that encountered a crash will automatically be launched when users press the 'restore' button. However, if you wish to launch a particular Activity instead of the most recent one, you can accomplish this by providing the launcher parameter:
+On Android you can decide which Activity is launched when the user restores the app. Without it, the most recent Activity before the crash is used.
```kotlin
Snitcher.install(
@@ -149,7 +188,7 @@ Snitcher.install(
### Custom Snitcher Theme
-The pre-built screens are styled by a `SnitcherThemeConfig`. Give one to `Snitcher.install`, and the built-in `ExceptionTraceActivity`, as well as every screen you wrap in `SnitcherTheme`, will be drawn with it:
+The pre-built screens are styled by a `SnitcherThemeConfig`. Give one to the installer, and the pre-built screens, as well as every screen you wrap in `SnitcherTheme`, will be drawn with it:
```kotlin
Snitcher.install(
@@ -165,138 +204,54 @@ Snitcher.install(
)
```
-`lightColors` and `darkColors` are picked by the system dark mode. Every field has a default, so you only need to declare what you want to change.
-
-**SnitcherColor**
-
-| Property | Where it is used |
-|---|---|
-| `primary` | The title, the section labels, and the button background |
-| `onPrimary` | The content that is drawn on the buttons |
-| `background` | The screen background, which is also drawn behind the system bars |
-| `textHighEmphasis` | The exception message, the stack trace, and the restore screen texts |
-| `textLowEmphasis` | The package and device information |
-| `outline` | The border of the stack trace container |
-
-**SnitcherTypography**
-
-`title`, `message`, `deviceInfo`, `sectionLabel`, `stacktrace`, and `button` are plain `TextStyle`s, so fonts, sizes, and weights are all yours.
-
-**SnitcherShapes**
-
-`button` and `stacktrace` are plain `Shape`s.
-
-You can also swap the theme at any time, which restyles the screens right away:
-
-```kotlin
-Snitcher.theme = Snitcher.theme.copy(darkColors = SnitcherColor.defaultDarkColors())
-```
-
-If you build your own trace screens, wrap them in `SnitcherTheme`. Its defaults come from the installed configuration, so your screens and the pre-built ones stay in sync, and you can still override any of them for a single screen:
-
-```kotlin
-SnitcherTheme(
- colors = SnitcherTheme.colors.copy(primary = Color.Blue),
-) {
- if (exception != null) {
- ExceptionTraceScreen(
- launcher = launcher,
- snitcherException = exception!!,
- )
- }
-}
-```
-
-If you wish to personalize the text strings within the pre-built UIs, you can override the following string values within your `strings.xml` file:
-
-```xml
-Oops, Restore the previous screen?
-The app crashed unexpectedly. We apologize for the inconvenience. Would you like to return to where you left off?
-Restore
-Restore App
-Debug on IDE
-Stacktrace
-```
+Colors left unspecified follow the color they belong to, so `copy(primary = Color.Red)` restyles the title, the labels, the buttons, and the stack trace border together.
-### Custom Build Types
+### Custom texts
-If you intend to launch distinct trace activities and implement different behaviors or flavors, you can install Snitcher based on specific build types, as shown in the example below:
+The texts of the pre-built screens are plain strings, so they travel across platforms and you can translate them:
```kotlin
Snitcher.install(
application = this,
- traceActivity = if (BuildConfig.DEBUG) {
- MyExceptionTraceActivity::class
- } else {
- RestoreActivity::class
- },
- exceptionHandler = {
- if (!BuildConfig.DEBUG) {
- Firebase.crashlytics.log(exception.stackTrace)
- }
- }
+ strings = SnitcherStrings(
+ traceRestoreButton = "λ€μ μμ",
+ traceStacktrace = "μ€ννΈλ μ΄μ€",
+ ),
)
```
-Alternatively, you can create a single trace Activity and manage the different build types within the activity itself, as demonstrated in the example below:
+### Custom trace screens
+
+`ExceptionTraceScreen` and `AppRestoreScreen` are plain composables, so you can build your own screen around the state that Snitcher publishes:
```kotlin
-Snitcher.install(
- application = this,
- launcher = MyExceptionTraceActivity::class,
-)
+val exception by Snitcher.exception.collectAsState()
-class MyExceptionTraceActivity : ComponentActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContent {
- val exception by Snitcher.exception.collectAsState()
- val launcher by Snitcher.launcher.collectAsState()
-
- SnitcherTheme {
- if (exception != null) {
- if (Snitcher.isDebuggable) {
- ExceptionTraceScreen(
- launcher = launcher,
- snitcherException = exception!!,
- )
- } else {
- AppRestoreScreen(launcher = launcher)
- }
- }
- }
- }
+SnitcherTheme {
+ exception?.let {
+ ExceptionTraceScreen(
+ snitcherException = it,
+ onRestore = { /* restore your app */ },
+ )
}
}
```
-As demonstrated in the above example, you have the flexibility to create your own trace or restore Activities and install them according to your various build types.
-
-### Trace Strategy
-
-You can globally trace exceptions by providing the `exceptionHandler` lambda parameter during Snitcher installation. However, there might be instances where you don't wish to launch the trace Activity but rather perform other actions, such as reporting crashes or sending messages to a `BroadcastReceiver`. In such cases, you can modify the trace strategy as shown in the example below:
-
-```kotlin
-Snitcher.install(
- application = this,
- traceStrategy = TraceStrategy.REPLACE,
- exceptionHandler = {
- // do something
- },
-)
-```
+## Migrating from 1.0.x
-In this scenario, only the `exceptionHandler` lambda function will be executed without triggering the launch of any trace Activity. If the `traceStrategy` parameter is not specified, the default behavior is set to `TraceStrategy.CO_WORK`, which involves executing the `exceptionHandler` lambda and initiating the trace activity when an app crash occurs.
+- `Snitcher.install(..)` is a platform extension now, so add `import com.skydoves.snitcher.install`
+- The texts moved from `values/strings.xml` to `SnitcherStrings`, which the installer takes. Locale
+ specific `values-xx` overrides of the `snitcher_*` strings no longer apply
+- `SnitcherException.throwable` became `SnitcherException.toThrowable()`, on Android and desktop
+- `SnitcherException` is no longer `java.io.Serializable`, and `SnitcherInstaller` is gone
+- The crash is stored in a plain file rather than in DataStore, so a crash written by 1.0.x is
+ not read back after the update
## Find this repository useful? :heart:
-
-Support it by joining __[stargazers](https://github.com/skydoves/cloudy/stargazers)__ for this repository. :star:
+Support it by joining __[stargazers](https://github.com/skydoves/snitcher/stargazers)__ for this repository. :star:
Also, __[follow me](https://github.com/skydoves)__ on GitHub for my next creations! π€©
# License
-
```xml
Designed and developed by 2023 skydoves (Jaewoong Eum)
@@ -304,7 +259,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
+ http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/art/android_trace.png b/art/android_trace.png
new file mode 100644
index 0000000..26255c0
Binary files /dev/null and b/art/android_trace.png differ
diff --git a/art/demo_debug.png b/art/demo_debug.png
deleted file mode 100644
index eee9a9c..0000000
Binary files a/art/demo_debug.png and /dev/null differ
diff --git a/art/demo_release.png b/art/demo_release.png
deleted file mode 100644
index e8f0932..0000000
Binary files a/art/demo_release.png and /dev/null differ
diff --git a/art/desktop_restore.png b/art/desktop_restore.png
new file mode 100644
index 0000000..53d38d9
Binary files /dev/null and b/art/desktop_restore.png differ
diff --git a/art/desktop_trace.png b/art/desktop_trace.png
new file mode 100644
index 0000000..5a5127a
Binary files /dev/null and b/art/desktop_trace.png differ
diff --git a/art/desktop_trace_dark.png b/art/desktop_trace_dark.png
new file mode 100644
index 0000000..1d7ef90
Binary files /dev/null and b/art/desktop_trace_dark.png differ
diff --git a/art/ios_trace.png b/art/ios_trace.png
new file mode 100644
index 0000000..17b235e
Binary files /dev/null and b/art/ios_trace.png differ
diff --git a/benchmark/build.gradle.kts b/benchmark/build.gradle.kts
index ee4fb0f..15b6c9e 100644
--- a/benchmark/build.gradle.kts
+++ b/benchmark/build.gradle.kts
@@ -4,7 +4,6 @@ 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 84167e5..30a0a50 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -16,19 +16,16 @@
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.multiplatform) apply false
+ alias(libs.plugins.kotlin.jvm) apply false
+ alias(libs.plugins.android.kotlin.multiplatform.library) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.compose.compiler) apply false
+ alias(libs.plugins.compose.multiplatform) 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/buildSrc/src/main/kotlin/com/skydoves/snitcher/Configuration.kt b/buildSrc/src/main/kotlin/com/skydoves/snitcher/Configuration.kt
index d9a1137..d10511a 100644
--- a/buildSrc/src/main/kotlin/com/skydoves/snitcher/Configuration.kt
+++ b/buildSrc/src/main/kotlin/com/skydoves/snitcher/Configuration.kt
@@ -20,10 +20,10 @@ object Configuration {
const val compileSdk = 37
const val targetSdk = 37
const val minSdk = 23
- const val majorVersion = 1
+ const val majorVersion = 2
const val minorVersion = 0
- const val patchVersion = 3
+ const val patchVersion = 0
const val versionName = "$majorVersion.$minorVersion.$patchVersion"
- const val versionCode = 4
+ const val versionCode = 5
const val artifactGroup = "com.github.skydoves"
}
diff --git a/demo-desktop/build.gradle.kts b/demo-desktop/build.gradle.kts
new file mode 100644
index 0000000..31ab3d6
--- /dev/null
+++ b/demo-desktop/build.gradle.kts
@@ -0,0 +1,54 @@
+/*
+ * Designed and developed by 2023 skydoves (Jaewoong Eum)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
+plugins {
+ id(libs.plugins.kotlin.jvm.get().pluginId)
+ id(libs.plugins.compose.multiplatform.get().pluginId)
+ id(libs.plugins.compose.compiler.get().pluginId)
+}
+
+kotlin {
+ compilerOptions {
+ jvmTarget.set(JvmTarget.fromTarget(libs.versions.jvmTarget.get()))
+ }
+}
+
+java {
+ sourceCompatibility = JavaVersion.toVersion(libs.versions.jvmTarget.get())
+ targetCompatibility = JavaVersion.toVersion(libs.versions.jvmTarget.get())
+}
+
+compose.desktop {
+ application {
+ mainClass = "com.skydoves.snitcherdemo.desktop.MainKt"
+ }
+}
+
+dependencies {
+ implementation(project(":snitcher"))
+ implementation(compose.desktop.currentOs)
+}
+
+/** Renders the pre-built screens into `art/` for the documentation. */
+tasks.register("generateScreenshots") {
+ group = "documentation"
+ description = "Renders the desktop Snitcher screens off screen into the art directory."
+ mainClass.set("com.skydoves.snitcherdemo.desktop.ScreenshotsKt")
+ classpath = sourceSets.getByName("main").runtimeClasspath
+ args("${rootDir}/art")
+}
diff --git a/demo-desktop/src/main/kotlin/com/skydoves/snitcherdemo/desktop/Main.kt b/demo-desktop/src/main/kotlin/com/skydoves/snitcherdemo/desktop/Main.kt
new file mode 100644
index 0000000..377dbca
--- /dev/null
+++ b/demo-desktop/src/main/kotlin/com/skydoves/snitcherdemo/desktop/Main.kt
@@ -0,0 +1,88 @@
+/*
+ * Designed and developed by 2023 skydoves (Jaewoong Eum)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.skydoves.snitcherdemo.desktop
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.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.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.application
+import com.skydoves.snitcher.Snitcher
+import com.skydoves.snitcher.install
+import com.skydoves.snitcher.ui.SnitcherTraceWindow
+import com.skydoves.snitcher.ui.theme.SnitcherColor
+import com.skydoves.snitcher.ui.theme.SnitcherShapes
+import com.skydoves.snitcher.ui.theme.SnitcherTheme
+import com.skydoves.snitcher.ui.theme.SnitcherThemeConfig
+import com.skydoves.snitcher.ui.theme.SnitcherTypography
+
+private val Purple = Color(0xFF6650a4)
+
+public fun main() {
+ Snitcher.install(
+ theme = SnitcherThemeConfig(
+ lightColors = SnitcherColor.defaultColors().copy(primary = Purple),
+ darkColors = SnitcherColor.defaultDarkColors().copy(primary = Color(0xFFD0BCFF)),
+ typography = SnitcherTypography.defaultTypography().copy(
+ title = TextStyle(fontSize = 30.sp, fontWeight = FontWeight.Black),
+ ),
+ shapes = SnitcherShapes(button = RoundedCornerShape(20.dp)),
+ ),
+ exceptionHandler = {
+ // do something
+ },
+ )
+
+ application {
+ Window(
+ onCloseRequest = ::exitApplication,
+ title = "Snitcher Desktop Demo",
+ ) {
+ DemoContent()
+ }
+
+ SnitcherTraceWindow()
+ }
+}
+
+@Composable
+private fun DemoContent() {
+ SnitcherTheme {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(SnitcherTheme.colors.background),
+ ) {
+ Button(
+ modifier = Modifier.align(Alignment.Center),
+ onClick = { throw RuntimeException("This is an intended runtime exception.") },
+ ) {
+ Text(text = "Throw an exception")
+ }
+ }
+ }
+}
diff --git a/demo-desktop/src/main/kotlin/com/skydoves/snitcherdemo/desktop/Screenshots.kt b/demo-desktop/src/main/kotlin/com/skydoves/snitcherdemo/desktop/Screenshots.kt
new file mode 100644
index 0000000..dc15874
--- /dev/null
+++ b/demo-desktop/src/main/kotlin/com/skydoves/snitcherdemo/desktop/Screenshots.kt
@@ -0,0 +1,121 @@
+/*
+ * Designed and developed by 2023 skydoves (Jaewoong Eum)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:OptIn(ExperimentalComposeUiApi::class)
+
+package com.skydoves.snitcherdemo.desktop
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.ImageComposeScene
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.skydoves.snitcher.Snitcher
+import com.skydoves.snitcher.model.SnitcherException
+import com.skydoves.snitcher.ui.AppRestoreScreen
+import com.skydoves.snitcher.ui.ExceptionTraceScreen
+import com.skydoves.snitcher.ui.theme.SnitcherColor
+import com.skydoves.snitcher.ui.theme.SnitcherShapes
+import com.skydoves.snitcher.ui.theme.SnitcherTheme
+import com.skydoves.snitcher.ui.theme.SnitcherThemeConfig
+import com.skydoves.snitcher.ui.theme.SnitcherTypography
+import org.jetbrains.skia.EncodedImageFormat
+import java.io.File
+
+/** Renders the pre-built screens off screen and writes them as PNG files for the documentation. */
+public fun main(args: Array) {
+ val outputDirectory = File(args.firstOrNull() ?: "art")
+ outputDirectory.mkdirs()
+
+ Snitcher.theme = SnitcherThemeConfig(
+ lightColors = SnitcherColor.defaultColors().copy(primary = Color(0xFF6650a4)),
+ darkColors = SnitcherColor.defaultDarkColors().copy(primary = Color(0xFFD0BCFF)),
+ typography = SnitcherTypography.defaultTypography().copy(
+ title = TextStyle(fontSize = 30.sp, fontWeight = FontWeight.Black),
+ ),
+ shapes = SnitcherShapes(button = RoundedCornerShape(20.dp)),
+ )
+ Snitcher.platformInfo = "1.0.3 (4) macOS 26.5 (JVM 21)"
+
+ render(File(outputDirectory, "desktop_trace.png"), darkTheme = false) {
+ ExceptionTraceScreen(
+ snitcherException = sampleException,
+ onRestore = {},
+ onDebug = {},
+ )
+ }
+
+ render(File(outputDirectory, "desktop_trace_dark.png"), darkTheme = true) {
+ ExceptionTraceScreen(
+ snitcherException = sampleException,
+ onRestore = {},
+ onDebug = {},
+ )
+ }
+
+ render(File(outputDirectory, "desktop_restore.png"), darkTheme = false) {
+ AppRestoreScreen(onRestore = {})
+ }
+}
+
+private fun render(output: File, darkTheme: Boolean, content: @Composable () -> Unit) {
+ val scene = ImageComposeScene(
+ width = WIDTH,
+ height = HEIGHT,
+ density = Density(2f),
+ ) {
+ SnitcherTheme(darkTheme = darkTheme) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ content()
+ }
+ }
+ }
+
+ try {
+ val image = scene.render()
+ val data = image.encodeToData(EncodedImageFormat.PNG)
+ ?: error("failed to encode ${output.name}")
+ output.writeBytes(data.bytes)
+ println("wrote ${output.absolutePath}")
+ } finally {
+ scene.close()
+ }
+}
+
+private const val WIDTH = 1200
+private const val HEIGHT = 1100
+
+private val sampleException = SnitcherException(
+ threadId = 1,
+ threadName = "main",
+ packageName = "RuntimeException",
+ message = "This is an intended runtime exception.",
+ stackTrace = """
+ java.lang.RuntimeException: This is an intended runtime exception.
+ at com.skydoves.snitcherdemo.desktop.MainKt.DemoContent(Main.kt:74)
+ at androidx.compose.foundation.ClickableNode.handleUpEvent(Clickable.kt:950)
+ at androidx.compose.ui.input.pointer.Node.dispatchMainEventPass(HitPathTracker.kt:445)
+ at androidx.compose.ui.platform.ComposeSceneMediator.processPointerInput(Mediator.kt:381)
+ at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:92)
+ """.trimIndent(),
+)
diff --git a/demo-ios/Info.plist b/demo-ios/Info.plist
new file mode 100644
index 0000000..ab4dfd2
--- /dev/null
+++ b/demo-ios/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ CADisableMinimumFrameDurationOnPhone
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Snitcher
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0.3
+ CFBundleVersion
+ 4
+ UILaunchScreen
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+
+
+
diff --git a/demo-ios/SnitcherDemo.xcodeproj/project.pbxproj b/demo-ios/SnitcherDemo.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..16d44fe
--- /dev/null
+++ b/demo-ios/SnitcherDemo.xcodeproj/project.pbxproj
@@ -0,0 +1,327 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 728D9A7A0D124DC53C15A6E7 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E72BA346466D183904C6D0 /* iOSApp.swift */; };
+ B3D90AEE3675C33DE55DF8A2 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF1ACEE89A074AEB98E7ABE /* ContentView.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ 015F31CD5E844EC5F4F4AE26 /* SnitcherDemo.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = SnitcherDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 62E72BA346466D183904C6D0 /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; };
+ BAF1ACEE89A074AEB98E7ABE /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXGroup section */
+ 08AF48FDF21EA677E44FDEEF = {
+ isa = PBXGroup;
+ children = (
+ B4DF3DA3EB64365F3FEF038E /* Sources */,
+ BD43363266B25F3511410307 /* Products */,
+ );
+ sourceTree = "";
+ };
+ B4DF3DA3EB64365F3FEF038E /* Sources */ = {
+ isa = PBXGroup;
+ children = (
+ BAF1ACEE89A074AEB98E7ABE /* ContentView.swift */,
+ 62E72BA346466D183904C6D0 /* iOSApp.swift */,
+ );
+ path = Sources;
+ sourceTree = "";
+ };
+ BD43363266B25F3511410307 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 015F31CD5E844EC5F4F4AE26 /* SnitcherDemo.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 910AE3A3BAF6E8E918569B5F /* SnitcherDemo */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = C4F2C5C18DE32EE417DF35F2 /* Build configuration list for PBXNativeTarget "SnitcherDemo" */;
+ buildPhases = (
+ C89FF8CC558500BF625DAADF /* Build the Snitcher framework */,
+ 3C426FD3F0418B5272FE0EB4 /* Sources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = SnitcherDemo;
+ packageProductDependencies = (
+ );
+ productName = SnitcherDemo;
+ productReference = 015F31CD5E844EC5F4F4AE26 /* SnitcherDemo.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ E5107465666EAE2A378643C2 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = YES;
+ LastUpgradeCheck = 1430;
+ TargetAttributes = {
+ };
+ };
+ buildConfigurationList = C53E2F567FE65D9C9634D0D8 /* Build configuration list for PBXProject "SnitcherDemo" */;
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ Base,
+ en,
+ );
+ mainGroup = 08AF48FDF21EA677E44FDEEF;
+ minimizedProjectReferenceProxies = 1;
+ preferredProjectObjectVersion = 77;
+ productRefGroup = BD43363266B25F3511410307 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 910AE3A3BAF6E8E918569B5F /* SnitcherDemo */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ C89FF8CC558500BF625DAADF /* Build the Snitcher framework */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ name = "Build the Snitcher framework";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "cd \"$SRCROOT/..\"\n./gradlew :snitcher:embedAndSignAppleFrameworkForXcode\n";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 3C426FD3F0418B5272FE0EB4 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ B3D90AEE3675C33DE55DF8A2 /* ContentView.swift in Sources */,
+ 728D9A7A0D124DC53C15A6E7 /* iOSApp.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 518F275F155BAC1105898AE9 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SRCROOT)/../snitcher/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
+ );
+ INFOPLIST_FILE = Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-framework",
+ Snitcher,
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.skydoves.snitcherdemo;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ 8A977199B758CB9A89426009 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "DEBUG=1",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 952B37DF4B6183F9441ADD30 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "iPhone Developer";
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(SRCROOT)/../snitcher/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
+ );
+ INFOPLIST_FILE = Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-framework",
+ Snitcher,
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.skydoves.snitcherdemo;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ FCDABE18D1AD844F18BD9CF9 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ C4F2C5C18DE32EE417DF35F2 /* Build configuration list for PBXNativeTarget "SnitcherDemo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 952B37DF4B6183F9441ADD30 /* Debug */,
+ 518F275F155BAC1105898AE9 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Debug;
+ };
+ C53E2F567FE65D9C9634D0D8 /* Build configuration list for PBXProject "SnitcherDemo" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 8A977199B758CB9A89426009 /* Debug */,
+ FCDABE18D1AD844F18BD9CF9 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Debug;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = E5107465666EAE2A378643C2 /* Project object */;
+}
diff --git a/demo-ios/Sources/ContentView.swift b/demo-ios/Sources/ContentView.swift
new file mode 100644
index 0000000..3f8b5bf
--- /dev/null
+++ b/demo-ios/Sources/ContentView.swift
@@ -0,0 +1,54 @@
+import SwiftUI
+import UIKit
+import Snitcher
+
+struct ContentView: View {
+
+ @State private var showsCrash = Snitcher.shared.exception.value != nil
+
+ var body: some View {
+ VStack(spacing: 24) {
+ Text("Snitcher")
+ .font(.largeTitle.bold())
+
+ Button("Raise an exception") {
+ raiseDemoException()
+ }
+ .buttonStyle(.borderedProminent)
+ }
+ .onAppear {
+ // lets an automated run reproduce a crash without a tap, which is how the screenshots in the
+ // documentation are taken.
+ if ProcessInfo.processInfo.arguments.contains("--crash-on-launch") {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1) { raiseDemoException() }
+ }
+ }
+ .fullScreenCover(isPresented: $showsCrash) {
+ SnitcherScreen(onRestore: {
+ Snitcher.shared.clear()
+ showsCrash = false
+ })
+ .ignoresSafeArea()
+ }
+ }
+}
+
+private func raiseDemoException() {
+ NSException(
+ name: NSExceptionName("RuntimeException"),
+ reason: "This is an intended runtime exception.",
+ userInfo: nil
+ ).raise()
+}
+
+/// Wraps the UIViewController that Snitcher builds so that SwiftUI can present it.
+struct SnitcherScreen: UIViewControllerRepresentable {
+
+ let onRestore: () -> Void
+
+ func makeUIViewController(context: Context) -> UIViewController {
+ SnitcherViewControllerKt.snitcherViewController(onRestore: onRestore)
+ }
+
+ func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
+}
diff --git a/demo-ios/Sources/iOSApp.swift b/demo-ios/Sources/iOSApp.swift
new file mode 100644
index 0000000..db38ebd
--- /dev/null
+++ b/demo-ios/Sources/iOSApp.swift
@@ -0,0 +1,18 @@
+import SwiftUI
+import Snitcher
+
+@main
+struct SnitcherDemoApp: App {
+
+ init() {
+ // The defaults are enough here. Every parameter can be given explicitly instead, which is how
+ // you pass your own theme, texts, or an extra exception handler.
+ Snitcher.shared.install()
+ }
+
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
diff --git a/demo-ios/project.yml b/demo-ios/project.yml
new file mode 100644
index 0000000..7c22ffc
--- /dev/null
+++ b/demo-ios/project.yml
@@ -0,0 +1,42 @@
+name: SnitcherDemo
+options:
+ bundleIdPrefix: com.skydoves
+ deploymentTarget:
+ iOS: "16.0"
+ createIntermediateGroups: true
+settings:
+ base:
+ SWIFT_VERSION: "5.0"
+targets:
+ SnitcherDemo:
+ type: application
+ platform: iOS
+ sources:
+ - Sources
+ info:
+ path: Info.plist
+ properties:
+ CFBundleDisplayName: Snitcher
+ CFBundleShortVersionString: "1.0.3"
+ CFBundleVersion: "4"
+ CADisableMinimumFrameDurationOnPhone: true
+ UILaunchScreen: {}
+ UISupportedInterfaceOrientations:
+ - UIInterfaceOrientationPortrait
+ settings:
+ base:
+ PRODUCT_BUNDLE_IDENTIFIER: com.skydoves.snitcherdemo
+ FRAMEWORK_SEARCH_PATHS:
+ - $(inherited)
+ - $(SRCROOT)/../snitcher/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)
+ OTHER_LDFLAGS:
+ - $(inherited)
+ - -framework
+ - Snitcher
+ ENABLE_USER_SCRIPT_SANDBOXING: NO
+ preBuildScripts:
+ - name: Build the Snitcher framework
+ basedOnDependencyAnalysis: false
+ script: |
+ cd "$SRCROOT/.."
+ ./gradlew :snitcher:embedAndSignAppleFrameworkForXcode
diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts
index c0bec22..1fa6c7d 100644
--- a/demo/build.gradle.kts
+++ b/demo/build.gradle.kts
@@ -18,7 +18,6 @@ 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/demo/src/main/kotlin/com/skydoves/snitcherdemo/SnitcherDemoApp.kt b/demo/src/main/kotlin/com/skydoves/snitcherdemo/SnitcherDemoApp.kt
index 055226c..87c8e72 100644
--- a/demo/src/main/kotlin/com/skydoves/snitcherdemo/SnitcherDemoApp.kt
+++ b/demo/src/main/kotlin/com/skydoves/snitcherdemo/SnitcherDemoApp.kt
@@ -22,6 +22,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.skydoves.snitcher.Snitcher
+import com.skydoves.snitcher.install
import com.skydoves.snitcher.ui.theme.SnitcherColor
import com.skydoves.snitcher.ui.theme.SnitcherShapes
import com.skydoves.snitcher.ui.theme.SnitcherThemeConfig
diff --git a/docs/android.md b/docs/android.md
new file mode 100644
index 0000000..efb7beb
--- /dev/null
+++ b/docs/android.md
@@ -0,0 +1,43 @@
+# Android
+
+Install Snitcher in your `Application` class. Snitcher becomes the default uncaught exception handler, persists the crash synchronously, launches the exception tracing activity, and kills the crashed process.
+
+```kotlin
+class App : Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+
+ Snitcher.install(application = this)
+ }
+}
+```
+
+`install` is an extension of the `Snitcher` object that lives in the Android source set, so import it explicitly:
+
+```kotlin
+import com.skydoves.snitcher.Snitcher
+import com.skydoves.snitcher.install
+```
+
+## What the user sees
+
+The bundled `ExceptionTraceActivity` displays the full [exception trace screen](custom-trace-screen.md) on a debuggable build, and the friendly restore screen otherwise. The decision comes from `Snitcher.isDebuggable`, which is taken from the application's debuggable flag and can be overridden:
+
+```kotlin
+Snitcher.isDebuggable = BuildConfig.DEBUG
+```
+
+## Where the crash is stored
+
+The crash is written to `filesDir/snitcher.json` before the process is killed, and it is read back on the next launch, so `Snitcher.exception` is never empty right after a crash.
+
+Clear it once you have handled it:
+
+```kotlin
+Snitcher.clear()
+```
+
+## Requirements
+
+Android 6.0 (API 23) and above, which is the floor of the Compose and AndroidX libraries that Snitcher builds on.
diff --git a/docs/custom-build-type.md b/docs/custom-build-type.md
index a28367e..09809a3 100644
--- a/docs/custom-build-type.md
+++ b/docs/custom-build-type.md
@@ -10,11 +10,11 @@ Snitcher.install(
} else {
RestoreActivity::class
},
- exceptionHandler = {
+ exceptionHandler = { exception ->
if (!BuildConfig.DEBUG) {
Firebase.crashlytics.log(exception.stackTrace)
}
- }
+ },
)
```
@@ -23,7 +23,7 @@ Alternatively, you can create a single trace Activity and manage the different b
```kotlin
Snitcher.install(
application = this,
- launcher = MyExceptionTraceActivity::class,
+ traceActivity = MyExceptionTraceActivity::class,
)
class MyExceptionTraceActivity : ComponentActivity() {
@@ -36,12 +36,9 @@ class MyExceptionTraceActivity : ComponentActivity() {
val launcher by Snitcher.launcher.collectAsState()
SnitcherTheme {
- if (exception != null) {
+ exception?.let {
if (Snitcher.isDebuggable) {
- ExceptionTraceScreen(
- launcher = launcher,
- snitcherException = exception!!,
- )
+ ExceptionTraceScreen(launcher = launcher, snitcherException = it)
} else {
AppRestoreScreen(launcher = launcher)
}
@@ -52,4 +49,8 @@ class MyExceptionTraceActivity : ComponentActivity() {
}
```
-As demonstrated in the above example, you have the flexibility to create your own trace or restore Activities and install them according to your various build types.
+`Snitcher.isDebuggable` is taken from the application's debuggable flag on Android, from the binary kind on iOS, and is true on the desktop. Assign it yourself to decide which screen is displayed:
+
+```kotlin
+Snitcher.isDebuggable = BuildConfig.DEBUG
+```
diff --git a/docs/custom-launcher-activity.md b/docs/custom-launcher-activity.md
index a98298a..3a5fac8 100644
--- a/docs/custom-launcher-activity.md
+++ b/docs/custom-launcher-activity.md
@@ -1,10 +1,18 @@
# Launcher (Restore) Activity
-Furthermore, you can customize the launcher (restore activity), specifying which Activity should be executed upon restoration within the trace activity. If you don't specify a `launcher` activity, the most recent Activity that encountered a crash will automatically be launched when users press the 'restore' button. However, if you wish to launch a particular Activity instead of the most recent one, you can accomplish this by providing the launcher parameter:
+This one is Android only, since an application cannot relaunch itself on iOS, and the desktop process keeps running after a crash.
+
+You can customize the launcher, which specifies the Activity that is started when the user restores the app from the trace screen. If you don't specify a `launcher`, the most recent Activity before the crash is launched:
```kotlin
Snitcher.install(
application = this,
launcher = MainActivity::class,
)
-```
\ No newline at end of file
+```
+
+The launcher is published as a `StateFlow`, so your own trace screen can read it:
+
+```kotlin
+val launcher: String by Snitcher.launcher.collectAsState()
+```
diff --git a/docs/custom-theme.md b/docs/custom-theme.md
index bc7a754..7bba394 100644
--- a/docs/custom-theme.md
+++ b/docs/custom-theme.md
@@ -1,6 +1,6 @@
# Snitcher Theme
-The pre-built screens are styled by a `SnitcherThemeConfig`. Give one to `Snitcher.install`, and the built-in `ExceptionTraceActivity`, as well as every screen you wrap in `SnitcherTheme`, will be drawn with it:
+The pre-built screens are styled by a `SnitcherThemeConfig`. Give one to the platform installer, and the built-in `ExceptionTraceActivity`, as well as every screen you wrap in `SnitcherTheme`, will be drawn with it:
```kotlin
Snitcher.install(
@@ -16,7 +16,7 @@ Snitcher.install(
)
```
-`lightColors` and `darkColors` are picked by the system dark mode. Every field has a default, so you only need to declare what you want to change.
+`lightColors` and `darkColors` are picked by the system dark mode. Start from `defaultColors()`, `defaultDarkColors()` and `defaultTypography()` and `copy` what you want to change, since a palette and a typography carry no partial defaults.
**SnitcherColor**
@@ -47,24 +47,36 @@ If you build your own trace screens, wrap them in `SnitcherTheme`. Its defaults
```kotlin
SnitcherTheme(
- colors = SnitcherTheme.colors.copy(primary = Color.Blue),
+ colors = Snitcher.theme.lightColors.copy(primary = Color.Blue),
) {
- if (exception != null) {
- ExceptionTraceScreen(
- launcher = launcher,
- snitcherException = exception!!,
- )
+ exception?.let {
+ ExceptionTraceScreen(snitcherException = it, onRestore = { Snitcher.clear() })
}
}
```
-If you wish to personalize the text strings within the pre-built UIs, you can override the following string values within your `strings.xml` file:
+`SnitcherTheme.colors` reads the theme of the surrounding composition, so build the argument from `Snitcher.theme` rather than from `SnitcherTheme.colors` when you call `SnitcherTheme` yourself.
-```xml
-Oops, Restore the previous screen?
-The app crashed unexpectedly. We apologize for the inconvenience. Would you like to return to where you left off?
-Restore
-Restore App
-Debug on IDE
-Stacktrace
+The texts of the pre-built screens are plain strings rather than platform resources, so they are shared across platforms. Give a `SnitcherStrings` to the installer to translate or reword them:
+
+```kotlin
+Snitcher.install(
+ application = this,
+ strings = SnitcherStrings(
+ restoreTitle = "Oops, Restore the previous screen?",
+ restoreDescription = "The app crashed unexpectedly. Would you like to return to where you left off?",
+ restoreButton = "Restore",
+ traceRestoreButton = "Restore App",
+ traceDebugButton = "Debug on IDE",
+ traceStacktrace = "Stacktrace",
+ traceCopied = "Copied!",
+ ),
+)
+```
+
+Both can also be replaced at any time, which restyles the screens right away:
+
+```kotlin
+Snitcher.theme = Snitcher.theme.copy(darkColors = SnitcherColor.defaultDarkColors())
+Snitcher.strings = SnitcherStrings(traceStacktrace = "Stack trace")
```
diff --git a/docs/custom-trace-screen.md b/docs/custom-trace-screen.md
index f7c497c..bce45f8 100644
--- a/docs/custom-trace-screen.md
+++ b/docs/custom-trace-screen.md
@@ -1,36 +1,55 @@
# Exception Trace Screen
-Snitcher provides ready-to-use exception tracing screens (such as `ExceptionTraceActivity`, built with the `ExceptionTraceScreen` Composable), giving you the flexibility to extensively tailor these screens according to your preferences, and even design your own distinct tracing interfaces.
+Snitcher provides ready-to-use screens, `ExceptionTraceScreen` and `AppRestoreScreen`, which are Compose Multiplatform composables and render the same way on every platform. Each platform also gives you a host for them, and you are free to replace any of it.
+
+## The screens
+
+```kotlin
+@Composable
+public fun ExceptionTraceScreen(
+ snitcherException: SnitcherException,
+ modifier: Modifier = Modifier,
+ onRestore: (() -> Unit)? = null,
+ onDebug: (() -> Unit)? = null,
+)
+
+@Composable
+public fun AppRestoreScreen(
+ modifier: Modifier = Modifier,
+ onRestore: (() -> Unit)? = null,
+)
+```
+
+A button is hidden when its callback is null, which is how the screens adapt to what a platform supports. iOS cannot restore a throwable, for instance, so it never shows the debug button.
+
+## Android
+
+The bundled `ExceptionTraceActivity` is launched after a crash. Replace it with your own by giving the `traceActivity` parameter:
```kotlin
Snitcher.install(
application = this,
- traceActivity = ExceptionTraceActivity::class
+ traceActivity = MyExceptionTraceActivity::class,
)
```
-If you don't specify the `traceActivity` parameter, the default value will be `ExceptionTraceActivity`. You can tailor the launched activity by modifying the `traceActivity` parameter to match your preferred choice. The example below demonstrates the construction of a customized trace Activity:
-
```kotlin
class MyExceptionTraceActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
setContent {
val exception by Snitcher.exception.collectAsState()
val launcher by Snitcher.launcher.collectAsState()
SnitcherTheme {
- if (exception != null) {
+ exception?.let {
if (BuildConfig.DEBUG) {
- // implement your own exception trace screen
- ExceptionTraceScreen(
- launcher = launcher,
- snitcherException = exception!!,
- )
+ // the android overload wires the restore and debug actions for you
+ ExceptionTraceScreen(launcher = launcher, snitcherException = it)
} else {
- // implement your own app restore screen
AppRestoreScreen(launcher = launcher)
}
}
@@ -40,11 +59,40 @@ class MyExceptionTraceActivity : ComponentActivity() {
}
```
-As demonstrated in the example above, Snitcher provides access to the `SnitcherException` and the package name of the launcher Activity. This information can be utilized to construct highly customized trace screens that align with your specific needs. Snitcher conveniently provides this information through [StateFlow](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/)s, allowing you to observe these values without the need for cumbersome intent handling when initiating the trace Activity.
+## Desktop
+
+`SnitcherTraceWindow` opens a window whenever there is a crash. Build your own window instead when you want a different shell:
```kotlin
-val exception: SnitcherException? by Snitcher.exception.collectAsState()
-val launcher: String by Snitcher.launcher.collectAsState()
+application {
+ val exception by Snitcher.exception.collectAsState()
+
+ exception?.let {
+ Window(onCloseRequest = { Snitcher.clear() }, title = "Crash") {
+ SnitcherTheme {
+ ExceptionTraceScreen(
+ snitcherException = it,
+ onRestore = { Snitcher.clear() },
+ onDebug = { Snitcher.debug(it) },
+ )
+ }
+ }
+ }
+}
```
-> **Note**: Following any app crashes, you can readily observe the exception details across various components at any time and from any location.
+## iOS
+
+`snitcherViewController(onRestore:)` wraps the screens in a `UIViewController`. Build your own controller when you want to embed the screen in your own navigation:
+
+```kotlin
+fun myCrashViewController(): UIViewController = ComposeUIViewController {
+ val exception by Snitcher.exception.collectAsState()
+
+ SnitcherTheme {
+ exception?.let {
+ ExceptionTraceScreen(snitcherException = it, onRestore = { Snitcher.clear() })
+ }
+ }
+}
+```
diff --git a/docs/desktop.md b/docs/desktop.md
new file mode 100644
index 0000000..b771c41
--- /dev/null
+++ b/docs/desktop.md
@@ -0,0 +1,46 @@
+# Desktop
+
+Install Snitcher before your `application { }` block, and place `SnitcherTraceWindow` next to your own window:
+
+```kotlin
+import com.skydoves.snitcher.Snitcher
+import com.skydoves.snitcher.install
+import com.skydoves.snitcher.ui.SnitcherTraceWindow
+
+fun main() {
+ Snitcher.install()
+
+ application {
+ Window(onCloseRequest = ::exitApplication) {
+ App()
+ }
+
+ SnitcherTraceWindow()
+ }
+}
+```
+
+
+
+
+
+
+Snitcher becomes the default uncaught exception handler of the JVM and delegates to the handler that was installed before it, so a logging handler keeps working.
+
+Unlike Android, the desktop JVM keeps running after an uncaught exception on a background thread or on the AWT event thread. The crash is published through `Snitcher.exception` right away, the window opens, and closing it clears the crash.
+
+## Where the crash is stored
+
+`~/.snitcher/snitcher.json` by default, which you can change:
+
+```kotlin
+Snitcher.install(storageDirectory = "/path/that/your/app/owns")
+```
+
+## Throwing the exception again
+
+The trace window shows a **Debug on IDE** button, which throws the captured exception again so that an attached debugger catches it. You can do the same yourself:
+
+```kotlin
+Snitcher.debug(exception)
+```
diff --git a/docs/index.md b/docs/index.md
index 99dc25e..dab75d8 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,10 +1,17 @@
# Overview
-π¦ Snitcher captures global crashes, enabling easy redirection to the exception tracing screen for swift recovery.
+π¦ Snitcher captures global crashes on Android, desktop, and iOS, and redirects to the exception tracing screen for swift recovery.
+
+
+
+
+
## What is Snitcher?
-Snitcher offers versatile advantages such as aiding in debugging crashes during development, facilitating easy sharing of exceptions by your QA team, enhancing user experiences with recovery screens instead of abrupt closures, and enabling global exception tracing and customized launch behaviors tailored to your specific needs. You have the complete freedom to customize the crash tracing screens according to your build types and preferences, reporting to the Firebase's Crashlytics with displaying the exception screen, including options like launching a designated Activity, sending messages to your BroadcastReceiver, or any other desired actions.
+Snitcher offers versatile advantages such as aiding in debugging crashes during development, facilitating easy sharing of exceptions by your QA team, enhancing user experiences with recovery screens instead of abrupt closures, and enabling global exception tracing and customized launch behaviors tailored to your specific needs.
+
+The screens, the theme, and the captured model are shared across every platform with Compose Multiplatform, and each platform installs the crash capture that its runtime allows.
## Download
@@ -12,41 +19,41 @@ Snitcher offers versatile advantages such as aiding in debugging crashes during
### Gradle
-Add the dependency below to your **module**'s `build.gradle` file:
-
-=== "Groovy"
-
- ```Groovy
- dependencies {
- implementation "com.github.skydoves:snitcher:$version"
- }
- ```
-
-=== "KTS"
-
- ```kotlin
- dependencies {
- implementation("com.github.skydoves:snitcher:$version")
- }
- ```
-
-## Usage
-
-Installing Snitcher is a breeze; it hooks into global exceptions, replacing application closure with informative exception tracing screens. You can seamlessly install Snitcher using the following example:
+Add the dependency below to your **module**'s `build.gradle.kts` file:
```kotlin
-class App : Application() {
+dependencies {
+ implementation("com.github.skydoves:snitcher:2.0.0")
+}
+```
- override fun onCreate() {
- super.onCreate()
+In a multiplatform module, add it to the source set that needs it:
- Snitcher.install(application = this)
+```kotlin
+kotlin {
+ sourceSets {
+ commonMain.dependencies {
+ implementation("com.github.skydoves:snitcher:2.0.0")
+ }
}
}
```
-Upon completing the project build, in the event of encountering any exceptions, you will be presented with the screens illustrated below, tailored to your specific build types, rather than experiencing an abrupt application closure:
+## What each platform can capture
+
+| | Android | Desktop (JVM) | iOS |
+| --- | --- | --- | --- |
+| Uncaught JVM exceptions | yes | yes | not applicable |
+| Unhandled Kotlin exceptions | yes | yes | yes |
+| Uncaught `NSException` | not applicable | not applicable | yes |
+| Unix signals, such as a Swift trap or a memory error | no | no | no |
+| When the screen is shown | right after the crash, in a trace activity | right after the crash, in a window | on the next launch |
+| Can restart the app | yes | no, the process keeps running | no, iOS does not let an app relaunch itself |
+
+Snitcher never installs a signal handler. A crash that arrives as `SIGSEGV`, `SIGABRT`, or as a Swift runtime trap is out of reach for an in-process Kotlin handler, and belongs to a crash reporter such as KSCrash.
+
+## Platform guides
-| Debug | Release |
-| ------- | ------- |
-|  |  |
\ No newline at end of file
+- [Android](android.md)
+- [Desktop](desktop.md)
+- [iOS](ios.md)
diff --git a/docs/ios.md b/docs/ios.md
new file mode 100644
index 0000000..d09302a
--- /dev/null
+++ b/docs/ios.md
@@ -0,0 +1,141 @@
+# iOS
+
+Snitcher records a crash on iOS and leaves the termination to the runtime, because an unhandled Kotlin exception terminates the process. The crash is written to disk synchronously and displayed on the launch that follows it.
+
+Snitcher keeps the runtime behaviour intact: when no other hook was installed, the recorded crash ends in `terminateWithUnhandledException`, which is exactly what happens without Snitcher, so the system still writes its own crash report. A hook that was installed before Snitcher is chained instead.
+
+## Set up the framework
+
+Add Snitcher to the shared module that your iOS app already links, and build a framework out of it:
+
+```kotlin
+kotlin {
+ listOf(iosArm64(), iosSimulatorArm64()).forEach { target ->
+ target.binaries.framework {
+ baseName = "Shared"
+ isStatic = true
+ }
+ }
+
+ sourceSets {
+ commonMain.dependencies {
+ implementation("com.github.skydoves:snitcher:2.0.0")
+ }
+ }
+}
+```
+
+Then add a run script build phase to the Xcode target, before the compile phase:
+
+```bash
+cd "$SRCROOT/.."
+./gradlew :shared:embedAndSignAppleFrameworkForXcode
+```
+
+The [demo-ios](https://github.com/skydoves/snitcher/tree/main/demo-ios) sample links the Snitcher framework directly instead, and its Xcode project is generated from `project.yml` with [xcodegen](https://github.com/yonaskolb/XcodeGen):
+
+```bash
+cd demo-ios && xcodegen generate
+```
+
+## Install
+
+```swift
+import Snitcher
+
+@main
+struct SampleApp: App {
+
+ init() {
+ Snitcher.shared.install()
+ }
+
+ var body: some Scene {
+ WindowGroup { ContentView() }
+ }
+}
+```
+
+`Snitcher.shared.install()` uses the defaults. Kotlin default arguments do not cross the Objective-C boundary, so the fully configurable entry point takes every parameter:
+
+```swift
+Snitcher.shared.install(
+ storageDirectory: NSTemporaryDirectory(),
+ theme: SnitcherThemeConfig.companion.defaultThemeConfig(),
+ strings: SnitcherStrings.companion.defaultStrings(),
+ platformInfo: "1.0.0 iPhone",
+ catchObjectiveCExceptions: true,
+ exceptionHandler: { exception in
+ print(exception.stackTrace)
+ }
+)
+```
+
+From Kotlin, the same installer takes default arguments as usual:
+
+```kotlin
+Snitcher.install(
+ theme = SnitcherThemeConfig(lightColors = SnitcherColor.defaultColors()),
+)
+```
+
+## Present the screen
+
+```swift
+struct ContentView: View {
+
+ @State private var showsCrash = Snitcher.shared.exception.value != nil
+
+ var body: some View {
+ MyContent()
+ .fullScreenCover(isPresented: $showsCrash) {
+ SnitcherScreen {
+ Snitcher.shared.clear()
+ showsCrash = false
+ }
+ .ignoresSafeArea()
+ }
+ }
+}
+
+struct SnitcherScreen: UIViewControllerRepresentable {
+ let onRestore: () -> Void
+
+ func makeUIViewController(context: Context) -> UIViewController {
+ SnitcherViewControllerKt.snitcherViewController(onRestore: onRestore)
+ }
+
+ func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
+}
+```
+
+The bundled controller renders the same Compose screens as the other platforms. Build your own screen instead by observing `Snitcher.exception` and calling `ExceptionTraceScreen` from a `ComposeUIViewController`.
+
+## What is captured, and what is not
+
+| Kind of crash | Captured |
+| --- | --- |
+| Unhandled Kotlin exception, including one crossing into Swift | yes, through `setUnhandledExceptionHook` |
+| Unhandled exception inside a coroutine | yes, and the process terminates as it would without Snitcher |
+| Uncaught `NSException` raised by Swift or Objective-C | yes, through `NSSetUncaughtExceptionHandler` |
+| Swift runtime trap, such as `fatalError` or a nil force unwrap | no, it raises a signal |
+| Memory errors, `SIGSEGV`, `SIGABRT` | no, it raises a signal |
+| Watchdog terminations and out of memory kills | no |
+
+Pass `catchObjectiveCExceptions = false` if another crash reporter owns `NSSetUncaughtExceptionHandler`. Snitcher keeps and calls the handler that was installed before it in either case.
+
+The stack trace is a list of symbol strings rather than structured frames, which is what Kotlin/Native exposes, so `SnitcherException.stackTraceElement` is empty on iOS. Symbol names in a release build need the dSYM, or the `sourceInfoType=libbacktrace` binary option:
+
+```kotlin
+kotlin {
+ targets.withType().configureEach {
+ binaries.all {
+ binaryOption("sourceInfoType", "libbacktrace")
+ }
+ }
+}
+```
+
+## Where the crash is stored
+
+The caches directory of the app by default, which you can change with `storageDirectory`.
diff --git a/docs/trace-global-exception.md b/docs/trace-global-exception.md
index 3ad4df5..07e77a3 100644
--- a/docs/trace-global-exception.md
+++ b/docs/trace-global-exception.md
@@ -1,28 +1,66 @@
# Tracing Global Exceptions
-You can trace the global exceptions by providing `exceptionHandler` lambda parameter. This can be highly beneficial if you intend to gather and report exceptions to other platforms, such as [Firebase Crashlyrics](https://firebase.google.com/docs/crashlytics).
+Every platform installer takes an `exceptionHandler` lambda, which runs with the captured crash before the process goes down. It is useful to report exceptions to another platform, such as [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics).
```kotlin
Snitcher.install(
application = this,
exceptionHandler = { exception: SnitcherException ->
- Firebase.crashlytics.log(exception.stackTrace) // or exception.message,
- }
+ Firebase.crashlytics.log(exception.stackTrace)
+ },
)
```
-The `exceptionHandler` gives you `SnitcherException`, encompassing the exception message, stack traces, package name, and thread information. Additionally, it enables you to recover the original `Throwable` instance with the `SnitcherException.throwable` extension.
+The handler runs on the crashing thread, before the process is killed, so keep it short. Blocking on the network there delays, or prevents, the trace screen.
+
+The handler gives you a `SnitcherException`, which carries the exception name, the message, the whole stack trace, and the thread information.
```kotlin
Snitcher.install(
application = this,
exceptionHandler = { exception: SnitcherException ->
+ val name: String = exception.packageName
val message: String = exception.message
val stackTrace: String = exception.stackTrace
- val throwable: Throwable = exception.throwable
val threadName: String = exception.threadName
- // do somethings
- }
+ // do something
+ },
+)
+```
+
+On Android and on the desktop the original throwable can be restored, which is handy for reporters that take a `Throwable`:
+
+```kotlin
+val throwable: Throwable = exception.toThrowable()
+```
+
+Kotlin/Native does not expose structured stack trace frames, so `toThrowable()` is only available on Android and on the desktop, and `SnitcherException.stackTraceElement` is empty on iOS. The whole trace is always available as a string.
+
+## Chaining with another crash reporter
+
+On Android, Snitcher delegates to the handler that was installed before it, so a reporter keeps working. Two handlers are skipped on purpose: the one from the Android runtime and the one from Firebase Crashlytics, because both kill the process and the trace screen would never appear. Report from `exceptionHandler` instead:
+
+```kotlin
+Snitcher.install(
+ application = this,
+ exceptionHandler = { exception ->
+ Firebase.crashlytics.recordException(exception.toThrowable())
+ },
)
-```
\ No newline at end of file
+```
+
+## Observing the crash anywhere
+
+Snitcher publishes the most recent crash as a `StateFlow`, so any part of your app can observe it, including on the launch that follows a crash.
+
+```kotlin
+val exception: SnitcherException? by Snitcher.exception.collectAsState()
+val launcher: String by Snitcher.launcher.collectAsState()
+```
+
+Once you have handled it, clear it so that it is not displayed again:
+
+```kotlin
+Snitcher.clear()
+```
diff --git a/docs/trace-strategy.md b/docs/trace-strategy.md
index c620aef..26df8a8 100644
--- a/docs/trace-strategy.md
+++ b/docs/trace-strategy.md
@@ -1,5 +1,7 @@
# Trace Strategy
+This one is Android only, since it decides whether the trace Activity is launched after a crash.
+
You can globally trace exceptions by providing the `exceptionHandler` lambda parameter during Snitcher installation. However, there might be instances where you don't wish to launch the trace Activity but rather perform other actions, such as reporting crashes or sending messages to a `BroadcastReceiver`. In such cases, you can modify the trace strategy as shown in the example below:
```kotlin
@@ -12,4 +14,4 @@ Snitcher.install(
)
```
-In this scenario, only the `exceptionHandler` lambda function will be executed without triggering the launch of any trace Activity. If the `traceStrategy` parameter is not specified, the default behavior is set to `TraceStrategy.CO_WORK`, which involves executing the `exceptionHandler` lambda and initiating the trace activity when an app crash occurs.
\ No newline at end of file
+In this scenario, only the `exceptionHandler` lambda function will be executed without triggering the launch of any trace Activity. If the `traceStrategy` parameter is not specified, the default behavior is set to `TraceStrategy.CO_WORK`, which involves executing the `exceptionHandler` lambda and initiating the trace activity when an app crash occurs.
diff --git a/gradle.properties b/gradle.properties
index 909adc4..ec8cfd9 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -31,13 +31,6 @@ 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 d5f6093..d563c2d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -3,15 +3,15 @@ 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"
+composeMultiplatform = "1.12.0"
+okio = "3.18.2"
androidxTest = "1.7.0"
-androidxDataStore = "1.2.1"
coroutines = "1.11.0"
baselineProfiles = "1.4.1"
uiAutomator = "2.4.0"
@@ -24,12 +24,14 @@ 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" }
+compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
+kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
+android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
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" }
@@ -44,11 +46,15 @@ androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref =
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" }
-androidx-datastore = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "androidxDataStore" }
-androidx-datastore-core = { group = "androidx.datastore", name = "datastore-preferences-core", version.ref = "androidxDataStore" }
+okio = { group = "com.squareup.okio", name = "okio", version.ref = "okio" }
+compose-runtime = { group = "org.jetbrains.compose.runtime", name = "runtime", version.ref = "composeMultiplatform" }
+compose-foundation = { group = "org.jetbrains.compose.foundation", name = "foundation", version.ref = "composeMultiplatform" }
+compose-material = { group = "org.jetbrains.compose.material", name = "material", version.ref = "composeMultiplatform" }
+compose-ui = { group = "org.jetbrains.compose.ui", name = "ui", version.ref = "composeMultiplatform" }
+kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "kotlinSerialization" }
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" }
+coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
# unit test
androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTest" }
diff --git a/mkdocs.yml b/mkdocs.yml
index 3fb6e1a..f84090a 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -10,6 +10,9 @@ docs_dir: docs
# Navigation
nav:
- 'Overview': index.md
+ - 'Android': android.md
+ - 'Desktop': desktop.md
+ - 'iOS': ios.md
- 'Trace Global Exception': trace-global-exception.md
- 'Exception Screen': custom-trace-screen.md
- 'Launcher Activity': custom-launcher-activity.md
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 6574cf7..54391a7 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -28,5 +28,6 @@ dependencyResolutionManagement {
}
rootProject.name = "SnitcherDemo"
include(":demo")
+include(":demo-desktop")
include(":snitcher")
include(":benchmark")
diff --git a/snitcher/api/android/snitcher.api b/snitcher/api/android/snitcher.api
new file mode 100644
index 0000000..8b9ecba
--- /dev/null
+++ b/snitcher/api/android/snitcher.api
@@ -0,0 +1,291 @@
+public final class com/skydoves/snitcher/Launcher_androidKt {
+ public static final fun getLauncher (Lcom/skydoves/snitcher/Snitcher;)Lkotlinx/coroutines/flow/StateFlow;
+}
+
+public final class com/skydoves/snitcher/Snitcher {
+ public static final field $stable I
+ public static final field INSTANCE Lcom/skydoves/snitcher/Snitcher;
+ public final fun clear ()V
+ public final fun getException ()Lkotlinx/coroutines/flow/StateFlow;
+ public final fun getLatestException ()Lcom/skydoves/snitcher/model/SnitcherException;
+ public final fun getPlatformInfo ()Ljava/lang/String;
+ public final fun getStrings ()Lcom/skydoves/snitcher/SnitcherStrings;
+ public final fun getTheme ()Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;
+ public final fun isDebuggable ()Z
+ public final fun isInstalled ()Z
+ public final fun setDebuggable (Z)V
+ public final fun setPlatformInfo (Ljava/lang/String;)V
+ public final fun setStrings (Lcom/skydoves/snitcher/SnitcherStrings;)V
+ public final fun setTheme (Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;)V
+}
+
+public final class com/skydoves/snitcher/SnitcherStrings {
+ public static final field $stable I
+ public static final field Companion Lcom/skydoves/snitcher/SnitcherStrings$Companion;
+ public fun ()V
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/lang/String;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun component5 ()Ljava/lang/String;
+ public final fun component6 ()Ljava/lang/String;
+ public final fun component7 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/skydoves/snitcher/SnitcherStrings;
+ public static synthetic fun copy$default (Lcom/skydoves/snitcher/SnitcherStrings;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/skydoves/snitcher/SnitcherStrings;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getRestoreButton ()Ljava/lang/String;
+ public final fun getRestoreDescription ()Ljava/lang/String;
+ public final fun getRestoreTitle ()Ljava/lang/String;
+ public final fun getTraceCopied ()Ljava/lang/String;
+ public final fun getTraceDebugButton ()Ljava/lang/String;
+ public final fun getTraceRestoreButton ()Ljava/lang/String;
+ public final fun getTraceStacktrace ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/skydoves/snitcher/SnitcherStrings$Companion {
+ public final fun defaultStrings ()Lcom/skydoves/snitcher/SnitcherStrings;
+}
+
+public final class com/skydoves/snitcher/Snitcher_androidKt {
+ public static final fun install (Lcom/skydoves/snitcher/Snitcher;Landroid/app/Application;Lkotlin/reflect/KClass;Lcom/skydoves/snitcher/TraceStrategy;Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;Lcom/skydoves/snitcher/SnitcherStrings;Lkotlin/jvm/functions/Function1;)V
+ public static synthetic fun install$default (Lcom/skydoves/snitcher/Snitcher;Landroid/app/Application;Lkotlin/reflect/KClass;Lcom/skydoves/snitcher/TraceStrategy;Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;Lcom/skydoves/snitcher/SnitcherStrings;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
+ public static final fun installInternal (Lcom/skydoves/snitcher/Snitcher;Landroid/app/Application;Ljava/lang/String;Lkotlin/reflect/KClass;Lcom/skydoves/snitcher/TraceStrategy;Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;Lcom/skydoves/snitcher/SnitcherStrings;Lkotlin/jvm/functions/Function1;)V
+}
+
+public final class com/skydoves/snitcher/Snitcher_jvmKt {
+ public static final fun debug (Lcom/skydoves/snitcher/Snitcher;Lcom/skydoves/snitcher/model/SnitcherException;)Ljava/lang/Void;
+}
+
+public final class com/skydoves/snitcher/TraceStrategy : java/lang/Enum {
+ public static final field CO_WORK Lcom/skydoves/snitcher/TraceStrategy;
+ public static final field REPLACE Lcom/skydoves/snitcher/TraceStrategy;
+ public static fun getEntries ()Lkotlin/enums/EnumEntries;
+ public static fun valueOf (Ljava/lang/String;)Lcom/skydoves/snitcher/TraceStrategy;
+ public static fun values ()[Lcom/skydoves/snitcher/TraceStrategy;
+}
+
+public final class com/skydoves/snitcher/model/SnitcherException {
+ public static final field $stable I
+ public static final field Companion Lcom/skydoves/snitcher/model/SnitcherException$Companion;
+ public fun (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V
+ public synthetic fun (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()J
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/lang/String;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun component5 ()Ljava/lang/String;
+ public final fun component6 ()Ljava/util/List;
+ public final fun copy (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lcom/skydoves/snitcher/model/SnitcherException;
+ public static synthetic fun copy$default (Lcom/skydoves/snitcher/model/SnitcherException;JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lcom/skydoves/snitcher/model/SnitcherException;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getMessage ()Ljava/lang/String;
+ public final fun getPackageName ()Ljava/lang/String;
+ public final fun getStackTrace ()Ljava/lang/String;
+ public final fun getStackTraceElement ()Ljava/util/List;
+ public final fun getThreadId ()J
+ public final fun getThreadName ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+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 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 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;
+}
+
+public final class com/skydoves/snitcher/model/SnitcherException$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class com/skydoves/snitcher/model/SnitcherExceptionsKt {
+ public static final fun toThrowable (Lcom/skydoves/snitcher/model/SnitcherException;)Ljava/lang/Throwable;
+}
+
+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 fun (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()I
+ public final fun component3 ()Ljava/lang/String;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)Lcom/skydoves/snitcher/model/SnitcherStackTraceElement;
+ public static synthetic fun copy$default (Lcom/skydoves/snitcher/model/SnitcherStackTraceElement;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/skydoves/snitcher/model/SnitcherStackTraceElement;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getClassName ()Ljava/lang/String;
+ public final fun getFileName ()Ljava/lang/String;
+ public final fun getLineNumber ()I
+ public final fun getMethodName ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+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 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 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;
+}
+
+public final class com/skydoves/snitcher/model/SnitcherStackTraceElement$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class com/skydoves/snitcher/ui/AppRestoreScreenKt {
+ public static final fun AppRestoreScreen (Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V
+}
+
+public final class com/skydoves/snitcher/ui/ComposableSingletons$ExceptionTraceActivityKt {
+ public static final field INSTANCE Lcom/skydoves/snitcher/ui/ComposableSingletons$ExceptionTraceActivityKt;
+ public fun ()V
+ public final fun getLambda$-98219935$SnitcherDemo_snitcher ()Lkotlin/jvm/functions/Function2;
+}
+
+public class com/skydoves/snitcher/ui/ExceptionTraceActivity : androidx/activity/ComponentActivity {
+ public static final field $stable I
+ public fun ()V
+ protected fun onCreate (Landroid/os/Bundle;)V
+}
+
+public final class com/skydoves/snitcher/ui/ExceptionTraceScreenKt {
+ public static final fun ExceptionTraceScreen (Lcom/skydoves/snitcher/model/SnitcherException;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V
+}
+
+public final class com/skydoves/snitcher/ui/SnitcherScreens_androidKt {
+ public static final fun AppRestoreScreen (Ljava/lang/String;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
+ public static final fun ExceptionTraceScreen (Ljava/lang/String;Lcom/skydoves/snitcher/model/SnitcherException;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
+}
+
+public final class com/skydoves/snitcher/ui/theme/SnitcherColor {
+ public static final field $stable I
+ public static final field Companion Lcom/skydoves/snitcher/ui/theme/SnitcherColor$Companion;
+ public static final field UNSPECIFIED J
+ public synthetic fun (JJJJJJILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public synthetic fun (JJJJJJLkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1-0d7_KjU ()J
+ public final fun component2-0d7_KjU ()J
+ public final fun component3-0d7_KjU ()J
+ public final fun component4-0d7_KjU ()J
+ public final fun component5-0d7_KjU ()J
+ public final fun component6-0d7_KjU ()J
+ public final fun copy-tNS2XkQ (JJJJJJ)Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+ public static synthetic fun copy-tNS2XkQ$default (Lcom/skydoves/snitcher/ui/theme/SnitcherColor;JJJJJJILjava/lang/Object;)Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getBackground-0d7_KjU ()J
+ public final fun getOnPrimary-0d7_KjU ()J
+ public final fun getOutline-0d7_KjU ()J
+ public final fun getPrimary-0d7_KjU ()J
+ public final fun getTextHighEmphasis-0d7_KjU ()J
+ public final fun getTextLowEmphasis-0d7_KjU ()J
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/skydoves/snitcher/ui/theme/SnitcherColor$Companion {
+ public final fun defaultColors ()Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+ public final fun defaultDarkColors ()Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+ public final fun fromArgb (JJJJJJ)Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+ public static synthetic fun fromArgb$default (Lcom/skydoves/snitcher/ui/theme/SnitcherColor$Companion;JJJJJJILjava/lang/Object;)Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+}
+
+public final class com/skydoves/snitcher/ui/theme/SnitcherShapes {
+ public static final field $stable I
+ public static final field Companion Lcom/skydoves/snitcher/ui/theme/SnitcherShapes$Companion;
+ public fun ()V
+ public fun (Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;)V
+ public synthetic fun (Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Landroidx/compose/ui/graphics/Shape;
+ public final fun component2 ()Landroidx/compose/ui/graphics/Shape;
+ public final fun copy (Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;)Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;
+ public static synthetic fun copy$default (Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;ILjava/lang/Object;)Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getButton ()Landroidx/compose/ui/graphics/Shape;
+ public final fun getStacktrace ()Landroidx/compose/ui/graphics/Shape;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/skydoves/snitcher/ui/theme/SnitcherShapes$Companion {
+ public final fun defaultShapes ()Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;
+}
+
+public final class com/skydoves/snitcher/ui/theme/SnitcherTheme {
+ public static final field $stable I
+ public static final field INSTANCE Lcom/skydoves/snitcher/ui/theme/SnitcherTheme;
+ public final fun getColors (Landroidx/compose/runtime/Composer;I)Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+ public final fun getShapes (Landroidx/compose/runtime/Composer;I)Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;
+ public final fun getTypography (Landroidx/compose/runtime/Composer;I)Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;
+}
+
+public final class com/skydoves/snitcher/ui/theme/SnitcherThemeConfig {
+ public static final field $stable I
+ public static final field Companion Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig$Companion;
+ public fun ()V
+ public fun (Lcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;)V
+ public synthetic fun (Lcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+ public final fun component2 ()Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+ public final fun component3 ()Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;
+ public final fun component4 ()Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;
+ public final fun copy (Lcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;)Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;
+ public static synthetic fun copy$default (Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;Lcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;ILjava/lang/Object;)Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getDarkColors ()Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+ public final fun getLightColors ()Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+ public final fun getShapes ()Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;
+ public final fun getTypography ()Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/skydoves/snitcher/ui/theme/SnitcherThemeConfig$Companion {
+ public final fun defaultThemeConfig ()Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;
+}
+
+public final class com/skydoves/snitcher/ui/theme/SnitcherThemeKt {
+ public static final fun SnitcherTheme (ZLcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
+}
+
+public final class com/skydoves/snitcher/ui/theme/SnitcherTypography {
+ public static final field $stable I
+ public static final field Companion Lcom/skydoves/snitcher/ui/theme/SnitcherTypography$Companion;
+ public fun (Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;)V
+ public final fun component1 ()Landroidx/compose/ui/text/TextStyle;
+ public final fun component2 ()Landroidx/compose/ui/text/TextStyle;
+ public final fun component3 ()Landroidx/compose/ui/text/TextStyle;
+ public final fun component4 ()Landroidx/compose/ui/text/TextStyle;
+ public final fun component5 ()Landroidx/compose/ui/text/TextStyle;
+ public final fun component6 ()Landroidx/compose/ui/text/TextStyle;
+ public final fun copy (Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;)Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;
+ public static synthetic fun copy$default (Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/ui/text/TextStyle;ILjava/lang/Object;)Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getButton ()Landroidx/compose/ui/text/TextStyle;
+ public final fun getDeviceInfo ()Landroidx/compose/ui/text/TextStyle;
+ public final fun getMessage ()Landroidx/compose/ui/text/TextStyle;
+ public final fun getSectionLabel ()Landroidx/compose/ui/text/TextStyle;
+ public final fun getStacktrace ()Landroidx/compose/ui/text/TextStyle;
+ public final fun getTitle ()Landroidx/compose/ui/text/TextStyle;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class com/skydoves/snitcher/ui/theme/SnitcherTypography$Companion {
+ public final fun defaultTypography ()Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;
+}
+
diff --git a/snitcher/api/snitcher.api b/snitcher/api/desktop/snitcher.api
similarity index 73%
rename from snitcher/api/snitcher.api
rename to snitcher/api/desktop/snitcher.api
index f350aee..8329aa2 100644
--- a/snitcher/api/snitcher.api
+++ b/snitcher/api/desktop/snitcher.api
@@ -1,44 +1,65 @@
-public final class com/skydoves/snitcher/Snitcher : java/lang/Thread$UncaughtExceptionHandler {
+public final class com/skydoves/snitcher/Snitcher {
public static final field $stable I
- public static final field Companion Lcom/skydoves/snitcher/Snitcher$Companion;
- public fun (Landroid/app/Application;Ljava/lang/String;Lkotlin/reflect/KClass;Lcom/skydoves/snitcher/TraceStrategy;Ljava/lang/Thread$UncaughtExceptionHandler;Lkotlin/jvm/functions/Function2;)V
- public fun uncaughtException (Ljava/lang/Thread;Ljava/lang/Throwable;)V
-}
-
-public final class com/skydoves/snitcher/Snitcher$Companion {
+ public static final field INSTANCE Lcom/skydoves/snitcher/Snitcher;
+ public final fun clear ()V
public final fun getException ()Lkotlinx/coroutines/flow/StateFlow;
- public final fun getLauncher ()Lkotlinx/coroutines/flow/StateFlow;
+ public final fun getLatestException ()Lcom/skydoves/snitcher/model/SnitcherException;
+ public final fun getPlatformInfo ()Ljava/lang/String;
+ public final fun getStrings ()Lcom/skydoves/snitcher/SnitcherStrings;
public final fun getTheme ()Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;
- public final fun install (Landroid/app/Application;Lkotlin/reflect/KClass;Lcom/skydoves/snitcher/TraceStrategy;Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;Lkotlin/jvm/functions/Function1;)V
- public static synthetic fun install$default (Lcom/skydoves/snitcher/Snitcher$Companion;Landroid/app/Application;Lkotlin/reflect/KClass;Lcom/skydoves/snitcher/TraceStrategy;Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public final fun isDebuggable ()Z
- public final fun isDebuggableApp (Landroid/app/Application;)Z
public final fun isInstalled ()Z
public final fun setDebuggable (Z)V
+ public final fun setPlatformInfo (Ljava/lang/String;)V
+ public final fun setStrings (Lcom/skydoves/snitcher/SnitcherStrings;)V
public final fun setTheme (Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;)V
}
-public final class com/skydoves/snitcher/TraceStrategy : java/lang/Enum {
- public static final field CO_WORK Lcom/skydoves/snitcher/TraceStrategy;
- public static final field REPLACE Lcom/skydoves/snitcher/TraceStrategy;
- public static fun getEntries ()Lkotlin/enums/EnumEntries;
- public static fun valueOf (Ljava/lang/String;)Lcom/skydoves/snitcher/TraceStrategy;
- public static fun values ()[Lcom/skydoves/snitcher/TraceStrategy;
+public final class com/skydoves/snitcher/SnitcherStrings {
+ public static final field $stable I
+ public static final field Companion Lcom/skydoves/snitcher/SnitcherStrings$Companion;
+ public fun ()V
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/lang/String;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun component5 ()Ljava/lang/String;
+ public final fun component6 ()Ljava/lang/String;
+ public final fun component7 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/skydoves/snitcher/SnitcherStrings;
+ public static synthetic fun copy$default (Lcom/skydoves/snitcher/SnitcherStrings;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/skydoves/snitcher/SnitcherStrings;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getRestoreButton ()Ljava/lang/String;
+ public final fun getRestoreDescription ()Ljava/lang/String;
+ public final fun getRestoreTitle ()Ljava/lang/String;
+ public final fun getTraceCopied ()Ljava/lang/String;
+ public final fun getTraceDebugButton ()Ljava/lang/String;
+ public final fun getTraceRestoreButton ()Ljava/lang/String;
+ public final fun getTraceStacktrace ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
}
-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
- public static final fun isInstalled ()Z
- public final fun setInternalSnitcher (Lkotlinx/coroutines/flow/MutableStateFlow;)V
+public final class com/skydoves/snitcher/SnitcherStrings$Companion {
+ public final fun defaultStrings ()Lcom/skydoves/snitcher/SnitcherStrings;
+}
+
+public final class com/skydoves/snitcher/Snitcher_desktopKt {
+ public static final fun install (Lcom/skydoves/snitcher/Snitcher;Ljava/lang/String;Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;Lcom/skydoves/snitcher/SnitcherStrings;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)V
+ public static synthetic fun install$default (Lcom/skydoves/snitcher/Snitcher;Ljava/lang/String;Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;Lcom/skydoves/snitcher/SnitcherStrings;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
}
-public final class com/skydoves/snitcher/model/SnitcherException : java/io/Serializable {
+public final class com/skydoves/snitcher/Snitcher_jvmKt {
+ public static final fun debug (Lcom/skydoves/snitcher/Snitcher;Lcom/skydoves/snitcher/model/SnitcherException;)Ljava/lang/Void;
+}
+
+public final class com/skydoves/snitcher/model/SnitcherException {
public static final field $stable I
public static final field Companion Lcom/skydoves/snitcher/model/SnitcherException$Companion;
public fun (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V
+ public synthetic fun (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()J
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
@@ -54,7 +75,6 @@ public final class com/skydoves/snitcher/model/SnitcherException : java/io/Seria
public final fun getStackTraceElement ()Ljava/util/List;
public final fun getThreadId ()J
public final fun getThreadName ()Ljava/lang/String;
- public final fun getThrowable ()Ljava/lang/Throwable;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
@@ -75,6 +95,10 @@ public final class com/skydoves/snitcher/model/SnitcherException$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
+public final class com/skydoves/snitcher/model/SnitcherExceptionsKt {
+ public static final fun toThrowable (Lcom/skydoves/snitcher/model/SnitcherException;)Ljava/lang/Throwable;
+}
+
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;
@@ -111,28 +135,21 @@ public final class com/skydoves/snitcher/model/SnitcherStackTraceElement$Compani
}
public final class com/skydoves/snitcher/ui/AppRestoreScreenKt {
- public static final fun AppRestoreScreen (Ljava/lang/String;Landroidx/compose/runtime/Composer;I)V
+ public static final fun AppRestoreScreen (Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V
}
-public final class com/skydoves/snitcher/ui/ComposableSingletons$ExceptionTraceActivityKt {
- public static final field INSTANCE Lcom/skydoves/snitcher/ui/ComposableSingletons$ExceptionTraceActivityKt;
- public fun ()V
- public final fun getLambda$-98219935$SnitcherDemo_snitcher_release ()Lkotlin/jvm/functions/Function2;
-}
-
-public class com/skydoves/snitcher/ui/ExceptionTraceActivity : androidx/activity/ComponentActivity {
- public static final field $stable I
- public fun ()V
- protected fun onCreate (Landroid/os/Bundle;)V
+public final class com/skydoves/snitcher/ui/ExceptionTraceScreenKt {
+ public static final fun ExceptionTraceScreen (Lcom/skydoves/snitcher/model/SnitcherException;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;II)V
}
-public final class com/skydoves/snitcher/ui/ExceptionTraceScreenKt {
- public static final fun ExceptionTraceScreen (Ljava/lang/String;Lcom/skydoves/snitcher/model/SnitcherException;Landroidx/compose/runtime/Composer;I)V
+public final class com/skydoves/snitcher/ui/SnitcherTraceWindowKt {
+ public static final fun SnitcherTraceWindow (Landroidx/compose/ui/window/ApplicationScope;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Landroidx/compose/runtime/Composer;II)V
}
public final class com/skydoves/snitcher/ui/theme/SnitcherColor {
public static final field $stable I
public static final field Companion Lcom/skydoves/snitcher/ui/theme/SnitcherColor$Companion;
+ public static final field UNSPECIFIED J
public synthetic fun (JJJJJJILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun (JJJJJJLkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1-0d7_KjU ()J
@@ -157,10 +174,13 @@ public final class com/skydoves/snitcher/ui/theme/SnitcherColor {
public final class com/skydoves/snitcher/ui/theme/SnitcherColor$Companion {
public final fun defaultColors ()Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
public final fun defaultDarkColors ()Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+ public final fun fromArgb (JJJJJJ)Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
+ public static synthetic fun fromArgb$default (Lcom/skydoves/snitcher/ui/theme/SnitcherColor$Companion;JJJJJJILjava/lang/Object;)Lcom/skydoves/snitcher/ui/theme/SnitcherColor;
}
public final class com/skydoves/snitcher/ui/theme/SnitcherShapes {
public static final field $stable I
+ public static final field Companion Lcom/skydoves/snitcher/ui/theme/SnitcherShapes$Companion;
public fun ()V
public fun (Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;)V
public synthetic fun (Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -175,6 +195,10 @@ public final class com/skydoves/snitcher/ui/theme/SnitcherShapes {
public fun toString ()Ljava/lang/String;
}
+public final class com/skydoves/snitcher/ui/theme/SnitcherShapes$Companion {
+ public final fun defaultShapes ()Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;
+}
+
public final class com/skydoves/snitcher/ui/theme/SnitcherTheme {
public static final field $stable I
public static final field INSTANCE Lcom/skydoves/snitcher/ui/theme/SnitcherTheme;
@@ -185,6 +209,7 @@ public final class com/skydoves/snitcher/ui/theme/SnitcherTheme {
public final class com/skydoves/snitcher/ui/theme/SnitcherThemeConfig {
public static final field $stable I
+ public static final field Companion Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig$Companion;
public fun ()V
public fun (Lcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;)V
public synthetic fun (Lcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -203,6 +228,10 @@ public final class com/skydoves/snitcher/ui/theme/SnitcherThemeConfig {
public fun toString ()Ljava/lang/String;
}
+public final class com/skydoves/snitcher/ui/theme/SnitcherThemeConfig$Companion {
+ public final fun defaultThemeConfig ()Lcom/skydoves/snitcher/ui/theme/SnitcherThemeConfig;
+}
+
public final class com/skydoves/snitcher/ui/theme/SnitcherThemeKt {
public static final fun SnitcherTheme (ZLcom/skydoves/snitcher/ui/theme/SnitcherColor;Lcom/skydoves/snitcher/ui/theme/SnitcherTypography;Lcom/skydoves/snitcher/ui/theme/SnitcherShapes;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
}
diff --git a/snitcher/api/snitcher.klib.api b/snitcher/api/snitcher.klib.api
new file mode 100644
index 0000000..ef2a6e5
--- /dev/null
+++ b/snitcher/api/snitcher.klib.api
@@ -0,0 +1,300 @@
+// Klib ABI Dump
+// Targets: [iosArm64, iosSimulatorArm64]
+// Rendering settings:
+// - Signature version: 2
+// - Show manifest properties: true
+// - Show declarations: true
+
+// Library unique name:
+final class com.skydoves.snitcher.model/SnitcherException { // com.skydoves.snitcher.model/SnitcherException|null[0]
+ constructor (kotlin/Long, kotlin/String, kotlin/String, kotlin/String, kotlin/String, kotlin.collections/List = ...) // com.skydoves.snitcher.model/SnitcherException.|(kotlin.Long;kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.collections.List){}[0]
+
+ final val message // com.skydoves.snitcher.model/SnitcherException.message|{}message[0]
+ final fun (): kotlin/String // com.skydoves.snitcher.model/SnitcherException.message.|(){}[0]
+ final val packageName // com.skydoves.snitcher.model/SnitcherException.packageName|{}packageName[0]
+ final fun (): kotlin/String // com.skydoves.snitcher.model/SnitcherException.packageName.|(){}[0]
+ final val stackTrace // com.skydoves.snitcher.model/SnitcherException.stackTrace|{}stackTrace[0]
+ final fun (): kotlin/String // com.skydoves.snitcher.model/SnitcherException.stackTrace.|(){}[0]
+ final val stackTraceElement // com.skydoves.snitcher.model/SnitcherException.stackTraceElement|{}stackTraceElement[0]
+ final fun (): kotlin.collections/List // com.skydoves.snitcher.model/SnitcherException.stackTraceElement.|(){}[0]
+ final val threadId // com.skydoves.snitcher.model/SnitcherException.threadId|{}threadId[0]
+ final fun (): kotlin/Long // com.skydoves.snitcher.model/SnitcherException.threadId.|(){}[0]
+ final val threadName // com.skydoves.snitcher.model/SnitcherException.threadName|{}threadName[0]
+ final fun (): kotlin/String // com.skydoves.snitcher.model/SnitcherException.threadName.|(){}[0]
+
+ final fun component1(): kotlin/Long // com.skydoves.snitcher.model/SnitcherException.component1|component1(){}[0]
+ final fun component2(): kotlin/String // com.skydoves.snitcher.model/SnitcherException.component2|component2(){}[0]
+ final fun component3(): kotlin/String // com.skydoves.snitcher.model/SnitcherException.component3|component3(){}[0]
+ final fun component4(): kotlin/String // com.skydoves.snitcher.model/SnitcherException.component4|component4(){}[0]
+ final fun component5(): kotlin/String // com.skydoves.snitcher.model/SnitcherException.component5|component5(){}[0]
+ final fun component6(): kotlin.collections/List // com.skydoves.snitcher.model/SnitcherException.component6|component6(){}[0]
+ final fun copy(kotlin/Long = ..., kotlin/String = ..., kotlin/String = ..., kotlin/String = ..., kotlin/String = ..., kotlin.collections/List = ...): com.skydoves.snitcher.model/SnitcherException // com.skydoves.snitcher.model/SnitcherException.copy|copy(kotlin.Long;kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.collections.List){}[0]
+ final fun equals(kotlin/Any?): kotlin/Boolean // com.skydoves.snitcher.model/SnitcherException.equals|equals(kotlin.Any?){}[0]
+ final fun hashCode(): kotlin/Int // com.skydoves.snitcher.model/SnitcherException.hashCode|hashCode(){}[0]
+ final fun toString(): kotlin/String // com.skydoves.snitcher.model/SnitcherException.toString|toString(){}[0]
+
+ final object $serializer : kotlinx.serialization.internal/GeneratedSerializer { // com.skydoves.snitcher.model/SnitcherException.$serializer|null[0]
+ final val descriptor // com.skydoves.snitcher.model/SnitcherException.$serializer.descriptor|{}descriptor[0]
+ final fun (): kotlinx.serialization.descriptors/SerialDescriptor // com.skydoves.snitcher.model/SnitcherException.$serializer.descriptor.|(){}[0]
+
+ final fun childSerializers(): kotlin/Array> // com.skydoves.snitcher.model/SnitcherException.$serializer.childSerializers|childSerializers(){}[0]
+ final fun deserialize(kotlinx.serialization.encoding/Decoder): com.skydoves.snitcher.model/SnitcherException // com.skydoves.snitcher.model/SnitcherException.$serializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0]
+ final fun serialize(kotlinx.serialization.encoding/Encoder, com.skydoves.snitcher.model/SnitcherException) // com.skydoves.snitcher.model/SnitcherException.$serializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;com.skydoves.snitcher.model.SnitcherException){}[0]
+ }
+
+ final object Companion { // com.skydoves.snitcher.model/SnitcherException.Companion|null[0]
+ final val $childSerializers // com.skydoves.snitcher.model/SnitcherException.Companion.$childSerializers|{}$childSerializers[0]
+
+ final fun serializer(): kotlinx.serialization/KSerializer // com.skydoves.snitcher.model/SnitcherException.Companion.serializer|serializer(){}[0]
+ }
+}
+
+final class com.skydoves.snitcher.model/SnitcherStackTraceElement { // com.skydoves.snitcher.model/SnitcherStackTraceElement|null[0]
+ constructor (kotlin/String, kotlin/Int, kotlin/String, kotlin/String) // com.skydoves.snitcher.model/SnitcherStackTraceElement.|(kotlin.String;kotlin.Int;kotlin.String;kotlin.String){}[0]
+
+ final val className // com.skydoves.snitcher.model/SnitcherStackTraceElement.className|{}className[0]
+ final fun (): kotlin/String // com.skydoves.snitcher.model/SnitcherStackTraceElement.className.|(){}[0]
+ final val fileName // com.skydoves.snitcher.model/SnitcherStackTraceElement.fileName|{}fileName[0]
+ final fun (): kotlin/String // com.skydoves.snitcher.model/SnitcherStackTraceElement.fileName.|(){}[0]
+ final val lineNumber // com.skydoves.snitcher.model/SnitcherStackTraceElement.lineNumber|{}lineNumber[0]
+ final fun (): kotlin/Int // com.skydoves.snitcher.model/SnitcherStackTraceElement.lineNumber.|(){}[0]
+ final val methodName // com.skydoves.snitcher.model/SnitcherStackTraceElement.methodName|{}methodName[0]
+ final fun (): kotlin/String // com.skydoves.snitcher.model/SnitcherStackTraceElement.methodName.|(){}[0]
+
+ final fun component1(): kotlin/String // com.skydoves.snitcher.model/SnitcherStackTraceElement.component1|component1(){}[0]
+ final fun component2(): kotlin/Int // com.skydoves.snitcher.model/SnitcherStackTraceElement.component2|component2(){}[0]
+ final fun component3(): kotlin/String // com.skydoves.snitcher.model/SnitcherStackTraceElement.component3|component3(){}[0]
+ final fun component4(): kotlin/String // com.skydoves.snitcher.model/SnitcherStackTraceElement.component4|component4(){}[0]
+ final fun copy(kotlin/String = ..., kotlin/Int = ..., kotlin/String = ..., kotlin/String = ...): com.skydoves.snitcher.model/SnitcherStackTraceElement // com.skydoves.snitcher.model/SnitcherStackTraceElement.copy|copy(kotlin.String;kotlin.Int;kotlin.String;kotlin.String){}[0]
+ final fun equals(kotlin/Any?): kotlin/Boolean // com.skydoves.snitcher.model/SnitcherStackTraceElement.equals|equals(kotlin.Any?){}[0]
+ final fun hashCode(): kotlin/Int // com.skydoves.snitcher.model/SnitcherStackTraceElement.hashCode|hashCode(){}[0]
+ final fun toString(): kotlin/String // com.skydoves.snitcher.model/SnitcherStackTraceElement.toString|toString(){}[0]
+
+ final object $serializer : kotlinx.serialization.internal/GeneratedSerializer { // com.skydoves.snitcher.model/SnitcherStackTraceElement.$serializer|null[0]
+ final val descriptor // com.skydoves.snitcher.model/SnitcherStackTraceElement.$serializer.descriptor|{}descriptor[0]
+ final fun (): kotlinx.serialization.descriptors/SerialDescriptor // com.skydoves.snitcher.model/SnitcherStackTraceElement.$serializer.descriptor.|(){}[0]
+
+ final fun childSerializers(): kotlin/Array> // com.skydoves.snitcher.model/SnitcherStackTraceElement.$serializer.childSerializers|childSerializers(){}[0]
+ final fun deserialize(kotlinx.serialization.encoding/Decoder): com.skydoves.snitcher.model/SnitcherStackTraceElement // com.skydoves.snitcher.model/SnitcherStackTraceElement.$serializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0]
+ final fun serialize(kotlinx.serialization.encoding/Encoder, com.skydoves.snitcher.model/SnitcherStackTraceElement) // com.skydoves.snitcher.model/SnitcherStackTraceElement.$serializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;com.skydoves.snitcher.model.SnitcherStackTraceElement){}[0]
+ }
+
+ final object Companion { // com.skydoves.snitcher.model/SnitcherStackTraceElement.Companion|null[0]
+ final fun serializer(): kotlinx.serialization/KSerializer // com.skydoves.snitcher.model/SnitcherStackTraceElement.Companion.serializer|serializer(){}[0]
+ }
+}
+
+final class com.skydoves.snitcher.ui.theme/SnitcherColor { // com.skydoves.snitcher.ui.theme/SnitcherColor|null[0]
+ constructor (androidx.compose.ui.graphics/Color, androidx.compose.ui.graphics/Color, androidx.compose.ui.graphics/Color, androidx.compose.ui.graphics/Color = ..., androidx.compose.ui.graphics/Color = ..., androidx.compose.ui.graphics/Color = ...) // com.skydoves.snitcher.ui.theme/SnitcherColor.|(androidx.compose.ui.graphics.Color;androidx.compose.ui.graphics.Color;androidx.compose.ui.graphics.Color;androidx.compose.ui.graphics.Color;androidx.compose.ui.graphics.Color;androidx.compose.ui.graphics.Color){}[0]
+
+ final val background // com.skydoves.snitcher.ui.theme/SnitcherColor.background|{}background[0]
+ final fun (): androidx.compose.ui.graphics/Color // com.skydoves.snitcher.ui.theme/SnitcherColor.background.|(){}[0]
+ final val onPrimary // com.skydoves.snitcher.ui.theme/SnitcherColor.onPrimary|{}onPrimary[0]
+ final fun (): androidx.compose.ui.graphics/Color // com.skydoves.snitcher.ui.theme/SnitcherColor.onPrimary.|(){}[0]
+ final val outline // com.skydoves.snitcher.ui.theme/SnitcherColor.outline|{}outline[0]
+ final fun (): androidx.compose.ui.graphics/Color // com.skydoves.snitcher.ui.theme/SnitcherColor.outline.|(){}[0]
+ final val primary // com.skydoves.snitcher.ui.theme/SnitcherColor.primary|{}primary[0]
+ final fun (): androidx.compose.ui.graphics/Color // com.skydoves.snitcher.ui.theme/SnitcherColor.primary.|(){}[0]
+ final val textHighEmphasis // com.skydoves.snitcher.ui.theme/SnitcherColor.textHighEmphasis|{}textHighEmphasis[0]
+ final fun (): androidx.compose.ui.graphics/Color // com.skydoves.snitcher.ui.theme/SnitcherColor.textHighEmphasis.|(){}[0]
+ final val textLowEmphasis // com.skydoves.snitcher.ui.theme/SnitcherColor.textLowEmphasis|{}textLowEmphasis[0]
+ final fun (): androidx.compose.ui.graphics/Color // com.skydoves.snitcher.ui.theme/SnitcherColor.textLowEmphasis.|(){}[0]
+
+ final fun component1(): androidx.compose.ui.graphics/Color // com.skydoves.snitcher.ui.theme/SnitcherColor.component1|component1(){}[0]
+ final fun component2(): androidx.compose.ui.graphics/Color // com.skydoves.snitcher.ui.theme/SnitcherColor.component2|component2(){}[0]
+ final fun component3(): androidx.compose.ui.graphics/Color // com.skydoves.snitcher.ui.theme/SnitcherColor.component3|component3(){}[0]
+ final fun component4(): androidx.compose.ui.graphics/Color // com.skydoves.snitcher.ui.theme/SnitcherColor.component4|component4(){}[0]
+ final fun component5(): androidx.compose.ui.graphics/Color // com.skydoves.snitcher.ui.theme/SnitcherColor.component5|component5(){}[0]
+ final fun component6(): androidx.compose.ui.graphics/Color // com.skydoves.snitcher.ui.theme/SnitcherColor.component6|component6(){}[0]
+ final fun copy(androidx.compose.ui.graphics/Color = ..., androidx.compose.ui.graphics/Color = ..., androidx.compose.ui.graphics/Color = ..., androidx.compose.ui.graphics/Color = ..., androidx.compose.ui.graphics/Color = ..., androidx.compose.ui.graphics/Color = ...): com.skydoves.snitcher.ui.theme/SnitcherColor // com.skydoves.snitcher.ui.theme/SnitcherColor.copy|copy(androidx.compose.ui.graphics.Color;androidx.compose.ui.graphics.Color;androidx.compose.ui.graphics.Color;androidx.compose.ui.graphics.Color;androidx.compose.ui.graphics.Color;androidx.compose.ui.graphics.Color){}[0]
+ final fun equals(kotlin/Any?): kotlin/Boolean // com.skydoves.snitcher.ui.theme/SnitcherColor.equals|equals(kotlin.Any?){}[0]
+ final fun hashCode(): kotlin/Int // com.skydoves.snitcher.ui.theme/SnitcherColor.hashCode|hashCode(){}[0]
+ final fun toString(): kotlin/String // com.skydoves.snitcher.ui.theme/SnitcherColor.toString|toString(){}[0]
+
+ final object Companion { // com.skydoves.snitcher.ui.theme/SnitcherColor.Companion|null[0]
+ final const val UNSPECIFIED // com.skydoves.snitcher.ui.theme/SnitcherColor.Companion.UNSPECIFIED|{}UNSPECIFIED[0]
+ final fun (): kotlin/Long // com.skydoves.snitcher.ui.theme/SnitcherColor.Companion.UNSPECIFIED.|(){}[0]
+
+ final fun defaultColors(): com.skydoves.snitcher.ui.theme/SnitcherColor // com.skydoves.snitcher.ui.theme/SnitcherColor.Companion.defaultColors|defaultColors(){}[0]
+ final fun defaultDarkColors(): com.skydoves.snitcher.ui.theme/SnitcherColor // com.skydoves.snitcher.ui.theme/SnitcherColor.Companion.defaultDarkColors|defaultDarkColors(){}[0]
+ final fun fromArgb(kotlin/Long, kotlin/Long, kotlin/Long, kotlin/Long = ..., kotlin/Long = ..., kotlin/Long = ...): com.skydoves.snitcher.ui.theme/SnitcherColor // com.skydoves.snitcher.ui.theme/SnitcherColor.Companion.fromArgb|fromArgb(kotlin.Long;kotlin.Long;kotlin.Long;kotlin.Long;kotlin.Long;kotlin.Long){}[0]
+ }
+}
+
+final class com.skydoves.snitcher.ui.theme/SnitcherShapes { // com.skydoves.snitcher.ui.theme/SnitcherShapes|null[0]
+ constructor (androidx.compose.ui.graphics/Shape = ..., androidx.compose.ui.graphics/Shape = ...) // com.skydoves.snitcher.ui.theme/SnitcherShapes.|(androidx.compose.ui.graphics.Shape;androidx.compose.ui.graphics.Shape){}[0]
+
+ final val button // com.skydoves.snitcher.ui.theme/SnitcherShapes.button|{}button[0]
+ final fun (): androidx.compose.ui.graphics/Shape // com.skydoves.snitcher.ui.theme/SnitcherShapes.button.|(){}[0]
+ final val stacktrace // com.skydoves.snitcher.ui.theme/SnitcherShapes.stacktrace|{}stacktrace[0]
+ final fun (): androidx.compose.ui.graphics/Shape // com.skydoves.snitcher.ui.theme/SnitcherShapes.stacktrace.|(){}[0]
+
+ final fun component1(): androidx.compose.ui.graphics/Shape // com.skydoves.snitcher.ui.theme/SnitcherShapes.component1|component1(){}[0]
+ final fun component2(): androidx.compose.ui.graphics/Shape // com.skydoves.snitcher.ui.theme/SnitcherShapes.component2|component2(){}[0]
+ final fun copy(androidx.compose.ui.graphics/Shape = ..., androidx.compose.ui.graphics/Shape = ...): com.skydoves.snitcher.ui.theme/SnitcherShapes // com.skydoves.snitcher.ui.theme/SnitcherShapes.copy|copy(androidx.compose.ui.graphics.Shape;androidx.compose.ui.graphics.Shape){}[0]
+ final fun equals(kotlin/Any?): kotlin/Boolean // com.skydoves.snitcher.ui.theme/SnitcherShapes.equals|equals(kotlin.Any?){}[0]
+ final fun hashCode(): kotlin/Int // com.skydoves.snitcher.ui.theme/SnitcherShapes.hashCode|hashCode(){}[0]
+ final fun toString(): kotlin/String // com.skydoves.snitcher.ui.theme/SnitcherShapes.toString|toString(){}[0]
+
+ final object Companion { // com.skydoves.snitcher.ui.theme/SnitcherShapes.Companion|null[0]
+ final fun defaultShapes(): com.skydoves.snitcher.ui.theme/SnitcherShapes // com.skydoves.snitcher.ui.theme/SnitcherShapes.Companion.defaultShapes|defaultShapes(){}[0]
+ }
+}
+
+final class com.skydoves.snitcher.ui.theme/SnitcherThemeConfig { // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig|null[0]
+ constructor (com.skydoves.snitcher.ui.theme/SnitcherColor = ..., com.skydoves.snitcher.ui.theme/SnitcherColor = ..., com.skydoves.snitcher.ui.theme/SnitcherTypography = ..., com.skydoves.snitcher.ui.theme/SnitcherShapes = ...) // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.|(com.skydoves.snitcher.ui.theme.SnitcherColor;com.skydoves.snitcher.ui.theme.SnitcherColor;com.skydoves.snitcher.ui.theme.SnitcherTypography;com.skydoves.snitcher.ui.theme.SnitcherShapes){}[0]
+
+ final val darkColors // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.darkColors|{}darkColors[0]
+ final fun (): com.skydoves.snitcher.ui.theme/SnitcherColor // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.darkColors.|(){}[0]
+ final val lightColors // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.lightColors|{}lightColors[0]
+ final fun (): com.skydoves.snitcher.ui.theme/SnitcherColor // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.lightColors.|(){}[0]
+ final val shapes // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.shapes|{}shapes[0]
+ final fun (): com.skydoves.snitcher.ui.theme/SnitcherShapes // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.shapes.|(){}[0]
+ final val typography // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.typography|{}typography[0]
+ final fun (): com.skydoves.snitcher.ui.theme/SnitcherTypography // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.typography.|(){}[0]
+
+ final fun component1(): com.skydoves.snitcher.ui.theme/SnitcherColor // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.component1|component1(){}[0]
+ final fun component2(): com.skydoves.snitcher.ui.theme/SnitcherColor // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.component2|component2(){}[0]
+ final fun component3(): com.skydoves.snitcher.ui.theme/SnitcherTypography // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.component3|component3(){}[0]
+ final fun component4(): com.skydoves.snitcher.ui.theme/SnitcherShapes // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.component4|component4(){}[0]
+ final fun copy(com.skydoves.snitcher.ui.theme/SnitcherColor = ..., com.skydoves.snitcher.ui.theme/SnitcherColor = ..., com.skydoves.snitcher.ui.theme/SnitcherTypography = ..., com.skydoves.snitcher.ui.theme/SnitcherShapes = ...): com.skydoves.snitcher.ui.theme/SnitcherThemeConfig // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.copy|copy(com.skydoves.snitcher.ui.theme.SnitcherColor;com.skydoves.snitcher.ui.theme.SnitcherColor;com.skydoves.snitcher.ui.theme.SnitcherTypography;com.skydoves.snitcher.ui.theme.SnitcherShapes){}[0]
+ final fun equals(kotlin/Any?): kotlin/Boolean // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.equals|equals(kotlin.Any?){}[0]
+ final fun hashCode(): kotlin/Int // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.hashCode|hashCode(){}[0]
+ final fun toString(): kotlin/String // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.toString|toString(){}[0]
+
+ final object Companion { // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.Companion|null[0]
+ final fun defaultThemeConfig(): com.skydoves.snitcher.ui.theme/SnitcherThemeConfig // com.skydoves.snitcher.ui.theme/SnitcherThemeConfig.Companion.defaultThemeConfig|defaultThemeConfig(){}[0]
+ }
+}
+
+final class com.skydoves.snitcher.ui.theme/SnitcherTypography { // com.skydoves.snitcher.ui.theme/SnitcherTypography|null[0]
+ constructor (androidx.compose.ui.text/TextStyle, androidx.compose.ui.text/TextStyle, androidx.compose.ui.text/TextStyle, androidx.compose.ui.text/TextStyle, androidx.compose.ui.text/TextStyle, androidx.compose.ui.text/TextStyle) // com.skydoves.snitcher.ui.theme/SnitcherTypography.|(androidx.compose.ui.text.TextStyle;androidx.compose.ui.text.TextStyle;androidx.compose.ui.text.TextStyle;androidx.compose.ui.text.TextStyle;androidx.compose.ui.text.TextStyle;androidx.compose.ui.text.TextStyle){}[0]
+
+ final val button // com.skydoves.snitcher.ui.theme/SnitcherTypography.button|{}button[0]
+ final fun (): androidx.compose.ui.text/TextStyle // com.skydoves.snitcher.ui.theme/SnitcherTypography.button.|(){}[0]
+ final val deviceInfo // com.skydoves.snitcher.ui.theme/SnitcherTypography.deviceInfo|{}deviceInfo[0]
+ final fun (): androidx.compose.ui.text/TextStyle // com.skydoves.snitcher.ui.theme/SnitcherTypography.deviceInfo.|(){}[0]
+ final val message // com.skydoves.snitcher.ui.theme/SnitcherTypography.message|{}message[0]
+ final fun (): androidx.compose.ui.text/TextStyle // com.skydoves.snitcher.ui.theme/SnitcherTypography.message.|(){}[0]
+ final val sectionLabel // com.skydoves.snitcher.ui.theme/SnitcherTypography.sectionLabel|{}sectionLabel[0]
+ final fun (): androidx.compose.ui.text/TextStyle // com.skydoves.snitcher.ui.theme/SnitcherTypography.sectionLabel.|(){}[0]
+ final val stacktrace // com.skydoves.snitcher.ui.theme/SnitcherTypography.stacktrace|{}stacktrace[0]
+ final fun (): androidx.compose.ui.text/TextStyle // com.skydoves.snitcher.ui.theme/SnitcherTypography.stacktrace.|(){}[0]
+ final val title // com.skydoves.snitcher.ui.theme/SnitcherTypography.title|{}title[0]
+ final fun (): androidx.compose.ui.text/TextStyle // com.skydoves.snitcher.ui.theme/SnitcherTypography.title.|(){}[0]
+
+ final fun component1(): androidx.compose.ui.text/TextStyle // com.skydoves.snitcher.ui.theme/SnitcherTypography.component1|component1(){}[0]
+ final fun component2(): androidx.compose.ui.text/TextStyle // com.skydoves.snitcher.ui.theme/SnitcherTypography.component2|component2(){}[0]
+ final fun component3(): androidx.compose.ui.text/TextStyle // com.skydoves.snitcher.ui.theme/SnitcherTypography.component3|component3(){}[0]
+ final fun component4(): androidx.compose.ui.text/TextStyle // com.skydoves.snitcher.ui.theme/SnitcherTypography.component4|component4(){}[0]
+ final fun component5(): androidx.compose.ui.text/TextStyle // com.skydoves.snitcher.ui.theme/SnitcherTypography.component5|component5(){}[0]
+ final fun component6(): androidx.compose.ui.text/TextStyle // com.skydoves.snitcher.ui.theme/SnitcherTypography.component6|component6(){}[0]
+ final fun copy(androidx.compose.ui.text/TextStyle = ..., androidx.compose.ui.text/TextStyle = ..., androidx.compose.ui.text/TextStyle = ..., androidx.compose.ui.text/TextStyle = ..., androidx.compose.ui.text/TextStyle = ..., androidx.compose.ui.text/TextStyle = ...): com.skydoves.snitcher.ui.theme/SnitcherTypography // com.skydoves.snitcher.ui.theme/SnitcherTypography.copy|copy(androidx.compose.ui.text.TextStyle;androidx.compose.ui.text.TextStyle;androidx.compose.ui.text.TextStyle;androidx.compose.ui.text.TextStyle;androidx.compose.ui.text.TextStyle;androidx.compose.ui.text.TextStyle){}[0]
+ final fun equals(kotlin/Any?): kotlin/Boolean // com.skydoves.snitcher.ui.theme/SnitcherTypography.equals|equals(kotlin.Any?){}[0]
+ final fun hashCode(): kotlin/Int // com.skydoves.snitcher.ui.theme/SnitcherTypography.hashCode|hashCode(){}[0]
+ final fun toString(): kotlin/String // com.skydoves.snitcher.ui.theme/SnitcherTypography.toString|toString(){}[0]
+
+ final object Companion { // com.skydoves.snitcher.ui.theme/SnitcherTypography.Companion|null[0]
+ final fun defaultTypography(): com.skydoves.snitcher.ui.theme/SnitcherTypography // com.skydoves.snitcher.ui.theme/SnitcherTypography.Companion.defaultTypography|defaultTypography(){}[0]
+ }
+}
+
+final class com.skydoves.snitcher/SnitcherStrings { // com.skydoves.snitcher/SnitcherStrings|null[0]
+ constructor (kotlin/String = ..., kotlin/String = ..., kotlin/String = ..., kotlin/String = ..., kotlin/String = ..., kotlin/String = ..., kotlin/String = ...) // com.skydoves.snitcher/SnitcherStrings.|(kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.String){}[0]
+
+ final val restoreButton // com.skydoves.snitcher/SnitcherStrings.restoreButton|{}restoreButton[0]
+ final fun (): kotlin/String // com.skydoves.snitcher/SnitcherStrings.restoreButton.|(){}[0]
+ final val restoreDescription // com.skydoves.snitcher/SnitcherStrings.restoreDescription|{}restoreDescription[0]
+ final fun (): kotlin/String // com.skydoves.snitcher/SnitcherStrings.restoreDescription.|(){}[0]
+ final val restoreTitle // com.skydoves.snitcher/SnitcherStrings.restoreTitle|{}restoreTitle[0]
+ final fun (): kotlin/String // com.skydoves.snitcher/SnitcherStrings.restoreTitle.|(){}[0]
+ final val traceCopied // com.skydoves.snitcher/SnitcherStrings.traceCopied|{}traceCopied[0]
+ final fun (): kotlin/String // com.skydoves.snitcher/SnitcherStrings.traceCopied.|(){}[0]
+ final val traceDebugButton // com.skydoves.snitcher/SnitcherStrings.traceDebugButton|{}traceDebugButton[0]
+ final fun (): kotlin/String // com.skydoves.snitcher/SnitcherStrings.traceDebugButton.|(){}[0]
+ final val traceRestoreButton // com.skydoves.snitcher/SnitcherStrings.traceRestoreButton|{}traceRestoreButton[0]
+ final fun (): kotlin/String // com.skydoves.snitcher/SnitcherStrings.traceRestoreButton.|(){}[0]
+ final val traceStacktrace // com.skydoves.snitcher/SnitcherStrings.traceStacktrace|{}traceStacktrace[0]
+ final fun (): kotlin/String // com.skydoves.snitcher/SnitcherStrings.traceStacktrace.|(){}[0]
+
+ final fun component1(): kotlin/String // com.skydoves.snitcher/SnitcherStrings.component1|component1(){}[0]
+ final fun component2(): kotlin/String // com.skydoves.snitcher/SnitcherStrings.component2|component2(){}[0]
+ final fun component3(): kotlin/String // com.skydoves.snitcher/SnitcherStrings.component3|component3(){}[0]
+ final fun component4(): kotlin/String // com.skydoves.snitcher/SnitcherStrings.component4|component4(){}[0]
+ final fun component5(): kotlin/String // com.skydoves.snitcher/SnitcherStrings.component5|component5(){}[0]
+ final fun component6(): kotlin/String // com.skydoves.snitcher/SnitcherStrings.component6|component6(){}[0]
+ final fun component7(): kotlin/String // com.skydoves.snitcher/SnitcherStrings.component7|component7(){}[0]
+ final fun copy(kotlin/String = ..., kotlin/String = ..., kotlin/String = ..., kotlin/String = ..., kotlin/String = ..., kotlin/String = ..., kotlin/String = ...): com.skydoves.snitcher/SnitcherStrings // com.skydoves.snitcher/SnitcherStrings.copy|copy(kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.String){}[0]
+ final fun equals(kotlin/Any?): kotlin/Boolean // com.skydoves.snitcher/SnitcherStrings.equals|equals(kotlin.Any?){}[0]
+ final fun hashCode(): kotlin/Int // com.skydoves.snitcher/SnitcherStrings.hashCode|hashCode(){}[0]
+ final fun toString(): kotlin/String // com.skydoves.snitcher/SnitcherStrings.toString|toString(){}[0]
+
+ final object Companion { // com.skydoves.snitcher/SnitcherStrings.Companion|null[0]
+ final fun defaultStrings(): com.skydoves.snitcher/SnitcherStrings // com.skydoves.snitcher/SnitcherStrings.Companion.defaultStrings|defaultStrings(){}[0]
+ }
+}
+
+final object com.skydoves.snitcher.ui.theme/SnitcherTheme { // com.skydoves.snitcher.ui.theme/SnitcherTheme|null[0]
+ final val colors // com.skydoves.snitcher.ui.theme/SnitcherTheme.colors|{}colors[0]
+ final fun (androidx.compose.runtime/Composer?, kotlin/Int): com.skydoves.snitcher.ui.theme/SnitcherColor // com.skydoves.snitcher.ui.theme/SnitcherTheme.colors.|(androidx.compose.runtime.Composer?;kotlin.Int){}[0]
+ final val shapes // com.skydoves.snitcher.ui.theme/SnitcherTheme.shapes|{}shapes[0]
+ final fun (androidx.compose.runtime/Composer?, kotlin/Int): com.skydoves.snitcher.ui.theme/SnitcherShapes // com.skydoves.snitcher.ui.theme/SnitcherTheme.shapes.|(androidx.compose.runtime.Composer?;kotlin.Int){}[0]
+ final val typography // com.skydoves.snitcher.ui.theme/SnitcherTheme.typography|{}typography[0]
+ final fun (androidx.compose.runtime/Composer?, kotlin/Int): com.skydoves.snitcher.ui.theme/SnitcherTypography // com.skydoves.snitcher.ui.theme/SnitcherTheme.typography.|(androidx.compose.runtime.Composer?;kotlin.Int){}[0]
+}
+
+final object com.skydoves.snitcher/Snitcher { // com.skydoves.snitcher/Snitcher|null[0]
+ final val exception // com.skydoves.snitcher/Snitcher.exception|{}exception[0]
+ final fun (): kotlinx.coroutines.flow/StateFlow // com.skydoves.snitcher/Snitcher.exception.|(){}[0]
+ final val isInstalled // com.skydoves.snitcher/Snitcher.isInstalled|{}isInstalled[0]
+ final fun (): kotlin/Boolean // com.skydoves.snitcher/Snitcher.isInstalled.|(){}[0]
+ final val latestException // com.skydoves.snitcher/Snitcher.latestException|{}latestException[0]
+ final fun (): com.skydoves.snitcher.model/SnitcherException? // com.skydoves.snitcher/Snitcher.latestException.|(){}[0]
+
+ final var isDebuggable // com.skydoves.snitcher/Snitcher.isDebuggable|{}isDebuggable[0]
+ final fun (): kotlin/Boolean // com.skydoves.snitcher/Snitcher.isDebuggable.|(){}[0]
+ final fun (kotlin/Boolean) // com.skydoves.snitcher/Snitcher.isDebuggable.|(kotlin.Boolean){}[0]
+ final var platformInfo // com.skydoves.snitcher/Snitcher.platformInfo|{}platformInfo[0]
+ final fun (): kotlin/String // com.skydoves.snitcher/Snitcher.platformInfo.|(){}[0]
+ final fun (kotlin/String) // com.skydoves.snitcher/Snitcher.platformInfo.|(kotlin.String){}[0]
+ final var strings // com.skydoves.snitcher/Snitcher.strings|{}strings[0]
+ final fun (): com.skydoves.snitcher/SnitcherStrings // com.skydoves.snitcher/Snitcher.strings.|(){}[0]
+ final fun (com.skydoves.snitcher/SnitcherStrings) // com.skydoves.snitcher/Snitcher.strings.|(com.skydoves.snitcher.SnitcherStrings){}[0]
+ final var theme // com.skydoves.snitcher/Snitcher.theme|{}theme[0]
+ final fun (): com.skydoves.snitcher.ui.theme/SnitcherThemeConfig // com.skydoves.snitcher/Snitcher.theme.|(){}[0]
+ final fun (com.skydoves.snitcher.ui.theme/SnitcherThemeConfig) // com.skydoves.snitcher/Snitcher.theme.|(com.skydoves.snitcher.ui.theme.SnitcherThemeConfig){}[0]
+
+ final fun clear() // com.skydoves.snitcher/Snitcher.clear|clear(){}[0]
+}
+
+final val com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherException$stableprop // com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherException$stableprop|#static{}com_skydoves_snitcher_model_SnitcherException$stableprop[0]
+final val com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherException_$serializer$stableprop // com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherException_$serializer$stableprop|#static{}com_skydoves_snitcher_model_SnitcherException_$serializer$stableprop[0]
+final val com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherPreference_$serializer$stableprop // com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherPreference_$serializer$stableprop|#static{}com_skydoves_snitcher_model_SnitcherPreference_$serializer$stableprop[0]
+final val com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherStackTraceElement$stableprop // com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherStackTraceElement$stableprop|#static{}com_skydoves_snitcher_model_SnitcherStackTraceElement$stableprop[0]
+final val com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherStackTraceElement_$serializer$stableprop // com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherStackTraceElement_$serializer$stableprop|#static{}com_skydoves_snitcher_model_SnitcherStackTraceElement_$serializer$stableprop[0]
+final val com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherColor$stableprop // com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherColor$stableprop|#static{}com_skydoves_snitcher_ui_theme_SnitcherColor$stableprop[0]
+final val com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherShapes$stableprop // com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherShapes$stableprop|#static{}com_skydoves_snitcher_ui_theme_SnitcherShapes$stableprop[0]
+final val com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherTheme$stableprop // com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherTheme$stableprop|#static{}com_skydoves_snitcher_ui_theme_SnitcherTheme$stableprop[0]
+final val com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherThemeConfig$stableprop // com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherThemeConfig$stableprop|#static{}com_skydoves_snitcher_ui_theme_SnitcherThemeConfig$stableprop[0]
+final val com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherTypography$stableprop // com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherTypography$stableprop|#static{}com_skydoves_snitcher_ui_theme_SnitcherTypography$stableprop[0]
+final val com.skydoves.snitcher/com_skydoves_snitcher_Snitcher$stableprop // com.skydoves.snitcher/com_skydoves_snitcher_Snitcher$stableprop|#static{}com_skydoves_snitcher_Snitcher$stableprop[0]
+final val com.skydoves.snitcher/com_skydoves_snitcher_SnitcherStrings$stableprop // com.skydoves.snitcher/com_skydoves_snitcher_SnitcherStrings$stableprop|#static{}com_skydoves_snitcher_SnitcherStrings$stableprop[0]
+
+final fun (com.skydoves.snitcher/Snitcher).com.skydoves.snitcher/install() // com.skydoves.snitcher/install|install@com.skydoves.snitcher.Snitcher(){}[0]
+final fun (com.skydoves.snitcher/Snitcher).com.skydoves.snitcher/install(kotlin/String = ..., com.skydoves.snitcher.ui.theme/SnitcherThemeConfig = ..., com.skydoves.snitcher/SnitcherStrings = ..., kotlin/String = ..., kotlin/Boolean = ..., kotlin/Function1 = ...) // com.skydoves.snitcher/install|install@com.skydoves.snitcher.Snitcher(kotlin.String;com.skydoves.snitcher.ui.theme.SnitcherThemeConfig;com.skydoves.snitcher.SnitcherStrings;kotlin.String;kotlin.Boolean;kotlin.Function1){}[0]
+final fun com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherException$stableprop_getter(): kotlin/Int // com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherException$stableprop_getter|com_skydoves_snitcher_model_SnitcherException$stableprop_getter(){}[0]
+final fun com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherException_$serializer$stableprop_getter(): kotlin/Int // com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherException_$serializer$stableprop_getter|com_skydoves_snitcher_model_SnitcherException_$serializer$stableprop_getter(){}[0]
+final fun com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherPreference_$serializer$stableprop_getter(): kotlin/Int // com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherPreference_$serializer$stableprop_getter|com_skydoves_snitcher_model_SnitcherPreference_$serializer$stableprop_getter(){}[0]
+final fun com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherStackTraceElement$stableprop_getter(): kotlin/Int // com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherStackTraceElement$stableprop_getter|com_skydoves_snitcher_model_SnitcherStackTraceElement$stableprop_getter(){}[0]
+final fun com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherStackTraceElement_$serializer$stableprop_getter(): kotlin/Int // com.skydoves.snitcher.model/com_skydoves_snitcher_model_SnitcherStackTraceElement_$serializer$stableprop_getter|com_skydoves_snitcher_model_SnitcherStackTraceElement_$serializer$stableprop_getter(){}[0]
+final fun com.skydoves.snitcher.ui.theme/SnitcherTheme(kotlin/Boolean, com.skydoves.snitcher.ui.theme/SnitcherColor?, com.skydoves.snitcher.ui.theme/SnitcherTypography?, com.skydoves.snitcher.ui.theme/SnitcherShapes?, kotlin/Function2, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.skydoves.snitcher.ui.theme/SnitcherTheme|SnitcherTheme(kotlin.Boolean;com.skydoves.snitcher.ui.theme.SnitcherColor?;com.skydoves.snitcher.ui.theme.SnitcherTypography?;com.skydoves.snitcher.ui.theme.SnitcherShapes?;kotlin.Function2;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
+final fun com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherColor$stableprop_getter(): kotlin/Int // com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherColor$stableprop_getter|com_skydoves_snitcher_ui_theme_SnitcherColor$stableprop_getter(){}[0]
+final fun com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherShapes$stableprop_getter(): kotlin/Int // com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherShapes$stableprop_getter|com_skydoves_snitcher_ui_theme_SnitcherShapes$stableprop_getter(){}[0]
+final fun com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherTheme$stableprop_getter(): kotlin/Int // com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherTheme$stableprop_getter|com_skydoves_snitcher_ui_theme_SnitcherTheme$stableprop_getter(){}[0]
+final fun com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherThemeConfig$stableprop_getter(): kotlin/Int // com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherThemeConfig$stableprop_getter|com_skydoves_snitcher_ui_theme_SnitcherThemeConfig$stableprop_getter(){}[0]
+final fun com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherTypography$stableprop_getter(): kotlin/Int // com.skydoves.snitcher.ui.theme/com_skydoves_snitcher_ui_theme_SnitcherTypography$stableprop_getter|com_skydoves_snitcher_ui_theme_SnitcherTypography$stableprop_getter(){}[0]
+final fun com.skydoves.snitcher.ui/AppRestoreScreen(androidx.compose.ui/Modifier?, kotlin/Function0?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.skydoves.snitcher.ui/AppRestoreScreen|AppRestoreScreen(androidx.compose.ui.Modifier?;kotlin.Function0?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
+final fun com.skydoves.snitcher.ui/ExceptionTraceScreen(com.skydoves.snitcher.model/SnitcherException, androidx.compose.ui/Modifier?, kotlin/Function0?, kotlin/Function0?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.skydoves.snitcher.ui/ExceptionTraceScreen|ExceptionTraceScreen(com.skydoves.snitcher.model.SnitcherException;androidx.compose.ui.Modifier?;kotlin.Function0?;kotlin.Function0?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
+final fun com.skydoves.snitcher.ui/snitcherViewController(kotlin/Function0? = ...): platform.UIKit/UIViewController // com.skydoves.snitcher.ui/snitcherViewController|snitcherViewController(kotlin.Function0?){}[0]
+final fun com.skydoves.snitcher/com_skydoves_snitcher_Snitcher$stableprop_getter(): kotlin/Int // com.skydoves.snitcher/com_skydoves_snitcher_Snitcher$stableprop_getter|com_skydoves_snitcher_Snitcher$stableprop_getter(){}[0]
+final fun com.skydoves.snitcher/com_skydoves_snitcher_SnitcherStrings$stableprop_getter(): kotlin/Int // com.skydoves.snitcher/com_skydoves_snitcher_SnitcherStrings$stableprop_getter|com_skydoves_snitcher_SnitcherStrings$stableprop_getter(){}[0]
diff --git a/snitcher/build.gradle.kts b/snitcher/build.gradle.kts
index 7c5d4ad..227d2a8 100644
--- a/snitcher/build.gradle.kts
+++ b/snitcher/build.gradle.kts
@@ -1,33 +1,40 @@
-// Designed and developed by 2023 skydoves (Jaewoong Eum)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+ * Designed and developed by 2023 skydoves (Jaewoong Eum)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
import com.skydoves.snitcher.Configuration
+import com.vanniktech.maven.publish.JavadocJar
+import com.vanniktech.maven.publish.KotlinMultiplatform
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.kotlin.multiplatform.get().pluginId)
+ id(libs.plugins.android.kotlin.multiplatform.library.get().pluginId)
+ id(libs.plugins.compose.multiplatform.get().pluginId)
id(libs.plugins.compose.compiler.get().pluginId)
+ id(libs.plugins.kotlin.serialization.get().pluginId)
id(libs.plugins.nexus.plugin.get().pluginId)
id(libs.plugins.baseline.profile.get().pluginId)
+ id(libs.plugins.dokka.get().pluginId)
}
apply(from = "${rootDir}/scripts/publish-module.gradle.kts")
mavenPublishing {
+ configure(KotlinMultiplatform(javadocJar = JavadocJar.Dokka("dokkaGeneratePublicationHtml")))
+
val artifactId = "snitcher"
coordinates(
Configuration.artifactGroup,
@@ -41,39 +48,78 @@ mavenPublishing {
}
}
-android {
- compileSdk = Configuration.compileSdk
- namespace = "com.skydoves.snitcher"
- defaultConfig {
+kotlin {
+ explicitApi()
+
+ @OptIn(org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation::class)
+ abiValidation {
+ referenceDumpDir.set(layout.projectDirectory.dir("api"))
+ }
+
+ android {
+ namespace = "com.skydoves.snitcher"
+ compileSdk = Configuration.compileSdk
minSdk = Configuration.minSdk
+
+ lint {
+ abortOnError = false
+ }
+
+ withHostTest {}
+
+ compilerOptions {
+ jvmTarget.set(JvmTarget.fromTarget(libs.versions.jvmTarget.get()))
+ }
}
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
+ jvm("desktop") {
+ compilerOptions {
+ jvmTarget.set(JvmTarget.fromTarget(libs.versions.jvmTarget.get()))
+ }
}
- buildFeatures {
- compose = true
+ // Compose Multiplatform no longer publishes the intel simulator target.
+ listOf(iosArm64(), iosSimulatorArm64()).forEach { target ->
+ target.binaries.framework {
+ baseName = "Snitcher"
+ isStatic = true
+ }
}
- resourcePrefix = "snitcher"
+ applyDefaultHierarchyTemplate()
- lint {
- abortOnError = false
- }
-}
+ sourceSets {
+ val jvmSharedMain = create("jvmSharedMain")
+ jvmSharedMain.dependsOn(commonMain.get())
+ getByName("desktopMain").dependsOn(jvmSharedMain)
+ getByName("androidMain").dependsOn(jvmSharedMain)
-kotlin {
- compilerOptions {
- jvmTarget.set(JvmTarget.fromTarget(libs.versions.jvmTarget.get()))
- freeCompilerArgs.add("-Xexplicit-api=strict")
- }
-}
+ commonMain.dependencies {
+ // these carry types of the public API, such as StateFlow, Color, TextStyle and Modifier.
+ api(libs.compose.runtime)
+ api(libs.compose.foundation)
+ api(libs.compose.ui)
+ api(libs.coroutines)
+ api(libs.kotlinx.serialization.core)
+
+ implementation(libs.compose.material)
+ implementation(libs.kotlinx.serialization.json)
+ implementation(libs.okio)
+ }
+
+ commonTest.dependencies {
+ implementation(kotlin("test"))
+ implementation(libs.coroutines.test)
+ }
+
+ androidMain.dependencies {
+ // ExceptionTraceActivity extends ComponentActivity, which consumers subclass.
+ api(libs.androidx.activity.compose)
-tasks.withType(JavaCompile::class.java).configureEach {
- this.targetCompatibility = libs.versions.jvmTarget.get()
- this.sourceCompatibility = libs.versions.jvmTarget.get()
+ implementation(libs.androidx.core.ktx)
+ }
+
+ }
}
baselineProfile {
@@ -83,23 +129,5 @@ baselineProfile {
}
dependencies {
- implementation(platform(libs.androidx.compose.bom))
- implementation(libs.androidx.activity.compose)
- implementation(libs.androidx.compose.ui)
- implementation(libs.androidx.compose.ui.tooling)
- implementation(libs.androidx.compose.foundation)
- implementation(libs.androidx.compose.runtime)
- implementation(libs.androidx.compose.material)
- implementation(libs.androidx.compose.material.iconsExtended)
- implementation(libs.androidx.core.ktx)
-
- implementation(libs.androidx.datastore)
- implementation(libs.androidx.datastore.core)
-
- implementation(libs.kotlinx.serialization.json)
- implementation(libs.kotlinx.serialization.protobuf)
-
- implementation(libs.coroutines)
-
baselineProfile(project(":benchmark"))
}
diff --git a/snitcher/src/main/AndroidManifest.xml b/snitcher/src/androidMain/AndroidManifest.xml
similarity index 95%
rename from snitcher/src/main/AndroidManifest.xml
rename to snitcher/src/androidMain/AndroidManifest.xml
index 76c3080..0b728d0 100644
--- a/snitcher/src/main/AndroidManifest.xml
+++ b/snitcher/src/androidMain/AndroidManifest.xml
@@ -14,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
+
diff --git a/snitcher/src/main/generated/baselineProfiles/baseline-prof.txt b/snitcher/src/androidMain/baselineProfiles/baseline-prof.txt
similarity index 88%
rename from snitcher/src/main/generated/baselineProfiles/baseline-prof.txt
rename to snitcher/src/androidMain/baselineProfiles/baseline-prof.txt
index bfba4b6..7d856d8 100644
--- a/snitcher/src/main/generated/baselineProfiles/baseline-prof.txt
+++ b/snitcher/src/androidMain/baselineProfiles/baseline-prof.txt
@@ -1,9 +1,7 @@
-Lcom/skydoves/snitcher/R$string;
Lcom/skydoves/snitcher/Snitcher;
HSPLcom/skydoves/snitcher/Snitcher;->()V
HSPLcom/skydoves/snitcher/Snitcher;->(Landroid/app/Application;Ljava/lang/String;Lkotlin/reflect/KClass;Lcom/skydoves/snitcher/TraceStrategy;Ljava/lang/Thread$UncaughtExceptionHandler;Lkotlin/jvm/functions/Function2;)V
HSPLcom/skydoves/snitcher/Snitcher;->access$getApplication$p(Lcom/skydoves/snitcher/Snitcher;)Landroid/app/Application;
-HSPLcom/skydoves/snitcher/Snitcher;->access$getDataStore$p(Lcom/skydoves/snitcher/Snitcher;)Lcom/skydoves/snitcher/datastore/SnitcherDataStore;
HSPLcom/skydoves/snitcher/Snitcher;->access$getException$cp()Lkotlinx/coroutines/flow/StateFlow;
HSPLcom/skydoves/snitcher/Snitcher;->access$getLauncher$cp()Lkotlinx/coroutines/flow/StateFlow;
HSPLcom/skydoves/snitcher/Snitcher;->access$get_exception$cp()Lkotlinx/coroutines/flow/MutableStateFlow;
@@ -53,17 +51,6 @@ Lcom/skydoves/snitcher/TraceStrategy;
HSPLcom/skydoves/snitcher/TraceStrategy;->$values()[Lcom/skydoves/snitcher/TraceStrategy;
HSPLcom/skydoves/snitcher/TraceStrategy;->()V
HSPLcom/skydoves/snitcher/TraceStrategy;->(Ljava/lang/String;I)V
-Lcom/skydoves/snitcher/datastore/SnitcherDataStore;
-HSPLcom/skydoves/snitcher/datastore/SnitcherDataStore;->(Landroidx/datastore/core/DataStore;)V
-HSPLcom/skydoves/snitcher/datastore/SnitcherDataStore;->getData()Lkotlinx/coroutines/flow/Flow;
-Lcom/skydoves/snitcher/datastore/SnitcherSerializer;
-HSPLcom/skydoves/snitcher/datastore/SnitcherSerializer;->()V
-HSPLcom/skydoves/snitcher/datastore/SnitcherSerializer;->readFrom(Ljava/io/InputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
-Lcom/skydoves/snitcher/installer/SnitcherInstaller;
-HSPLcom/skydoves/snitcher/installer/SnitcherInstaller;->