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, EDIT,
DELETE, DELETE,
} }
enum class ChapterDownloadAction {
START,
START_NOW,
CANCEL,
DELETE,
}

View file

@ -1,95 +1,40 @@
package eu.kanade.tachiyomi.ui.manga.chapter package eu.kanade.tachiyomi.ui.manga.chapter
import android.content.Context import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import androidx.compose.runtime.Composable
import android.widget.FrameLayout import androidx.compose.runtime.getValue
import androidx.core.view.isVisible import androidx.compose.runtime.mutableStateOf
import com.google.android.material.progressindicator.BaseProgressIndicator import androidx.compose.runtime.setValue
import eu.kanade.tachiyomi.R 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.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) : class ChapterDownloadView @JvmOverloads constructor(
FrameLayout(context, attrs) { context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
) : AbstractComposeView(context, attrs, defStyle) {
private val binding: ChapterDownloadViewBinding = private var state by mutableStateOf(Download.State.NOT_DOWNLOADED)
ChapterDownloadViewBinding.inflate(LayoutInflater.from(context), this, false) private var progress by mutableStateOf(0)
private var state: Download.State? = null var listener: (ChapterDownloadAction) -> Unit = {}
private var progress = -1
init { @Composable
addView(binding.root) override fun Content() {
} TachiyomiTheme {
ChapterDownloadIndicator(
fun setState(state: Download.State, progress: Int = -1) { downloadState = state,
val isDirty = this.state?.value != state.value || this.progress != progress downloadProgress = progress,
if (isDirty) { onClick = listener,
updateLayout(state, progress) )
} }
} }
private fun updateLayout(state: Download.State, progress: Int) { fun setState(state: Download.State, progress: Int = 0) {
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
}
}
this.state = state this.state = state
this.progress = progress this.progress = progress
} }

View file

@ -22,13 +22,7 @@ class ChapterHolder(
private val binding = ChaptersItemBinding.bind(view) private val binding = ChaptersItemBinding.bind(view)
init { init {
binding.download.setOnClickListener { binding.download.listener = downloadActionListener
onDownloadClick(it, bindingAdapterPosition)
}
binding.download.setOnLongClickListener {
onDownloadLongClick(bindingAdapterPosition)
true
}
} }
fun bind(item: ChapterItem, manga: Manga) { 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 android.view.View
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.presentation.manga.ChapterDownloadAction
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.util.view.popupMenu
open class BaseChapterHolder( open class BaseChapterHolder(
view: View, view: View,
private val adapter: BaseChaptersAdapter<*>, private val adapter: BaseChaptersAdapter<*>,
) : FlexibleViewHolder(view, adapter) { ) : FlexibleViewHolder(view, adapter) {
fun onDownloadClick(view: View, position: Int) { val downloadActionListener: (ChapterDownloadAction) -> Unit = { action ->
val item = adapter.getItem(position) as? BaseChapterItem<*, *> ?: return when (action) {
when (item.status) { ChapterDownloadAction.START -> adapter.clickListener.downloadChapter(bindingAdapterPosition)
Download.State.NOT_DOWNLOADED, Download.State.ERROR -> { ChapterDownloadAction.START_NOW -> adapter.clickListener.startDownloadNow(bindingAdapterPosition)
adapter.clickListener.downloadChapter(position) ChapterDownloadAction.CANCEL, ChapterDownloadAction.DELETE -> {
} adapter.clickListener.deleteChapter(bindingAdapterPosition)
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)
} }
} }
} }

View file

@ -27,13 +27,7 @@ class UpdatesHolder(private val view: View, private val adapter: UpdatesAdapter)
adapter.coverClickListener.onCoverClick(bindingAdapterPosition) adapter.coverClickListener.onCoverClick(bindingAdapterPosition)
} }
binding.download.setOnClickListener { binding.download.listener = downloadActionListener
onDownloadClick(it, bindingAdapterPosition)
}
binding.download.setOnLongClickListener {
onDownloadLongClick(bindingAdapterPosition)
true
}
} }
fun bind(item: UpdatesItem) { fun bind(item: UpdatesItem) {