mirror of
https://git.mihon.tech/mihonapp/mihon
synced 2024-11-23 21:55:57 +03:00
ChapterDownloadView: Convert to compose (#7354)
This commit is contained in:
parent
8e985eb0db
commit
a77bce7b37
6 changed files with 187 additions and 140 deletions
|
@ -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
|
|
@ -4,3 +4,10 @@ enum class EditCoverAction {
|
||||||
EDIT,
|
EDIT,
|
||||||
DELETE,
|
DELETE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class ChapterDownloadAction {
|
||||||
|
START,
|
||||||
|
START_NOW,
|
||||||
|
CANCEL,
|
||||||
|
DELETE,
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue