mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-21 20:47:03 -05:00
Chapter transition tweaks (#9470)
* Chapter transition tweaks * Chapter transition cleanups
This commit is contained in:
parent
332d9ff61b
commit
d36cf5ce15
2 changed files with 339 additions and 97 deletions
|
@ -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,
|
||||
)
|
||||
|
||||
Text(text = pluralStringResource(R.plurals.missing_chapters_warning, count = chapterGap, chapterGap))
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Spacer(Modifier.height(VerticalSpacerSize))
|
||||
} else {
|
||||
NoChapterNotification(
|
||||
text = fallbackLabel,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
}
|
||||
|
||||
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) }
|
||||
if (bottomChapter != null) {
|
||||
if (chapterGap > 0) {
|
||||
ChapterGapWarning(
|
||||
gapCount = chapterGap,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(VerticalSpacerSize))
|
||||
|
||||
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(chapter.name)
|
||||
}
|
||||
|
||||
chapter.scanlator?.let {
|
||||
ProvideTextStyle(
|
||||
MaterialTheme.typography.bodyMedium.copy(
|
||||
color = LocalContentColor.current.copy(alpha = SecondaryItemAlpha),
|
||||
),
|
||||
) {
|
||||
Text(it)
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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),
|
||||
)
|
||||
|
||||
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),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,35 +2,71 @@ package eu.kanade.tachiyomi.ui.reader.viewer
|
|||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.AbstractComposeView
|
||||
import eu.kanade.presentation.reader.ChapterTransition
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
|
||||
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
|
||||
class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
FrameLayout(context, attrs) {
|
||||
AbstractComposeView(context, attrs) {
|
||||
|
||||
private var data: Data? by mutableStateOf(null)
|
||||
|
||||
init {
|
||||
layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
|
||||
fun bind(transition: ChapterTransition, downloadManager: DownloadManager, manga: Manga?) {
|
||||
manga ?: return
|
||||
data = if (manga != null) {
|
||||
Data(
|
||||
transition = transition,
|
||||
currChapterDownloaded = transition.from.pageLoader?.isLocal == true,
|
||||
goingToChapterDownloaded = transition.to?.chapter?.let { goingToChapter ->
|
||||
downloadManager.isChapterDownloaded(
|
||||
chapterName = goingToChapter.name,
|
||||
chapterScanlator = goingToChapter.scanlator,
|
||||
mangaTitle = manga.title,
|
||||
sourceId = manga.source,
|
||||
skipCache = true,
|
||||
)
|
||||
} ?: false,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
removeAllViews()
|
||||
|
||||
val transitionView = ComposeView(context).apply {
|
||||
setComposeContent {
|
||||
ChapterTransition(
|
||||
transition = transition,
|
||||
downloadManager = downloadManager,
|
||||
manga = manga,
|
||||
)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
data?.let {
|
||||
TachiyomiTheme {
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides MaterialTheme.typography.bodySmall,
|
||||
LocalContentColor provides MaterialTheme.colorScheme.onBackground,
|
||||
) {
|
||||
ChapterTransition(
|
||||
transition = it.transition,
|
||||
currChapterDownloaded = it.currChapterDownloaded,
|
||||
goingToChapterDownloaded = it.goingToChapterDownloaded,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
addView(transitionView)
|
||||
}
|
||||
|
||||
private data class Data(
|
||||
val transition: ChapterTransition,
|
||||
val currChapterDownloaded: Boolean,
|
||||
val goingToChapterDownloaded: Boolean,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue