diff --git a/app/src/main/java/eu/kanade/presentation/components/InfoScaffold.kt b/app/src/main/java/eu/kanade/presentation/components/InfoScaffold.kt new file mode 100644 index 0000000000..3a9d64a8f8 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/components/InfoScaffold.kt @@ -0,0 +1,141 @@ +package eu.kanade.presentation.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Newspaper +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationBarDefaults +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import eu.kanade.presentation.theme.TachiyomiTheme +import eu.kanade.presentation.util.ThemePreviews +import eu.kanade.presentation.util.padding +import eu.kanade.presentation.util.secondaryItemAlpha + +@Composable +fun InfoScaffold( + icon: ImageVector, + headingText: String, + subtitleText: String, + acceptText: String, + onAcceptClick: () -> Unit, + rejectText: String, + onRejectClick: () -> Unit, + content: @Composable ColumnScope.() -> Unit, +) { + Scaffold( + bottomBar = { + val strokeWidth = Dp.Hairline + val borderColor = MaterialTheme.colorScheme.outline + Column( + modifier = Modifier + .background(MaterialTheme.colorScheme.background) + .drawBehind { + drawLine( + borderColor, + Offset(0f, 0f), + Offset(size.width, 0f), + strokeWidth.value, + ) + } + .windowInsetsPadding(NavigationBarDefaults.windowInsets) + .padding( + horizontal = MaterialTheme.padding.medium, + vertical = MaterialTheme.padding.small, + ), + ) { + androidx.compose.material3.Button( + modifier = Modifier.fillMaxWidth(), + onClick = onAcceptClick, + ) { + Text(text = acceptText) + } + OutlinedButton( + modifier = Modifier.fillMaxWidth(), + onClick = onRejectClick, + ) { + Text(text = rejectText) + } + } + }, + ) { paddingValues -> + // Status bar scrim + Box( + modifier = Modifier + .zIndex(2f) + .secondaryItemAlpha() + .background(MaterialTheme.colorScheme.background) + .fillMaxWidth() + .height(paddingValues.calculateTopPadding()), + ) + + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .fillMaxWidth() + .padding(paddingValues) + .padding(top = 48.dp) + .padding(horizontal = MaterialTheme.padding.medium), + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier + .padding(bottom = MaterialTheme.padding.small) + .size(48.dp), + tint = MaterialTheme.colorScheme.primary, + ) + Text( + text = headingText, + style = MaterialTheme.typography.headlineLarge, + ) + Text( + text = subtitleText, + modifier = Modifier + .secondaryItemAlpha() + .padding(vertical = MaterialTheme.padding.small), + style = MaterialTheme.typography.titleSmall, + ) + + content() + } + } +} + +@ThemePreviews +@Composable +private fun InfoScaffoldPreview() { + TachiyomiTheme { + InfoScaffold( + icon = Icons.Outlined.Newspaper, + headingText = "Heading", + subtitleText = "Subtitle", + acceptText = "Accept", + onAcceptClick = {}, + rejectText = "Reject", + onRejectClick = {}, + ) { + Text("Hello world") + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt b/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt index 1ff3b8b37e..64f544d318 100644 --- a/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt @@ -1,37 +1,22 @@ package eu.kanade.presentation.crash import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.BugReport -import androidx.compose.material3.Button -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp +import eu.kanade.presentation.components.InfoScaffold +import eu.kanade.presentation.theme.TachiyomiTheme +import eu.kanade.presentation.util.ThemePreviews import eu.kanade.presentation.util.padding import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.CrashLogUtil @@ -44,82 +29,41 @@ fun CrashScreen( ) { val scope = rememberCoroutineScope() val context = LocalContext.current - Scaffold( - contentWindowInsets = WindowInsets.systemBars, - bottomBar = { - val strokeWidth = Dp.Hairline - val borderColor = MaterialTheme.colorScheme.outline - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.surface) - .drawBehind { - drawLine( - borderColor, - Offset(0f, 0f), - Offset(size.width, 0f), - strokeWidth.value, - ) - } - .padding(WindowInsets.navigationBars.asPaddingValues()) - .padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small), - verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), - ) { - Button( - onClick = { - scope.launch { - CrashLogUtil(context).dumpLogs() - } - }, - modifier = Modifier.fillMaxWidth(), - ) { - Text(text = stringResource(R.string.pref_dump_crash_logs)) - } - OutlinedButton( - onClick = onRestartClick, - modifier = Modifier.fillMaxWidth(), - ) { - Text(text = stringResource(R.string.crash_screen_restart_application)) - } + + InfoScaffold( + icon = Icons.Outlined.BugReport, + headingText = stringResource(R.string.crash_screen_title), + subtitleText = stringResource(R.string.crash_screen_description, stringResource(R.string.app_name)), + acceptText = stringResource(R.string.pref_dump_crash_logs), + onAcceptClick = { + scope.launch { + CrashLogUtil(context).dumpLogs() } }, - ) { paddingValues -> - Column( + rejectText = stringResource(R.string.crash_screen_restart_application), + onRejectClick = onRestartClick, + ) { + Box( modifier = Modifier - .verticalScroll(rememberScrollState()) - .padding(paddingValues) - .padding(top = 56.dp) - .padding(horizontal = MaterialTheme.padding.medium), - horizontalAlignment = Alignment.CenterHorizontally, + .padding(vertical = MaterialTheme.padding.small) + .clip(MaterialTheme.shapes.small) + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceVariant), ) { - Icon( - imageVector = Icons.Outlined.BugReport, - contentDescription = null, - modifier = Modifier - .size(64.dp), - ) Text( - text = stringResource(R.string.crash_screen_title), - style = MaterialTheme.typography.titleLarge, - ) - Text( - text = stringResource(R.string.crash_screen_description, stringResource(R.string.app_name)), + text = exception.toString(), modifier = Modifier - .padding(vertical = MaterialTheme.padding.small), + .padding(all = MaterialTheme.padding.small), + color = MaterialTheme.colorScheme.onSurfaceVariant, ) - Box( - modifier = Modifier - .padding(vertical = MaterialTheme.padding.small) - .clip(MaterialTheme.shapes.small) - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceVariant), - ) { - Text( - text = exception.toString(), - modifier = Modifier - .padding(all = MaterialTheme.padding.small), - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } } } } + +@ThemePreviews +@Composable +private fun CrashScreenPreview() { + TachiyomiTheme { + CrashScreen(exception = RuntimeException("Dummy")) {} + } +} diff --git a/app/src/main/java/eu/kanade/presentation/manga/TrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/manga/TrackInfoDialogHome.kt index 5810c437a6..48aac34baa 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/TrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/TrackInfoDialogHome.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import eu.kanade.presentation.components.Divider @@ -250,6 +251,7 @@ private fun TrackDetailsItem( maxLines = 2, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, ) } } diff --git a/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt b/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt index 0d6909461a..5b9c572c41 100644 --- a/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt @@ -1,41 +1,28 @@ package eu.kanade.presentation.more -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.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.OpenInNew import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBarDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.zIndex import com.halilibo.richtext.markdown.Markdown import com.halilibo.richtext.ui.RichTextStyle import com.halilibo.richtext.ui.material3.Material3RichText import com.halilibo.richtext.ui.string.RichTextStringStyle -import eu.kanade.presentation.components.Scaffold +import eu.kanade.presentation.components.InfoScaffold +import eu.kanade.presentation.theme.TachiyomiTheme +import eu.kanade.presentation.util.ThemePreviews import eu.kanade.presentation.util.padding -import eu.kanade.presentation.util.secondaryItemAlpha import eu.kanade.tachiyomi.R @Composable @@ -46,98 +33,56 @@ fun NewUpdateScreen( onRejectUpdate: () -> Unit, onAcceptUpdate: () -> Unit, ) { - Scaffold( - bottomBar = { - val strokeWidth = Dp.Hairline - val borderColor = MaterialTheme.colorScheme.outline - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .drawBehind { - drawLine( - borderColor, - Offset(0f, 0f), - Offset(size.width, 0f), - strokeWidth.value, - ) - } - .windowInsetsPadding(NavigationBarDefaults.windowInsets) - .padding( - horizontal = MaterialTheme.padding.medium, - vertical = MaterialTheme.padding.small, - ), - ) { - TextButton( - modifier = Modifier.fillMaxWidth(), - onClick = onAcceptUpdate, - ) { - Text(text = stringResource(id = R.string.update_check_confirm)) - } - TextButton( - modifier = Modifier.fillMaxWidth(), - onClick = onRejectUpdate, - ) { - Text(text = stringResource(R.string.action_not_now)) - } - } - }, - ) { paddingValues -> - // Status bar scrim - Box( + InfoScaffold( + icon = Icons.Outlined.NewReleases, + headingText = stringResource(R.string.update_check_notification_update_available), + subtitleText = versionName, + acceptText = stringResource(id = R.string.update_check_confirm), + onAcceptClick = onAcceptUpdate, + rejectText = stringResource(R.string.action_not_now), + onRejectClick = onRejectUpdate, + ) { + Material3RichText( modifier = Modifier - .zIndex(2f) - .secondaryItemAlpha() - .background(MaterialTheme.colorScheme.background) .fillMaxWidth() - .height(paddingValues.calculateTopPadding()), - ) - - Column( - modifier = Modifier - .verticalScroll(rememberScrollState()) - .padding(paddingValues) - .padding(top = 48.dp) - .padding(horizontal = MaterialTheme.padding.medium), - ) { - Icon( - imageVector = Icons.Outlined.NewReleases, - contentDescription = null, - modifier = Modifier - .padding(bottom = MaterialTheme.padding.small) - .size(48.dp), - tint = MaterialTheme.colorScheme.primary, - ) - Text( - text = stringResource(R.string.update_check_notification_update_available), - style = MaterialTheme.typography.headlineLarge, - ) - Text( - text = versionName, - modifier = Modifier.secondaryItemAlpha(), - style = MaterialTheme.typography.titleSmall, - ) - - Material3RichText( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = MaterialTheme.padding.large), - style = RichTextStyle( - stringStyle = RichTextStringStyle( - linkStyle = SpanStyle(color = MaterialTheme.colorScheme.primary), - ), + .padding(vertical = MaterialTheme.padding.large), + style = RichTextStyle( + stringStyle = RichTextStringStyle( + linkStyle = SpanStyle(color = MaterialTheme.colorScheme.primary), ), - ) { - Markdown(content = changelogInfo) + ), + ) { + Markdown(content = changelogInfo) - TextButton( - onClick = onOpenInBrowser, - modifier = Modifier.padding(top = MaterialTheme.padding.small), - ) { - Text(text = stringResource(R.string.update_check_open)) - Spacer(modifier = Modifier.width(MaterialTheme.padding.tiny)) - Icon(imageVector = Icons.Default.OpenInNew, contentDescription = null) - } + TextButton( + onClick = onOpenInBrowser, + modifier = Modifier.padding(top = MaterialTheme.padding.small), + ) { + Text(text = stringResource(R.string.update_check_open)) + Spacer(modifier = Modifier.width(MaterialTheme.padding.tiny)) + Icon(imageVector = Icons.Default.OpenInNew, contentDescription = null) } } } } + +@ThemePreviews +@Composable +private fun NewUpdateScreenPreview() { + TachiyomiTheme { + NewUpdateScreen( + versionName = "v0.99.9", + changelogInfo = """ + ## Yay + Foobar + + ### More info + - Hello + - World + """.trimIndent(), + onOpenInBrowser = {}, + onRejectUpdate = {}, + onAcceptUpdate = {}, + ) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 04ab0d45c2..02fb892ad8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -377,14 +377,13 @@ class Downloader( } val digitCount = (download.pages?.size ?: 0).toString().length.coerceAtLeast(3) - val filename = String.format("%0${digitCount}d", page.number) val tmpFile = tmpDir.findFile("$filename.tmp") - // Delete temp file if it exists. + // Delete temp file if it exists tmpFile?.delete() - // Try to find the image file. + // Try to find the image file val imageFile = tmpDir.listFiles()?.firstOrNull { it.name!!.startsWith("$filename.") || it.name!!.startsWith("${filename}__001") } // If the image is already downloaded, do nothing. Otherwise download from network @@ -492,7 +491,7 @@ class Downloader( val imageFile = tmpDir.listFiles()?.firstOrNull { it.name.orEmpty().startsWith(filenamePrefix) } ?: throw Error(context.getString(R.string.download_notifier_split_page_not_found, page.number)) - // Check if the original page was previously splitted before then skip. + // If the original page was previously split, then skip if (imageFile.name.orEmpty().startsWith("${filenamePrefix}__")) return true return try { @@ -521,7 +520,7 @@ class Downloader( val downloadPageCount = download.pages?.size ?: return // Ensure that all pages has been downloaded if (download.downloadedImages < downloadPageCount) return - // Ensure that the chapter folder has all the pages. + // Ensure that the chapter folder has all the pages val downloadedImagesCount = tmpDir.listFiles().orEmpty().count { val fileName = it.name.orEmpty() when { @@ -542,7 +541,8 @@ class Downloader( // download.chapter.toDomainChapter()!!, // chapterUrl, // ) - // Only rename the directory if it's downloaded. + + // Only rename the directory if it's downloaded if (downloadPreferences.saveChaptersAsCBZ().get()) { archiveChapter(mangaDir, dirname, tmpDir) } else { @@ -621,7 +621,7 @@ class Downloader( private fun completeDownload(download: Download) { // Delete successful downloads from queue if (download.status == Download.State.DOWNLOADED) { - // remove downloaded chapter from queue + // Remove downloaded chapter from queue queue.remove(download) } if (areAllDownloadsFinished()) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreen.kt index 7951250acc..f634f38c3f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/NewUpdateScreen.kt @@ -16,6 +16,7 @@ class NewUpdateScreen( private val releaseLink: String, private val downloadLink: String, ) : Screen { + @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow @@ -23,6 +24,7 @@ class NewUpdateScreen( val changelogInfoNoChecksum = remember { changelogInfo.replace("""---(\R|.)*Checksums(\R|.)*""".toRegex(), "") } + NewUpdateScreen( versionName = versionName, changelogInfo = changelogInfoNoChecksum, diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 2085e32a8f..6cbd16821d 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -782,7 +782,7 @@ Not installed - An Unexpected Error Occurred + Whoops! %s ran into an unexpected error. We suggest you screenshot this message, dump the crash logs, and then share it in our support channel on Discord. Restart the application