|
@@ -2,60 +2,57 @@ package eu.kanade.presentation.reader
|
|
|
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
|
import androidx.compose.foundation.layout.Column
|
|
|
-import androidx.compose.foundation.layout.ColumnScope
|
|
|
-import androidx.compose.foundation.layout.FlowRow
|
|
|
import androidx.compose.foundation.layout.Row
|
|
|
import androidx.compose.foundation.layout.Spacer
|
|
|
+import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
import androidx.compose.foundation.layout.height
|
|
|
-import androidx.compose.foundation.layout.width
|
|
|
+import androidx.compose.foundation.layout.padding
|
|
|
+import androidx.compose.foundation.layout.widthIn
|
|
|
+import androidx.compose.foundation.text.InlineTextContent
|
|
|
+import androidx.compose.foundation.text.appendInlineContent
|
|
|
import androidx.compose.material.icons.Icons
|
|
|
+import androidx.compose.material.icons.outlined.Info
|
|
|
import androidx.compose.material.icons.outlined.OfflinePin
|
|
|
import androidx.compose.material.icons.outlined.Warning
|
|
|
+import androidx.compose.material3.CardColors
|
|
|
+import androidx.compose.material3.CardDefaults
|
|
|
import androidx.compose.material3.Icon
|
|
|
-import androidx.compose.material3.LocalContentColor
|
|
|
import androidx.compose.material3.MaterialTheme
|
|
|
+import androidx.compose.material3.OutlinedCard
|
|
|
import androidx.compose.material3.ProvideTextStyle
|
|
|
+import androidx.compose.material3.Surface
|
|
|
import androidx.compose.material3.Text
|
|
|
import androidx.compose.runtime.Composable
|
|
|
import androidx.compose.ui.Alignment
|
|
|
import androidx.compose.ui.Modifier
|
|
|
+import androidx.compose.ui.graphics.Color
|
|
|
import androidx.compose.ui.res.pluralStringResource
|
|
|
import androidx.compose.ui.res.stringResource
|
|
|
-import androidx.compose.ui.text.font.FontWeight
|
|
|
-import androidx.compose.ui.text.style.TextAlign
|
|
|
+import androidx.compose.ui.text.Placeholder
|
|
|
+import androidx.compose.ui.text.PlaceholderVerticalAlign
|
|
|
+import androidx.compose.ui.text.buildAnnotatedString
|
|
|
+import androidx.compose.ui.text.style.TextOverflow
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
+import androidx.compose.ui.unit.sp
|
|
|
+import eu.kanade.presentation.theme.TachiyomiTheme
|
|
|
import eu.kanade.tachiyomi.R
|
|
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
|
|
+import eu.kanade.tachiyomi.data.database.models.ChapterImpl
|
|
|
import eu.kanade.tachiyomi.data.database.models.toDomainChapter
|
|
|
-import eu.kanade.tachiyomi.data.download.DownloadManager
|
|
|
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
|
|
|
+import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
|
|
import tachiyomi.domain.chapter.service.calculateChapterGap
|
|
|
-import tachiyomi.domain.manga.model.Manga
|
|
|
-import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
|
|
+import tachiyomi.presentation.core.util.ThemePreviews
|
|
|
+import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|
|
|
|
|
@Composable
|
|
|
fun ChapterTransition(
|
|
|
transition: ChapterTransition,
|
|
|
- downloadManager: DownloadManager,
|
|
|
- manga: Manga?,
|
|
|
+ currChapterDownloaded: Boolean,
|
|
|
+ goingToChapterDownloaded: Boolean,
|
|
|
) {
|
|
|
- manga ?: return
|
|
|
-
|
|
|
val currChapter = transition.from.chapter
|
|
|
- val currChapterDownloaded = transition.from.pageLoader?.isLocal == true
|
|
|
-
|
|
|
val goingToChapter = transition.to?.chapter
|
|
|
- val goingToChapterDownloaded = if (goingToChapter != null) {
|
|
|
- downloadManager.isChapterDownloaded(
|
|
|
- goingToChapter.name,
|
|
|
- goingToChapter.scanlator,
|
|
|
- manga.title,
|
|
|
- manga.source,
|
|
|
- skipCache = true,
|
|
|
- )
|
|
|
- } else {
|
|
|
- false
|
|
|
- }
|
|
|
|
|
|
ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
|
|
|
when (transition) {
|
|
@@ -90,80 +87,289 @@ fun ChapterTransition(
|
|
|
@Composable
|
|
|
private fun TransitionText(
|
|
|
topLabel: String,
|
|
|
- topChapter: Chapter? = null,
|
|
|
+ topChapter: Chapter?,
|
|
|
topChapterDownloaded: Boolean,
|
|
|
bottomLabel: String,
|
|
|
- bottomChapter: Chapter? = null,
|
|
|
+ bottomChapter: Chapter?,
|
|
|
bottomChapterDownloaded: Boolean,
|
|
|
fallbackLabel: String,
|
|
|
chapterGap: Int,
|
|
|
) {
|
|
|
- val hasTopChapter = topChapter != null
|
|
|
- val hasBottomChapter = bottomChapter != null
|
|
|
+ Column(
|
|
|
+ modifier = Modifier
|
|
|
+ .widthIn(max = 460.dp)
|
|
|
+ .fillMaxWidth(),
|
|
|
+ ) {
|
|
|
+ if (topChapter != null) {
|
|
|
+ ChapterText(
|
|
|
+ header = topLabel,
|
|
|
+ name = topChapter.name,
|
|
|
+ scanlator = topChapter.scanlator,
|
|
|
+ downloaded = topChapterDownloaded,
|
|
|
+ )
|
|
|
|
|
|
- Column {
|
|
|
- Text(
|
|
|
- text = if (hasTopChapter) topLabel else fallbackLabel,
|
|
|
- fontWeight = FontWeight.Bold,
|
|
|
- textAlign = if (hasTopChapter) TextAlign.Start else TextAlign.Center,
|
|
|
- )
|
|
|
- topChapter?.let { ChapterText(chapter = it, downloaded = topChapterDownloaded) }
|
|
|
-
|
|
|
- Spacer(Modifier.height(16.dp))
|
|
|
-
|
|
|
- if (chapterGap > 0) {
|
|
|
- Row(
|
|
|
- horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
|
- verticalAlignment = Alignment.CenterVertically,
|
|
|
- ) {
|
|
|
- Icon(
|
|
|
- imageVector = Icons.Outlined.Warning,
|
|
|
- tint = MaterialTheme.colorScheme.error,
|
|
|
- contentDescription = null,
|
|
|
- )
|
|
|
+ Spacer(Modifier.height(VerticalSpacerSize))
|
|
|
+ } else {
|
|
|
+ NoChapterNotification(
|
|
|
+ text = fallbackLabel,
|
|
|
+ modifier = Modifier.align(Alignment.CenterHorizontally),
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
- Text(text = pluralStringResource(R.plurals.missing_chapters_warning, count = chapterGap, chapterGap))
|
|
|
+ if (bottomChapter != null) {
|
|
|
+ if (chapterGap > 0) {
|
|
|
+ ChapterGapWarning(
|
|
|
+ gapCount = chapterGap,
|
|
|
+ modifier = Modifier.align(Alignment.CenterHorizontally),
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
- Spacer(Modifier.height(16.dp))
|
|
|
- }
|
|
|
+ Spacer(Modifier.height(VerticalSpacerSize))
|
|
|
|
|
|
- Text(
|
|
|
- text = if (hasBottomChapter) bottomLabel else fallbackLabel,
|
|
|
- fontWeight = FontWeight.Bold,
|
|
|
- textAlign = if (hasBottomChapter) TextAlign.Start else TextAlign.Center,
|
|
|
- )
|
|
|
- bottomChapter?.let { ChapterText(chapter = it, downloaded = bottomChapterDownloaded) }
|
|
|
+ ChapterText(
|
|
|
+ header = bottomLabel,
|
|
|
+ name = bottomChapter.name,
|
|
|
+ scanlator = bottomChapter.scanlator,
|
|
|
+ downloaded = bottomChapterDownloaded,
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ NoChapterNotification(
|
|
|
+ text = fallbackLabel,
|
|
|
+ modifier = Modifier.align(Alignment.CenterHorizontally),
|
|
|
+ )
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@Composable
|
|
|
-private fun ColumnScope.ChapterText(
|
|
|
- chapter: Chapter,
|
|
|
- downloaded: Boolean,
|
|
|
+private fun NoChapterNotification(
|
|
|
+ text: String,
|
|
|
+ modifier: Modifier = Modifier,
|
|
|
) {
|
|
|
- FlowRow(
|
|
|
- verticalAlignment = Alignment.CenterVertically,
|
|
|
+ OutlinedCard(
|
|
|
+ modifier = modifier,
|
|
|
+ colors = CardColor,
|
|
|
) {
|
|
|
- if (downloaded) {
|
|
|
+ Row(
|
|
|
+ modifier = Modifier
|
|
|
+ .padding(horizontal = 16.dp, vertical = 12.dp),
|
|
|
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
|
|
|
+ verticalAlignment = Alignment.CenterVertically,
|
|
|
+ ) {
|
|
|
Icon(
|
|
|
- imageVector = Icons.Outlined.OfflinePin,
|
|
|
- contentDescription = stringResource(R.string.label_downloaded),
|
|
|
+ imageVector = Icons.Outlined.Info,
|
|
|
+ tint = MaterialTheme.colorScheme.primary,
|
|
|
+ contentDescription = null,
|
|
|
)
|
|
|
|
|
|
- Spacer(Modifier.width(8.dp))
|
|
|
+ Text(
|
|
|
+ text = text,
|
|
|
+ style = MaterialTheme.typography.bodyMedium,
|
|
|
+ )
|
|
|
}
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
- Text(chapter.name)
|
|
|
+@Composable
|
|
|
+private fun ChapterGapWarning(
|
|
|
+ gapCount: Int,
|
|
|
+ modifier: Modifier = Modifier,
|
|
|
+) {
|
|
|
+ OutlinedCard(
|
|
|
+ modifier = modifier,
|
|
|
+ colors = CardColor,
|
|
|
+ ) {
|
|
|
+ Row(
|
|
|
+ modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
|
|
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
|
|
|
+ verticalAlignment = Alignment.CenterVertically,
|
|
|
+ ) {
|
|
|
+ Icon(
|
|
|
+ imageVector = Icons.Outlined.Warning,
|
|
|
+ tint = MaterialTheme.colorScheme.error,
|
|
|
+ contentDescription = null,
|
|
|
+ )
|
|
|
+
|
|
|
+ Text(
|
|
|
+ text = pluralStringResource(R.plurals.missing_chapters_warning, count = gapCount, gapCount),
|
|
|
+ style = MaterialTheme.typography.bodyMedium,
|
|
|
+ )
|
|
|
+ }
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+@Composable
|
|
|
+private fun ChapterHeaderText(
|
|
|
+ text: String,
|
|
|
+ modifier: Modifier = Modifier,
|
|
|
+) {
|
|
|
+ Text(
|
|
|
+ text = text,
|
|
|
+ modifier = modifier,
|
|
|
+ style = MaterialTheme.typography.titleMedium,
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+@Composable
|
|
|
+private fun ChapterText(
|
|
|
+ header: String,
|
|
|
+ name: String,
|
|
|
+ scanlator: String?,
|
|
|
+ downloaded: Boolean,
|
|
|
+) {
|
|
|
+ Column {
|
|
|
+ ChapterHeaderText(
|
|
|
+ text = header,
|
|
|
+ modifier = Modifier.padding(bottom = 4.dp),
|
|
|
+ )
|
|
|
|
|
|
- chapter.scanlator?.let {
|
|
|
- ProvideTextStyle(
|
|
|
- MaterialTheme.typography.bodyMedium.copy(
|
|
|
- color = LocalContentColor.current.copy(alpha = SecondaryItemAlpha),
|
|
|
+ Text(
|
|
|
+ text = buildAnnotatedString {
|
|
|
+ if (downloaded) {
|
|
|
+ appendInlineContent(DownloadedIconContentId)
|
|
|
+ append(' ')
|
|
|
+ }
|
|
|
+ append(name)
|
|
|
+ },
|
|
|
+ fontSize = 20.sp,
|
|
|
+ maxLines = 5,
|
|
|
+ overflow = TextOverflow.Ellipsis,
|
|
|
+ style = MaterialTheme.typography.titleLarge,
|
|
|
+ inlineContent = mapOf(
|
|
|
+ DownloadedIconContentId to InlineTextContent(
|
|
|
+ Placeholder(
|
|
|
+ width = 22.sp,
|
|
|
+ height = 22.sp,
|
|
|
+ placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
|
|
|
+ ),
|
|
|
+ ) {
|
|
|
+ Icon(
|
|
|
+ imageVector = Icons.Outlined.OfflinePin,
|
|
|
+ contentDescription = stringResource(R.string.label_downloaded),
|
|
|
+ )
|
|
|
+ },
|
|
|
),
|
|
|
- ) {
|
|
|
- Text(it)
|
|
|
+ )
|
|
|
+
|
|
|
+ scanlator?.let {
|
|
|
+ Text(
|
|
|
+ text = it,
|
|
|
+ modifier = Modifier
|
|
|
+ .secondaryItemAlpha()
|
|
|
+ .padding(top = 2.dp),
|
|
|
+ maxLines = 2,
|
|
|
+ overflow = TextOverflow.Ellipsis,
|
|
|
+ style = MaterialTheme.typography.bodySmall,
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+private val CardColor: CardColors
|
|
|
+ @Composable
|
|
|
+ get() = CardDefaults.outlinedCardColors(
|
|
|
+ containerColor = Color.Transparent,
|
|
|
+ contentColor = MaterialTheme.colorScheme.onSurface,
|
|
|
+ )
|
|
|
+
|
|
|
+private val VerticalSpacerSize = 24.dp
|
|
|
+private const val DownloadedIconContentId = "downloaded"
|
|
|
+
|
|
|
+private fun previewChapter(name: String, scanlator: String, chapterNumber: Float) = ChapterImpl().apply {
|
|
|
+ this.name = name
|
|
|
+ this.scanlator = scanlator
|
|
|
+ this.chapter_number = chapterNumber
|
|
|
+
|
|
|
+ this.id = 0
|
|
|
+ this.manga_id = 0
|
|
|
+ this.url = ""
|
|
|
+}
|
|
|
+private val FakeChapter = previewChapter(
|
|
|
+ name = "Vol.1, Ch.1 - Fake Chapter Title",
|
|
|
+ scanlator = "Scanlator Name",
|
|
|
+ chapterNumber = 1f,
|
|
|
+)
|
|
|
+private val FakeGapChapter = previewChapter(
|
|
|
+ name = "Vol.5, Ch.44 - Fake Gap Chapter Title",
|
|
|
+ scanlator = "Scanlator Name",
|
|
|
+ chapterNumber = 44f,
|
|
|
+)
|
|
|
+private val FakeChapterLongTitle = previewChapter(
|
|
|
+ name = "Vol.1, Ch.0 - The Mundane Musings of a Metafictional Manga: A Chapter About a Chapter, Featuring" +
|
|
|
+ " an Absurdly Long Title and a Surprisingly Normal Day in the Lives of Our Heroes, as They Grapple with the " +
|
|
|
+ "Daily Challenges of Existence, from Paying Rent to Finding Love, All While Navigating the Strange World of " +
|
|
|
+ "Fictional Realities and Reality-Bending Fiction, Where the Fourth Wall is Always in Danger of Being Broken " +
|
|
|
+ "and the Line Between Author and Character is Forever Blurred.",
|
|
|
+ scanlator = "Long Long Funny Scanlator Sniper Group Name Reborn",
|
|
|
+ chapterNumber = 1f,
|
|
|
+)
|
|
|
+
|
|
|
+@ThemePreviews
|
|
|
+@Composable
|
|
|
+private fun TransitionTextPreview() {
|
|
|
+ TachiyomiTheme {
|
|
|
+ Surface(modifier = Modifier.padding(48.dp)) {
|
|
|
+ ChapterTransition(
|
|
|
+ transition = ChapterTransition.Next(ReaderChapter(FakeChapter), ReaderChapter(FakeChapter)),
|
|
|
+ currChapterDownloaded = false,
|
|
|
+ goingToChapterDownloaded = true,
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@ThemePreviews
|
|
|
+@Composable
|
|
|
+private fun TransitionTextLongTitlePreview() {
|
|
|
+ TachiyomiTheme {
|
|
|
+ Surface(modifier = Modifier.padding(48.dp)) {
|
|
|
+ ChapterTransition(
|
|
|
+ transition = ChapterTransition.Next(ReaderChapter(FakeChapterLongTitle), ReaderChapter(FakeChapter)),
|
|
|
+ currChapterDownloaded = true,
|
|
|
+ goingToChapterDownloaded = true,
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@ThemePreviews
|
|
|
+@Composable
|
|
|
+private fun TransitionTextWithGapPreview() {
|
|
|
+ TachiyomiTheme {
|
|
|
+ Surface(modifier = Modifier.padding(48.dp)) {
|
|
|
+ ChapterTransition(
|
|
|
+ transition = ChapterTransition.Next(ReaderChapter(FakeChapter), ReaderChapter(FakeGapChapter)),
|
|
|
+ currChapterDownloaded = true,
|
|
|
+ goingToChapterDownloaded = false,
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@ThemePreviews
|
|
|
+@Composable
|
|
|
+private fun TransitionTextNoNextPreview() {
|
|
|
+ TachiyomiTheme {
|
|
|
+ Surface(modifier = Modifier.padding(48.dp)) {
|
|
|
+ ChapterTransition(
|
|
|
+ transition = ChapterTransition.Next(ReaderChapter(FakeChapter), null),
|
|
|
+ currChapterDownloaded = true,
|
|
|
+ goingToChapterDownloaded = false,
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@ThemePreviews
|
|
|
+@Composable
|
|
|
+private fun TransitionTextNoPreviousPreview() {
|
|
|
+ TachiyomiTheme {
|
|
|
+ Surface(modifier = Modifier.padding(48.dp)) {
|
|
|
+ ChapterTransition(
|
|
|
+ transition = ChapterTransition.Prev(ReaderChapter(FakeChapter), null),
|
|
|
+ currChapterDownloaded = true,
|
|
|
+ goingToChapterDownloaded = false,
|
|
|
+ )
|
|
|
}
|
|
|
}
|
|
|
}
|