ChapterDownloadView: Convert to compose (#7354)

This commit is contained in:
Ivan Iskandar 2022-06-25 02:42:30 +07:00 committed by GitHub
parent 8e985eb0db
commit a77bce7b37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 187 additions and 140 deletions

View file

@ -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

View file

@ -4,3 +4,10 @@ enum class EditCoverAction {
EDIT,
DELETE,
}
enum class ChapterDownloadAction {
START,
START_NOW,
CANCEL,
DELETE,
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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)
}
}
}

View file

@ -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) {