From 0b125b7106570841a100b6a4b504533386315bbe Mon Sep 17 00:00:00 2001 From: arkon Date: Sat, 22 Apr 2023 16:33:36 -0400 Subject: [PATCH] Use Compose for reader transition chapter info (#9373) --- .../presentation/reader/ChapterTransition.kt | 170 ++++++++++++++++++ .../ui/reader/viewer/ReaderTransitionView.kt | 156 ++-------------- .../tachiyomi/util/view/ViewExtensions.kt | 18 ++ .../main/res/layout/compose_controller.xml | 4 - .../res/layout/reader_transition_view.xml | 50 ------ 5 files changed, 202 insertions(+), 196 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt delete mode 100644 app/src/main/res/layout/compose_controller.xml delete mode 100644 app/src/main/res/layout/reader_transition_view.xml diff --git a/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt b/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt new file mode 100644 index 0000000000..b2ac9734f7 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt @@ -0,0 +1,170 @@ +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.height +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.OfflinePin +import androidx.compose.material.icons.outlined.Warning +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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.unit.dp +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.data.database.models.toDomainChapter +import eu.kanade.tachiyomi.data.download.DownloadManager +import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader +import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition +import tachiyomi.domain.chapter.service.calculateChapterGap +import tachiyomi.domain.manga.model.Manga +import tachiyomi.presentation.core.components.material.SecondaryItemAlpha + +@Composable +fun ChapterTransition( + transition: ChapterTransition, + downloadManager: DownloadManager, + manga: Manga?, +) { + manga ?: return + + val currChapter = transition.from.chapter + val currChapterDownloaded = transition.from.pageLoader is DownloadPageLoader + + 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) { + is ChapterTransition.Prev -> { + TransitionText( + topLabel = stringResource(R.string.transition_previous), + topChapter = goingToChapter, + topChapterDownloaded = goingToChapterDownloaded, + bottomLabel = stringResource(R.string.transition_current), + bottomChapter = currChapter, + bottomChapterDownloaded = currChapterDownloaded, + fallbackLabel = stringResource(R.string.transition_no_previous), + chapterGap = calculateChapterGap(currChapter.toDomainChapter(), goingToChapter?.toDomainChapter()), + ) + } + is ChapterTransition.Next -> { + TransitionText( + topLabel = stringResource(R.string.transition_finished), + topChapter = currChapter, + topChapterDownloaded = currChapterDownloaded, + bottomLabel = stringResource(R.string.transition_next), + bottomChapter = goingToChapter, + bottomChapterDownloaded = goingToChapterDownloaded, + fallbackLabel = stringResource(R.string.transition_no_next), + chapterGap = calculateChapterGap(goingToChapter?.toDomainChapter(), currChapter.toDomainChapter()), + ) + } + } + } +} + +@Composable +private fun TransitionText( + topLabel: String, + topChapter: Chapter? = null, + topChapterDownloaded: Boolean, + bottomLabel: String, + bottomChapter: Chapter? = null, + bottomChapterDownloaded: Boolean, + fallbackLabel: String, + chapterGap: Int, +) { + val hasTopChapter = topChapter != null + val hasBottomChapter = bottomChapter != null + + 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)) + } + + 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) } + } +} + +@Composable +private fun ColumnScope.ChapterText( + chapter: Chapter, + downloaded: Boolean, +) { + FlowRow( + verticalAlignment = Alignment.CenterVertically, + ) { + if (downloaded) { + Icon( + imageVector = Icons.Outlined.OfflinePin, + contentDescription = stringResource(R.string.label_downloaded), + ) + + Spacer(Modifier.width(8.dp)) + } + + Text(chapter.name) + } + + chapter.scanlator?.let { + ProvideTextStyle( + MaterialTheme.typography.bodyMedium.copy( + color = LocalContentColor.current.copy(alpha = SecondaryItemAlpha), + ), + ) { + Text(it) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt index 9efeb7988e..250aaa15ad 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt @@ -1,30 +1,17 @@ package eu.kanade.tachiyomi.ui.reader.viewer import android.content.Context -import android.text.SpannableStringBuilder -import android.text.style.ImageSpan import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.LinearLayout -import androidx.core.content.ContextCompat -import androidx.core.text.bold -import androidx.core.text.buildSpannedString -import androidx.core.text.inSpans -import androidx.core.view.isVisible -import eu.kanade.tachiyomi.R +import android.widget.FrameLayout +import androidx.compose.ui.platform.ComposeView +import eu.kanade.presentation.reader.ChapterTransition import eu.kanade.tachiyomi.data.download.DownloadManager -import eu.kanade.tachiyomi.databinding.ReaderTransitionViewBinding -import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition -import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.view.setComposeContent import tachiyomi.domain.manga.model.Manga -import kotlin.math.roundToInt class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - LinearLayout(context, attrs) { - - private val binding: ReaderTransitionViewBinding = - ReaderTransitionViewBinding.inflate(LayoutInflater.from(context), this, true) + FrameLayout(context, attrs) { init { layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) @@ -32,133 +19,18 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At fun bind(transition: ChapterTransition, downloadManager: DownloadManager, manga: Manga?) { manga ?: return - when (transition) { - is ChapterTransition.Prev -> bindPrevChapterTransition(transition, downloadManager, manga) - is ChapterTransition.Next -> bindNextChapterTransition(transition, downloadManager, manga) - } - missingChapterWarning(transition) - } - /** - * Binds a previous chapter transition on this view and subscribes to the page load status. - */ - private fun bindPrevChapterTransition( - transition: ChapterTransition, - downloadManager: DownloadManager, - manga: Manga, - ) { - val prevChapter = transition.to?.chapter + removeAllViews() - binding.lowerText.isVisible = prevChapter != null - if (prevChapter != null) { - binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START - val isPrevDownloaded = downloadManager.isChapterDownloaded( - prevChapter.name, - prevChapter.scanlator, - manga.title, - manga.source, - skipCache = true, - ) - val isCurrentDownloaded = transition.from.pageLoader is DownloadPageLoader - binding.upperText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_previous)) } - append("\n${prevChapter.name}") - if (!prevChapter.scanlator.isNullOrBlank()) { - append(DOT_SEPARATOR) - append("${prevChapter.scanlator}") - } - if (isPrevDownloaded) addDLImageSpan() + val transitionView = ComposeView(context).apply { + setComposeContent { + ChapterTransition( + transition = transition, + downloadManager = downloadManager, + manga = manga, + ) } - binding.lowerText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_current)) } - append("\n${transition.from.chapter.name}") - if (!transition.from.chapter.scanlator.isNullOrBlank()) { - append(DOT_SEPARATOR) - append("${transition.from.chapter.scanlator}") - } - if (isCurrentDownloaded) addDLImageSpan() - } - } else { - binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER - binding.upperText.text = context.getString(R.string.transition_no_previous) } - } - - /** - * Binds a next chapter transition on this view and subscribes to the load status. - */ - private fun bindNextChapterTransition( - transition: ChapterTransition, - downloadManager: DownloadManager, - manga: Manga, - ) { - val nextChapter = transition.to?.chapter - - binding.lowerText.isVisible = nextChapter != null - if (nextChapter != null) { - binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START - val isCurrentDownloaded = transition.from.pageLoader is DownloadPageLoader - val isNextDownloaded = downloadManager.isChapterDownloaded( - nextChapter.name, - nextChapter.scanlator, - manga.title, - manga.source, - skipCache = true, - ) - binding.upperText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_finished)) } - append("\n${transition.from.chapter.name}") - if (!transition.from.chapter.scanlator.isNullOrBlank()) { - append(DOT_SEPARATOR) - append("${transition.from.chapter.scanlator}") - } - if (isCurrentDownloaded) addDLImageSpan() - } - binding.lowerText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_next)) } - append("\n${nextChapter.name}") - if (!nextChapter.scanlator.isNullOrBlank()) { - append(DOT_SEPARATOR) - append("${nextChapter.scanlator}") - } - if (isNextDownloaded) addDLImageSpan() - } - } else { - binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER - binding.upperText.text = context.getString(R.string.transition_no_next) - } - } - - private fun SpannableStringBuilder.addDLImageSpan() { - val icon = ContextCompat.getDrawable(context, R.drawable.ic_offline_pin_24dp)?.mutate() - ?.apply { - val size = binding.lowerText.textSize + 4.dpToPx - setTint(binding.lowerText.currentTextColor) - setBounds(0, 0, size.roundToInt(), size.roundToInt()) - } ?: return - append(" ") - inSpans(ImageSpan(icon)) { append("image") } - } - - private fun missingChapterWarning(transition: ChapterTransition) { - if (transition.to == null) { - binding.warning.isVisible = false - return - } - - val chapterGap = when (transition) { - is ChapterTransition.Prev -> calculateChapterGap(transition.from, transition.to) - is ChapterTransition.Next -> calculateChapterGap(transition.to, transition.from) - } - - if (chapterGap == 0) { - binding.warning.isVisible = false - return - } - - binding.warningText.text = resources.getQuantityString(R.plurals.missing_chapters_warning, chapterGap.toInt(), chapterGap.toInt()) - binding.warning.isVisible = true + addView(transitionView) } } - -private const val DOT_SEPARATOR = " • " diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 92f21074d6..60eaacbe93 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -25,6 +25,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionContext import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.view.forEach import com.google.android.material.shape.MaterialShapeDrawable import eu.kanade.presentation.theme.TachiyomiTheme @@ -47,6 +49,22 @@ inline fun ComponentActivity.setComposeContent( } } +fun ComposeView.setComposeContent( + content: @Composable () -> Unit, +) { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + TachiyomiTheme { + CompositionLocalProvider( + LocalTextStyle provides MaterialTheme.typography.bodySmall, + LocalContentColor provides MaterialTheme.colorScheme.onBackground, + ) { + content() + } + } + } +} + /** * Adds a tooltip shown on long press. * diff --git a/app/src/main/res/layout/compose_controller.xml b/app/src/main/res/layout/compose_controller.xml deleted file mode 100644 index 617287296b..0000000000 --- a/app/src/main/res/layout/compose_controller.xml +++ /dev/null @@ -1,4 +0,0 @@ - - diff --git a/app/src/main/res/layout/reader_transition_view.xml b/app/src/main/res/layout/reader_transition_view.xml deleted file mode 100644 index 2ec51224b2..0000000000 --- a/app/src/main/res/layout/reader_transition_view.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - -