Ver Fonte

ChapterDownloadView: Convert to compose (#7354)

Ivan Iskandar há 2 anos atrás
pai
commit
a77bce7b37

+ 146 - 0
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

+ 7 - 0
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,
+}

+ 28 - 83
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) {
-
-    private val binding: ChapterDownloadViewBinding =
-        ChapterDownloadViewBinding.inflate(LayoutInflater.from(context), this, false)
-
-    private var state: Download.State? = null
-    private var progress = -1
-
-    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)
+class ChapterDownloadView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyle: Int = 0,
+) : AbstractComposeView(context, attrs, defStyle) {
+
+    private var state by mutableStateOf(Download.State.NOT_DOWNLOADED)
+    private var progress by mutableStateOf(0)
+
+    var listener: (ChapterDownloadAction) -> Unit = {}
+
+    @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
     }

+ 1 - 7
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) {

+ 7 - 46
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)
             }
         }
     }

+ 1 - 7
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) {