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