From a77bce7b3735811002be3e80b95dae892029d0f5 Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Sat, 25 Jun 2022 02:42:30 +0700 Subject: [PATCH] ChapterDownloadView: Convert to compose (#7354) --- .../components/ChapterDownloadIndicator.kt | 146 ++++++++++++++++++ .../manga/MangaScreenConstants.kt | 7 + .../ui/manga/chapter/ChapterDownloadView.kt | 105 +++---------- .../ui/manga/chapter/ChapterHolder.kt | 8 +- .../manga/chapter/base/BaseChapterHolder.kt | 53 +------ .../ui/recent/updates/UpdatesHolder.kt | 8 +- 6 files changed, 187 insertions(+), 140 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/components/ChapterDownloadIndicator.kt diff --git a/app/src/main/java/eu/kanade/presentation/components/ChapterDownloadIndicator.kt b/app/src/main/java/eu/kanade/presentation/components/ChapterDownloadIndicator.kt new file mode 100644 index 000000000..fb5c44b58 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/components/ChapterDownloadIndicator.kt @@ -0,0 +1,146 @@ +package eu.kanade.presentation.components + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDownward +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalMinimumTouchTargetEnforcement +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProgressIndicatorDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import eu.kanade.presentation.manga.ChapterDownloadAction +import eu.kanade.presentation.util.secondaryItemAlpha +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.download.model.Download + +@Composable +fun ChapterDownloadIndicator( + modifier: Modifier = Modifier, + downloadState: Download.State, + downloadProgress: Int, + onClick: (ChapterDownloadAction) -> Unit, +) { + Box(modifier = modifier, contentAlignment = Alignment.Center) { + CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) { + val isDownloaded = downloadState == Download.State.DOWNLOADED + val isDownloading = downloadState != Download.State.NOT_DOWNLOADED + var isMenuExpanded by remember(downloadState) { mutableStateOf(false) } + IconButton( + onClick = { + if (isDownloaded || isDownloading) { + isMenuExpanded = true + } else { + onClick(ChapterDownloadAction.START) + } + }, + ) { + if (isDownloaded) { + Icon( + imageVector = Icons.Default.CheckCircle, + contentDescription = null, + modifier = Modifier.size(IndicatorSize), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) { + DropdownMenuItem( + text = { Text(text = stringResource(id = R.string.action_delete)) }, + onClick = { + onClick(ChapterDownloadAction.DELETE) + isMenuExpanded = false + }, + ) + } + } else { + val progressIndicatorModifier = Modifier + .size(IndicatorSize) + .padding(IndicatorStrokeWidth) + val inactiveAlphaModifier = if (!isDownloading) Modifier.secondaryItemAlpha() else Modifier + val arrowModifier = Modifier + .size(IndicatorSize - 7.dp) + .then(inactiveAlphaModifier) + val arrowColor: Color + val strokeColor = MaterialTheme.colorScheme.onSurfaceVariant + if (isDownloading) { + val indeterminate = downloadState == Download.State.QUEUE || + (downloadState == Download.State.DOWNLOADING && downloadProgress == 0) + if (indeterminate) { + arrowColor = strokeColor + CircularProgressIndicator( + modifier = progressIndicatorModifier, + color = strokeColor, + strokeWidth = IndicatorStrokeWidth, + ) + } else { + val animatedProgress by animateFloatAsState( + targetValue = downloadProgress / 100f, + animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, + ) + arrowColor = if (animatedProgress < 0.5f) { + strokeColor + } else { + MaterialTheme.colorScheme.background + } + CircularProgressIndicator( + progress = animatedProgress, + modifier = progressIndicatorModifier, + color = strokeColor, + strokeWidth = IndicatorSize / 2, + ) + } + } else { + arrowColor = strokeColor + CircularProgressIndicator( + progress = 1f, + modifier = progressIndicatorModifier.then(inactiveAlphaModifier), + color = strokeColor, + strokeWidth = IndicatorStrokeWidth, + ) + } + Icon( + imageVector = Icons.Default.ArrowDownward, + contentDescription = null, + modifier = arrowModifier, + tint = arrowColor, + ) + DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) { + DropdownMenuItem( + text = { Text(text = stringResource(id = R.string.action_start_downloading_now)) }, + onClick = { + onClick(ChapterDownloadAction.START_NOW) + isMenuExpanded = false + }, + ) + DropdownMenuItem( + text = { Text(text = stringResource(id = R.string.action_cancel)) }, + onClick = { + onClick(ChapterDownloadAction.CANCEL) + isMenuExpanded = false + }, + ) + } + } + } + } + } +} + +private val IndicatorSize = 26.dp +private val IndicatorStrokeWidth = 2.dp diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt index ec8eebc76..86c217fa4 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreenConstants.kt @@ -4,3 +4,10 @@ enum class EditCoverAction { EDIT, DELETE, } + +enum class ChapterDownloadAction { + START, + START_NOW, + CANCEL, + DELETE, +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterDownloadView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterDownloadView.kt index 9c294a9bc..593195d35 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterDownloadView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterDownloadView.kt @@ -1,95 +1,40 @@ package eu.kanade.tachiyomi.ui.manga.chapter import android.content.Context -import android.content.res.ColorStateList import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.FrameLayout -import androidx.core.view.isVisible -import com.google.android.material.progressindicator.BaseProgressIndicator -import eu.kanade.tachiyomi.R +import androidx.compose.runtime.Composable +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.components.ChapterDownloadIndicator +import eu.kanade.presentation.manga.ChapterDownloadAction +import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.data.download.model.Download -import eu.kanade.tachiyomi.databinding.ChapterDownloadViewBinding -import eu.kanade.tachiyomi.util.system.dpToPx -import eu.kanade.tachiyomi.util.system.getThemeColor -import eu.kanade.tachiyomi.util.view.setVectorCompat -class ChapterDownloadView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - FrameLayout(context, attrs) { +class ChapterDownloadView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0, +) : AbstractComposeView(context, attrs, defStyle) { - private val binding: ChapterDownloadViewBinding = - ChapterDownloadViewBinding.inflate(LayoutInflater.from(context), this, false) + private var state by mutableStateOf(Download.State.NOT_DOWNLOADED) + private var progress by mutableStateOf(0) - private var state: Download.State? = null - private var progress = -1 + var listener: (ChapterDownloadAction) -> Unit = {} - init { - addView(binding.root) - } - - fun setState(state: Download.State, progress: Int = -1) { - val isDirty = this.state?.value != state.value || this.progress != progress - if (isDirty) { - updateLayout(state, progress) + @Composable + override fun Content() { + TachiyomiTheme { + ChapterDownloadIndicator( + downloadState = state, + downloadProgress = progress, + onClick = listener, + ) } } - private fun updateLayout(state: Download.State, progress: Int) { - binding.downloadIcon.isVisible = state == Download.State.NOT_DOWNLOADED || - state == Download.State.DOWNLOADING || state == Download.State.QUEUE - binding.downloadIcon.imageTintList = if (state == Download.State.DOWNLOADING && progress > 0) { - ColorStateList.valueOf(context.getThemeColor(android.R.attr.colorBackground)) - } else { - ColorStateList.valueOf(context.getThemeColor(android.R.attr.textColorHint)) - } - - binding.downloadProgress.apply { - val shouldBeVisible = state == Download.State.DOWNLOADING || - state == Download.State.NOT_DOWNLOADED || state == Download.State.QUEUE - if (shouldBeVisible) { - hideAnimationBehavior = BaseProgressIndicator.HIDE_NONE - if (state == Download.State.NOT_DOWNLOADED || state == Download.State.QUEUE) { - trackThickness = 2.dpToPx - setIndicatorColor(context.getThemeColor(android.R.attr.textColorHint)) - if (state == Download.State.NOT_DOWNLOADED) { - if (isIndeterminate) { - hide() - isIndeterminate = false - } - setProgressCompat(100, false) - } else if (!isIndeterminate) { - hide() - isIndeterminate = true - show() - } - } else if (state == Download.State.DOWNLOADING) { - if (isIndeterminate) { - hide() - } - trackThickness = 12.dpToPx - setIndicatorColor(context.getThemeColor(android.R.attr.textColorPrimary)) - setProgressCompat(progress, true) - } - show() - } else { - hideAnimationBehavior = BaseProgressIndicator.HIDE_OUTWARD - hide() - } - } - - binding.downloadStatusIcon.apply { - if (state == Download.State.DOWNLOADED || state == Download.State.ERROR) { - isVisible = true - if (state == Download.State.DOWNLOADED) { - setVectorCompat(R.drawable.ic_check_circle_24dp, android.R.attr.textColorPrimary) - } else { - setVectorCompat(R.drawable.ic_error_outline_24dp, R.attr.colorError) - } - } else { - isVisible = false - } - } - + fun setState(state: Download.State, progress: Int = 0) { this.state = state this.progress = progress } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt index 18d3f7621..ad77304e3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt @@ -22,13 +22,7 @@ class ChapterHolder( private val binding = ChaptersItemBinding.bind(view) init { - binding.download.setOnClickListener { - onDownloadClick(it, bindingAdapterPosition) - } - binding.download.setOnLongClickListener { - onDownloadLongClick(bindingAdapterPosition) - true - } + binding.download.listener = downloadActionListener } fun bind(item: ChapterItem, manga: Manga) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/base/BaseChapterHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/base/BaseChapterHolder.kt index e2f1dcc9c..cba5fefb2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/base/BaseChapterHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/base/BaseChapterHolder.kt @@ -2,58 +2,19 @@ package eu.kanade.tachiyomi.ui.manga.chapter.base import android.view.View import eu.davidea.viewholders.FlexibleViewHolder -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.download.model.Download -import eu.kanade.tachiyomi.util.view.popupMenu +import eu.kanade.presentation.manga.ChapterDownloadAction open class BaseChapterHolder( view: View, private val adapter: BaseChaptersAdapter<*>, ) : FlexibleViewHolder(view, adapter) { - fun onDownloadClick(view: View, position: Int) { - val item = adapter.getItem(position) as? BaseChapterItem<*, *> ?: return - when (item.status) { - Download.State.NOT_DOWNLOADED, Download.State.ERROR -> { - adapter.clickListener.downloadChapter(position) - } - else -> { - view.popupMenu( - R.menu.chapter_download, - initMenu = { - // Download.State.DOWNLOADED - findItem(R.id.delete_download).isVisible = item.status == Download.State.DOWNLOADED - - // Download.State.DOWNLOADING, Download.State.QUEUE - findItem(R.id.cancel_download).isVisible = item.status != Download.State.DOWNLOADED - - // Download.State.QUEUE - findItem(R.id.start_download).isVisible = item.status == Download.State.QUEUE - }, - onMenuItemClick = { - if (itemId == R.id.start_download) { - adapter.clickListener.startDownloadNow(position) - } else { - adapter.clickListener.deleteChapter(position) - } - }, - ) - } - } - } - - fun onDownloadLongClick(position: Int) { - val item = adapter.getItem(position) as? BaseChapterItem<*, *> ?: return - when (item.status) { - Download.State.NOT_DOWNLOADED, Download.State.ERROR -> { - adapter.clickListener.downloadChapter(position) - } - Download.State.DOWNLOADED, Download.State.DOWNLOADING -> { - adapter.clickListener.deleteChapter(position) - } - // Download.State.QUEUE - else -> { - adapter.clickListener.startDownloadNow(position) + val downloadActionListener: (ChapterDownloadAction) -> Unit = { action -> + when (action) { + ChapterDownloadAction.START -> adapter.clickListener.downloadChapter(bindingAdapterPosition) + ChapterDownloadAction.START_NOW -> adapter.clickListener.startDownloadNow(bindingAdapterPosition) + ChapterDownloadAction.CANCEL, ChapterDownloadAction.DELETE -> { + adapter.clickListener.deleteChapter(bindingAdapterPosition) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt index e669fafce..1efa5b81f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt @@ -27,13 +27,7 @@ class UpdatesHolder(private val view: View, private val adapter: UpdatesAdapter) adapter.coverClickListener.onCoverClick(bindingAdapterPosition) } - binding.download.setOnClickListener { - onDownloadClick(it, bindingAdapterPosition) - } - binding.download.setOnLongClickListener { - onDownloadLongClick(bindingAdapterPosition) - true - } + binding.download.listener = downloadActionListener } fun bind(item: UpdatesItem) {