From ba3f5f3b7a98a167644dfff258143f1be04053ff Mon Sep 17 00:00:00 2001
From: Acclorite <140836141+Acclorite@users.noreply.github.com>
Date: Fri, 21 Mar 2025 19:16:11 +0200
Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Crash=20screen?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Shows app crash log
* Copy or report (share)
* Logs are saved in app's internal storage (/crash)
Resolves: #188
---
app/src/main/AndroidManifest.xml | 9 ++
.../ua/acclorite/book_story/Application.kt | 8 +-
.../book_story/core/crash/CrashHandler.kt | 36 ++++++++
.../book_story/core/crash/CrashUtils.kt | 57 +++++++++++++
.../data/parser/epub/EpubTextParser.kt | 1 -
.../presentation/crash/CrashBottomBar.kt | 71 ++++++++++++++++
.../presentation/crash/CrashContent.kt | 43 ++++++++++
.../presentation/crash/CrashLayout.kt | 27 ++++++
.../presentation/crash/CrashScaffold.kt | 85 +++++++++++++++++++
.../book_story/ui/crash/CrashActivity.kt | 41 +++++++++
.../book_story/ui/crash/CrashScreen.kt | 82 ++++++++++++++++++
.../book_story/ui/history/HistoryModel.kt | 2 -
app/src/main/res/values-uk/strings.xml | 5 ++
app/src/main/res/values/strings.xml | 5 ++
14 files changed, 468 insertions(+), 4 deletions(-)
create mode 100644 app/src/main/java/ua/acclorite/book_story/core/crash/CrashHandler.kt
create mode 100644 app/src/main/java/ua/acclorite/book_story/core/crash/CrashUtils.kt
create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashBottomBar.kt
create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashContent.kt
create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashLayout.kt
create mode 100644 app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashScaffold.kt
create mode 100644 app/src/main/java/ua/acclorite/book_story/ui/crash/CrashActivity.kt
create mode 100644 app/src/main/java/ua/acclorite/book_story/ui/crash/CrashScreen.kt
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 40a098d7..9bec01d3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -35,6 +35,7 @@
tools:ignore="DataExtractionRules"
tools:targetApi="tiramisu">
+
+
+
+
+ writer.write(crashLog)
+ }
+
+ if (!tempFile.renameTo(crashFile)) {
+ throw IllegalStateException("Failed to rename .tmp file.")
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ tempFile.delete()
+ return false
+ }
+
+ return true
+ }
+
+ fun getCrashLog(context: Context): String? {
+ val crashDir = File(context.filesDir, "crash")
+ if (!crashDir.exists()) return null
+
+ val crashLogs = crashDir.listFiles() ?: return null
+ val crashLog = crashLogs.maxByOrNull { it.lastModified() } ?: return null
+ return crashLog.bufferedReader().use { reader ->
+ reader.readText()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt
index ce373d44..b8e475ba 100644
--- a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt
+++ b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt
@@ -29,7 +29,6 @@ import java.util.concurrent.ConcurrentLinkedQueue
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import javax.inject.Inject
-import kotlin.collections.set
private const val EPUB_TAG = "EPUB Parser"
private typealias Source = String
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashBottomBar.kt b/app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashBottomBar.kt
new file mode 100644
index 00000000..27988d79
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashBottomBar.kt
@@ -0,0 +1,71 @@
+/*
+ * Book's Story — free and open-source Material You eBook reader.
+ * Copyright (C) 2024-2025 Acclorite
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package ua.acclorite.book_story.presentation.crash
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material3.Button
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalWindowInfo
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import ua.acclorite.book_story.R
+import ua.acclorite.book_story.presentation.core.components.common.StyledText
+
+@Composable
+fun CrashBottomBar(
+ copy: () -> Unit,
+ report: () -> Unit
+) {
+ val windowInfo = LocalWindowInfo.current
+ val density = LocalDensity.current
+ val copyWidth = remember(windowInfo.containerSize) {
+ with(density) {
+ (windowInfo.containerSize.width.toDp() - 36.dp) * (1f / 2.5f)
+ }
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .navigationBarsPadding()
+ .padding(top = 18.dp, bottom = 8.dp)
+ .padding(horizontal = 18.dp),
+ horizontalArrangement = Arrangement.spacedBy(18.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ TextButton(
+ modifier = Modifier.widthIn(
+ min = copyWidth
+ ),
+ onClick = { copy() }
+ ) {
+ StyledText(
+ text = stringResource(id = R.string.copy),
+ maxLines = 1
+ )
+ }
+ Button(
+ modifier = Modifier.weight(1f),
+ onClick = { report() }
+ ) {
+ StyledText(
+ text = stringResource(id = R.string.report),
+ maxLines = 1
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashContent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashContent.kt
new file mode 100644
index 00000000..470e52fc
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashContent.kt
@@ -0,0 +1,43 @@
+/*
+ * Book's Story — free and open-source Material You eBook reader.
+ * Copyright (C) 2024-2025 Acclorite
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package ua.acclorite.book_story.presentation.crash
+
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import ua.acclorite.book_story.presentation.core.components.common.StyledText
+
+@Composable
+fun CrashContent(
+ crashLog: String?,
+ copy: () -> Unit,
+ report: () -> Unit
+) {
+ CrashScaffold(
+ copy = copy,
+ report = report
+ ) {
+ CrashLayout {
+ if (!crashLog.isNullOrBlank()) {
+ item {
+ SelectionContainer {
+ StyledText(
+ text = crashLog,
+ modifier = Modifier.horizontalScroll(rememberScrollState()),
+ style = MaterialTheme.typography.bodySmall.copy(
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashLayout.kt b/app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashLayout.kt
new file mode 100644
index 00000000..bf672ece
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashLayout.kt
@@ -0,0 +1,27 @@
+/*
+ * Book's Story — free and open-source Material You eBook reader.
+ * Copyright (C) 2024-2025 Acclorite
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package ua.acclorite.book_story.presentation.crash
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.LazyListScope
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import ua.acclorite.book_story.presentation.core.components.common.LazyColumnWithScrollbar
+
+@Composable
+fun CrashLayout(
+ content: LazyListScope.() -> Unit
+) {
+ LazyColumnWithScrollbar(
+ Modifier.fillMaxSize(),
+ contentPadding = PaddingValues(18.dp)
+ ) {
+ content()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashScaffold.kt b/app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashScaffold.kt
new file mode 100644
index 00000000..de44c0f6
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/presentation/crash/CrashScaffold.kt
@@ -0,0 +1,85 @@
+/*
+ * Book's Story — free and open-source Material You eBook reader.
+ * Copyright (C) 2024-2025 Acclorite
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package ua.acclorite.book_story.presentation.crash
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.BugReport
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import ua.acclorite.book_story.R
+import ua.acclorite.book_story.presentation.core.components.common.StyledText
+
+@Composable
+fun CrashScaffold(
+ copy: () -> Unit,
+ report: () -> Unit,
+ content: @Composable () -> Unit
+) {
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ containerColor = MaterialTheme.colorScheme.surface,
+ bottomBar = {
+ CrashBottomBar(
+ copy = copy,
+ report = report
+ )
+ }
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(it)
+ .padding(horizontal = 18.dp)
+ ) {
+ Spacer(modifier = Modifier.height(24.dp))
+ Icon(
+ imageVector = Icons.Default.BugReport,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.primary,
+ modifier = Modifier.size(64.dp)
+ )
+ Spacer(modifier = Modifier.height(12.dp))
+ StyledText(
+ text = stringResource(id = R.string.crash_title),
+ style = MaterialTheme.typography.headlineSmall.copy(
+ color = MaterialTheme.colorScheme.onSurface
+ )
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ StyledText(
+ text = stringResource(id = R.string.crash_description),
+ style = MaterialTheme.typography.bodyMedium.copy(
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ )
+ Spacer(modifier = Modifier.height(18.dp))
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(MaterialTheme.shapes.large)
+ .background(MaterialTheme.colorScheme.surfaceContainerLow)
+ ) {
+ content()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/crash/CrashActivity.kt b/app/src/main/java/ua/acclorite/book_story/ui/crash/CrashActivity.kt
new file mode 100644
index 00000000..42380bd5
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/ui/crash/CrashActivity.kt
@@ -0,0 +1,41 @@
+/*
+ * Book's Story — free and open-source Material You eBook reader.
+ * Copyright (C) 2024-2025 Acclorite
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package ua.acclorite.book_story.ui.crash
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.core.view.WindowCompat
+import ua.acclorite.book_story.domain.ui.ThemeContrast
+import ua.acclorite.book_story.ui.theme.BookStoryTheme
+import ua.acclorite.book_story.ui.theme.Theme
+
+class CrashActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+
+ // Default super
+ super.onCreate(savedInstanceState)
+
+ // Edge to edge
+ enableEdgeToEdge()
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
+ setContent {
+ BookStoryTheme(
+ theme = Theme.DYNAMIC,
+ isDark = isSystemInDarkTheme(),
+ isPureDark = false,
+ themeContrast = ThemeContrast.STANDARD
+ ) {
+ CrashScreen.Content()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/crash/CrashScreen.kt b/app/src/main/java/ua/acclorite/book_story/ui/crash/CrashScreen.kt
new file mode 100644
index 00000000..7aa7835a
--- /dev/null
+++ b/app/src/main/java/ua/acclorite/book_story/ui/crash/CrashScreen.kt
@@ -0,0 +1,82 @@
+/*
+ * Book's Story — free and open-source Material You eBook reader.
+ * Copyright (C) 2024-2025 Acclorite
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package ua.acclorite.book_story.ui.crash
+
+import android.content.ClipData
+import android.content.Intent
+import android.os.Build
+import android.os.Parcelable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.platform.ClipEntry
+import androidx.compose.ui.platform.LocalClipboard
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.parcelize.Parcelize
+import ua.acclorite.book_story.R
+import ua.acclorite.book_story.core.crash.CrashUtils
+import ua.acclorite.book_story.domain.navigator.Screen
+import ua.acclorite.book_story.presentation.core.util.LocalActivity
+import ua.acclorite.book_story.presentation.core.util.launchActivity
+import ua.acclorite.book_story.presentation.core.util.showToast
+import ua.acclorite.book_story.presentation.crash.CrashContent
+
+@Parcelize
+object CrashScreen : Screen, Parcelable {
+
+ @Composable
+ override fun Content() {
+ val activity = LocalActivity.current
+ val clipboard = LocalClipboard.current
+ val scope = rememberCoroutineScope()
+
+ val crashLog = remember {
+ CrashUtils.getCrashLog(activity)?.trimIndent()
+ }
+
+ CrashContent(
+ crashLog = crashLog,
+ copy = {
+ if (crashLog.isNullOrBlank()) return@CrashContent
+ scope.launch(Dispatchers.Main) {
+ clipboard.setClipEntry(
+ ClipEntry(ClipData.newPlainText("Crash", crashLog))
+ )
+
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
+ activity.getString(R.string.copied)
+ .showToast(context = activity, longToast = false)
+ }
+ }
+ },
+ report = {
+ Intent().apply {
+ action = Intent.ACTION_SEND
+ type = "text/plain"
+ putExtra(
+ Intent.EXTRA_SUBJECT,
+ activity.getString(R.string.app_name)
+ )
+ putExtra(
+ Intent.EXTRA_TEXT,
+ crashLog
+ )
+ }.launchActivity(
+ activity = activity,
+ createChooser = true,
+ success = {
+ return@CrashContent
+ }
+ )
+
+ activity.getString(R.string.error_no_share_app)
+ .showToast(context = activity, longToast = false)
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ua/acclorite/book_story/ui/history/HistoryModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/history/HistoryModel.kt
index 1617fe95..50f973ec 100644
--- a/app/src/main/java/ua/acclorite/book_story/ui/history/HistoryModel.kt
+++ b/app/src/main/java/ua/acclorite/book_story/ui/history/HistoryModel.kt
@@ -37,8 +37,6 @@ import java.util.Calendar
import java.util.Date
import java.util.Locale
import javax.inject.Inject
-import kotlin.collections.component1
-import kotlin.collections.component2
@HiltViewModel
class HistoryModel @Inject constructor(
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 257cfc70..f914b1c3 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -67,6 +67,7 @@
Ніколи
Скасувати
Копіювати
+ Повідомити
Перекласти
Словник
Далі
@@ -431,6 +432,10 @@
Сьогодні
Вчора
+
+ Ой! Застосунок крашнувся.
+ Будь ласка, повідомте про цей збій у найзручніший для вас спосіб.
+
Продовжити читання
Назад
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4f6bae2a..915b751a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -96,6 +96,7 @@
Never
Undo
Copy
+ Report
Translate
Dictionary
Next
@@ -566,6 +567,10 @@
Today
Yesterday
+
+ Oops! The app crashed.
+ Please report this crash message in the most convenient way for you.
+
Continue reading
Go back