diff --git a/.gitignore b/.gitignore index 7ccead02..bea964b8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ build/ local.properties .idea /.kotlin +.hotswan/ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 311d835c..6c9661a8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -79,9 +79,6 @@ android { } dependencies { - implementation(libs.acra.email) - implementation(libs.acra.dialog) - implementation(projects.feature.auth) implementation(projects.feature.chatmaterials) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5a3a7bf3..f4c91ec1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ - + @@ -37,6 +37,12 @@ + + + val crashLogsDirectory = File(filesDir.absolutePath + "/crashlogs") + if (!crashLogsDirectory.exists()) { + crashLogsDirectory.mkdirs() } - dialog { - text = "App crashed" - enabled = true + val crashLogFileName = "crash_" + System.currentTimeMillis() + ".txt" + val crashLogFile = File(crashLogsDirectory.absolutePath + "/" + crashLogFileName) + + FileOutputStream(crashLogFile).use { stream -> + stream.write(throwable.stackTraceToString().toByteArray()) + } + + if (AppSettings.Debug.showAlertAfterCrash) { + try { + val intent = Intent(this, CrashActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + intent.putExtra("CRASH_LOG_FILE_URI", Uri.fromFile(crashLogFile)) + startActivity(intent) + + exitProcess(0) + } catch (e: Exception) { + if (e !is RuntimeException) { + defaultExceptionHandler?.uncaughtException(thread, throwable) + } + } + } else { + defaultExceptionHandler?.uncaughtException(thread, throwable) } } } diff --git a/app/src/main/kotlin/dev/meloda/fast/presentation/AppCrashedDialog.kt b/app/src/main/kotlin/dev/meloda/fast/presentation/AppCrashedDialog.kt new file mode 100644 index 00000000..d420eb17 --- /dev/null +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/AppCrashedDialog.kt @@ -0,0 +1,35 @@ +package dev.meloda.fast.presentation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.window.DialogProperties +import dev.meloda.fast.ui.R +import dev.meloda.fast.ui.components.MaterialDialog + +@Composable +fun AppCrashedDialog( + stacktrace: String, + onDismiss: () -> Unit, + onShare: () -> Unit, + modifier: Modifier = Modifier, +) { + var showTrace by rememberSaveable { mutableStateOf(false) } + + MaterialDialog( + modifier = modifier, + onDismissRequest = onDismiss, + title = stringResource(R.string.title_error), + text = if (showTrace) stacktrace else stringResource(R.string.error_occurred), + confirmText = stringResource(R.string.action_share), + confirmAction = onShare, + cancelText = stringResource(if (showTrace) R.string.action_hide_stacktrace else R.string.action_show_stacktrace), + cancelAction = { showTrace = !showTrace }, + neutralText = stringResource(R.string.action_close), + properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false) + ) +} diff --git a/app/src/main/kotlin/dev/meloda/fast/presentation/CrashActivity.kt b/app/src/main/kotlin/dev/meloda/fast/presentation/CrashActivity.kt new file mode 100644 index 00000000..b8e13df8 --- /dev/null +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/CrashActivity.kt @@ -0,0 +1,71 @@ +package dev.meloda.fast.presentation + +import android.content.ClipData +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.runtime.collectAsState +import androidx.core.content.FileProvider +import androidx.core.net.toFile +import dev.meloda.fast.datastore.UserSettings +import dev.meloda.fast.ui.theme.AppTheme +import dev.meloda.fast.ui.util.isNeedToEnableDarkMode +import org.koin.compose.koinInject +import java.io.File + +class CrashActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + + val crashLogFileUri = intent.getParcelableExtra("CRASH_LOG_FILE_URI") ?: run { + finish() + return + } + val crashLogFile = crashLogFileUri.toFile().takeIf(File::exists) ?: run { + finish() + return + } + + val stacktrace = crashLogFile.bufferedReader().readText() + + setContent { + val userSettings: UserSettings = koinInject() + + AppTheme( + useDarkTheme = isNeedToEnableDarkMode(darkMode = userSettings.darkMode.collectAsState().value), + useDynamicColors = userSettings.enableDynamicColors.collectAsState().value, + selectedColorScheme = 0, + useAmoledBackground = userSettings.enableAmoledDark.collectAsState().value, + useSystemFont = userSettings.useSystemFont.collectAsState().value + ) { + AppCrashedDialog( + stacktrace = stacktrace, + onDismiss = { finish() }, + onShare = { + val uri = FileProvider.getUriForFile( + this, + "$packageName.provider", + crashLogFile + ) + + val sendIntent = Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + putExtra(Intent.EXTRA_STREAM, uri) + clipData = ClipData.newRawUri(null, uri) + } + + val chooserIntent = Intent.createChooser(sendIntent, null) + chooserIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + startActivity(chooserIntent) + } + ) + } + } + } +} diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index f86ae261..34ffb6d3 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -306,4 +306,7 @@ Are you sure you want to create chat «%s» only with yourself? Edit message + Close + Hide stacktrace + Show stacktrace diff --git a/core/ui/src/main/res/values/themes.xml b/core/ui/src/main/res/values/themes.xml index 46d12ce8..c44510ed 100644 --- a/core/ui/src/main/res/values/themes.xml +++ b/core/ui/src/main/res/values/themes.xml @@ -2,4 +2,13 @@ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 92cd4687..e94cae93 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,13 +36,9 @@ nanokt = "1.3.0" androidx-navigation = "2.9.8" serialization = "1.11.0" -acra = "5.13.1" okhttp = "5.3.2" [libraries] -acra-email = { module = "ch.acra:acra-mail", version.ref = "acra" } -acra-dialog = { module = "ch.acra:acra-dialog", version.ref = "acra" } - okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }