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