From 198960f93ca09fd3b23e11eca95595f3119c18dc Mon Sep 17 00:00:00 2001 From: Vladimir Dostoyevskiy <99822064+VDostoyevskiy@users.noreply.github.com> Date: Sun, 26 Nov 2023 00:46:07 +0800 Subject: [PATCH] Compose DownloadCard (#339) * Cleanup res * Compose DownloadCard * Refine * Fix CI * Fixes * Fix recompose * Fix textStyle * Fixes * Fixes * Restore design specs and clean up --------- Co-authored-by: GG Bond --- .../ehviewer/download/DownloadManager.kt | 32 +- .../hippo/ehviewer/ui/main/DownloadCard.kt | 278 ++++++++++++++ .../hippo/ehviewer/ui/scene/DownloadsScene.kt | 340 ++---------------- .../main/res/drawable/big_sad_pandroid.xml | 36 -- .../main/res/drawable/v_help_circle_x24.xml | 27 -- app/src/main/res/drawable/v_play_x24.xml | 27 -- app/src/main/res/layout/item_download.xml | 151 -------- app/src/main/res/menu/drawer_gallery_list.xml | 32 -- app/src/main/res/values-sw600dp/dimens.xml | 2 - .../main/res/values-sw720dp-land/dimens.xml | 2 - app/src/main/res/values/dimens.xml | 4 - app/src/main/res/values/pathdata.xml | 2 - app/src/main/res/values/styles.xml | 13 - 13 files changed, 342 insertions(+), 604 deletions(-) create mode 100644 app/src/main/kotlin/com/hippo/ehviewer/ui/main/DownloadCard.kt delete mode 100644 app/src/main/res/drawable/big_sad_pandroid.xml delete mode 100644 app/src/main/res/drawable/v_help_circle_x24.xml delete mode 100644 app/src/main/res/drawable/v_play_x24.xml delete mode 100644 app/src/main/res/layout/item_download.xml delete mode 100644 app/src/main/res/menu/drawer_gallery_list.xml diff --git a/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadManager.kt b/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadManager.kt index bc795983a..22bcedad5 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadManager.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadManager.kt @@ -18,6 +18,12 @@ package com.hippo.ehviewer.download import android.net.Uri import android.util.Log import android.util.SparseLongArray +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisallowComposableCalls +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.remember import com.google.android.material.math.MathUtils import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.Settings @@ -47,6 +53,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch import splitties.init.appCtx @@ -246,15 +253,12 @@ object DownloadManager : OnSpiderListener { private val callbackFlowScope = CoroutineScope(Dispatchers.IO) - private val _stateFlow = callbackFlow { + val _stateFlow = callbackFlow { val listener = object : DownloadInfoListener { override fun onUpdate(info: DownloadInfo) { - trySend(info.gid) - } - - override fun onUpdateAll() { - trySend(0) + trySend(info) } + override fun onUpdateAll() {} } mDownloadInfoListener = listener awaitClose { @@ -262,7 +266,21 @@ object DownloadManager : OnSpiderListener { } }.shareIn(callbackFlowScope, SharingStarted.Eagerly) - fun stateFlow(gid: Long) = _stateFlow.filter { it == gid } + @Stable + @Composable + fun collectDownloadState(gid: Long): State { + val flow = remember { stateFlow(gid).map { getDownloadState(it) } } + return flow.collectAsState(getDownloadState(gid)) + } + + @Stable + @Composable + inline fun updatedDownloadInfo(info: DownloadInfo, crossinline transform: @DisallowComposableCalls DownloadInfo.() -> T): T { + val flow = remember { _stateFlow.filter { it.gid == info.gid }.map { transform(it) } } + return flow.collectAsState(transform(info)).value + } + + fun stateFlow(gid: Long) = _stateFlow.map { it.gid }.filter { it == gid } fun stateFlow() = _stateFlow diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/DownloadCard.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/DownloadCard.kt new file mode 100644 index 000000000..d68ade2d1 --- /dev/null +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/DownloadCard.kt @@ -0,0 +1,278 @@ +package com.hippo.ehviewer.ui.main + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.DragHandle +import androidx.compose.material.icons.filled.Pause +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material3.Card +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.ShapeDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import coil.compose.AsyncImage +import coil.compose.AsyncImagePainter +import com.hippo.ehviewer.EhApplication +import com.hippo.ehviewer.R +import com.hippo.ehviewer.Settings +import com.hippo.ehviewer.client.EhUtils +import com.hippo.ehviewer.coil.read +import com.hippo.ehviewer.dao.DownloadInfo +import com.hippo.ehviewer.download.DownloadManager +import com.hippo.ehviewer.download.downloadDir +import com.hippo.ehviewer.ktbuilder.imageRequest +import com.hippo.ehviewer.ui.tools.CropDefaults +import com.hippo.ehviewer.ui.tools.CrystalCard +import com.hippo.ehviewer.ui.tools.GalleryListCardRating +import com.hippo.ehviewer.util.FileUtils +import com.hippo.ehviewer.util.sendTo +import com.hippo.unifile.UniFile +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Composable +private fun AsyncThumb( + info: DownloadInfo, + modifier: Modifier = Modifier, +) { + val path = remember(info.gid) { info.downloadDir?.subFile(".thumb") } + var contentScale by remember(info.gid) { mutableStateOf(ContentScale.Fit) } + val coroutineScope = rememberCoroutineScope { Dispatchers.IO } + val context = LocalContext.current + var localReq by remember(info.gid) { + path?.takeIf { it.isFile }?.uri?.let { + context.imageRequest { + data(it.toString()) + memoryCacheKey(info.thumbKey) + } + }.let { mutableStateOf(it) } + } + AsyncImage( + model = localReq ?: requestOf(info), + contentDescription = null, + modifier = modifier, + onState = { state -> + if (state is AsyncImagePainter.State.Success) { + state.result.drawable.run { + if (CropDefaults.shouldCrop(intrinsicWidth, intrinsicHeight)) { + contentScale = ContentScale.Crop + } + } + path?.let { + coroutineScope.launch { + runCatching { + if (!path.exists() && path.ensureFile()) { + val key = info.thumbKey!! + EhApplication.imageCache.read(key) { + UniFile.fromFile(data.toFile())!!.openFileDescriptor("r").use { src -> + path.openFileDescriptor("w").use { dst -> + src sendTo dst + } + } + } + } + }.onFailure { + it.printStackTrace() + } + } + } + } + if (state is AsyncImagePainter.State.Error) { + path?.let { + coroutineScope.launch { + if (path.exists()) { + path.delete() + localReq = null + } + } + } + } + }, + contentScale = contentScale, + ) +} + +@Composable +fun DownloadCard( + onClick: () -> Unit, + onThumbClick: () -> Unit, + onLongClick: () -> Unit, + onStart: () -> Unit, + onStop: () -> Unit, + onDrag: () -> Unit, + info: DownloadInfo, + modifier: Modifier = Modifier, +) { + CrystalCard( + modifier = modifier, + onClick = onClick, + onLongClick = onLongClick, + ) { + Row { + Card(onClick = onThumbClick) { + AsyncThumb( + info = info, + modifier = Modifier.aspectRatio(DEFAULT_ASPECT).fillMaxSize(), + ) + } + + val stateFailed = stringResource(R.string.download_state_failed) + val stateFailed2 = stringResource(R.string.download_state_failed_2, info.legacy) + val downloadState by DownloadManager.collectDownloadState(info.gid) + val stateText = when (downloadState) { + DownloadInfo.STATE_NONE -> stringResource(R.string.download_state_none) + DownloadInfo.STATE_WAIT -> stringResource(R.string.download_state_wait) + DownloadInfo.STATE_DOWNLOAD -> null + DownloadInfo.STATE_FAILED -> if (info.legacy <= 0) stateFailed else stateFailed2 + DownloadInfo.STATE_FINISH -> stringResource(R.string.download_state_finish) + else -> error("Illegal State $downloadState!!!") + } + ConstraintLayout(modifier = Modifier.padding(8.dp, 4.dp).fillMaxSize()) { + val ( + titleRef, uploaderRef, ratingRef, categoryRef, actionsRef, stateTextRef, + progressBarRef, progressTextRef, speedRef, + ) = createRefs() + Text( + text = EhUtils.getSuitableTitle(info), + maxLines = 2, + modifier = Modifier.constrainAs(titleRef) { + top.linkTo(parent.top) + start.linkTo(parent.start) + }.fillMaxWidth(), + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleSmall, + ) + if (downloadState != DownloadInfo.STATE_DOWNLOAD) { + ProvideTextStyle(MaterialTheme.typography.labelLarge) { + info.uploader?.let { + Text( + text = it, + modifier = Modifier.constrainAs(uploaderRef) { + start.linkTo(parent.start) + bottom.linkTo(ratingRef.top) + }.alpha(if (info.disowned) 0.5f else 1f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + GalleryListCardRating( + rating = info.rating, + modifier = Modifier.constrainAs(ratingRef) { + start.linkTo(parent.start) + bottom.linkTo(categoryRef.top) + }, + ) + val categoryColor = EhUtils.getCategoryColor(info.category) + val categoryText = EhUtils.getCategory(info.category).uppercase() + Text( + text = categoryText, + modifier = Modifier.constrainAs(categoryRef) { + start.linkTo(parent.start) + bottom.linkTo(parent.bottom) + }.clip(ShapeDefaults.Small).background(categoryColor).padding(vertical = 2.dp, horizontal = 8.dp), + color = if (Settings.harmonizeCategoryColor) Color.Unspecified else EhUtils.categoryTextColor, + textAlign = TextAlign.Center, + ) + } + } else { + val (total, finished, speed) = DownloadManager.updatedDownloadInfo(info) { + Triple(total, finished, speed) + } + ProvideTextStyle(MaterialTheme.typography.labelMedium) { + if (total <= 0 || finished < 0) { + LinearProgressIndicator( + modifier = Modifier.constrainAs(progressBarRef) { + bottom.linkTo(actionsRef.top) + }.fillMaxWidth(), + ) + } else { + LinearProgressIndicator( + progress = { finished.toFloat() / total.toFloat() }, + modifier = Modifier.constrainAs(progressBarRef) { + bottom.linkTo(actionsRef.top) + }.fillMaxWidth(), + ) + Text( + text = "$finished/$total", + modifier = Modifier.constrainAs(progressTextRef) { + bottom.linkTo(progressBarRef.top) + start.linkTo(progressBarRef.start) + }, + ) + } + Text( + text = FileUtils.humanReadableByteCount(speed.coerceAtLeast(0), false) + "/S", + modifier = Modifier.constrainAs(speedRef) { + bottom.linkTo(progressBarRef.top) + end.linkTo(progressBarRef.end) + }, + ) + } + } + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.constrainAs(actionsRef) { + end.linkTo(parent.end) + bottom.linkTo(parent.bottom) + }, + ) { + IconButton(onClick = { }) { + Icon( + imageVector = Icons.Default.DragHandle, + contentDescription = null, + modifier = Modifier.pointerInput(info.gid) { + awaitPointerEventScope { onDrag() } + }, + ) + } + if (downloadState == DownloadInfo.STATE_WAIT || downloadState == DownloadInfo.STATE_DOWNLOAD) { + IconButton(onClick = onStop) { + Icon(imageVector = Icons.Default.Pause, contentDescription = null) + } + } else { + IconButton(onClick = onStart) { + Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null) + } + } + } + if (stateText != null) { + Text( + text = stateText, + modifier = Modifier.constrainAs(stateTextRef) { + end.linkTo(parent.end, 16.dp) + bottom.linkTo(actionsRef.top) + }, + style = MaterialTheme.typography.titleSmall, + ) + } + } + } + } +} diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/DownloadsScene.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/DownloadsScene.kt index aa3fdd91a..c1dc8ce1b 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/DownloadsScene.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/scene/DownloadsScene.kt @@ -16,24 +16,17 @@ package com.hippo.ehviewer.ui.scene import android.annotation.SuppressLint -import android.app.Activity import android.content.DialogInterface import android.content.Intent import android.graphics.Rect import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem -import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.Button import androidx.appcompat.app.AlertDialog -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.DriveFileMove import androidx.compose.material.icons.automirrored.outlined.Label @@ -42,29 +35,22 @@ import androidx.compose.material.icons.filled.DoneAll import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Pause import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material3.Card import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ShapeDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.lifecycle.lifecycleScope @@ -75,27 +61,17 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.StaggeredGridLayoutManager import androidx.viewbinding.ViewBinding -import arrow.core.partially1 -import coil.compose.AsyncImage -import coil.compose.AsyncImagePainter -import com.hippo.ehviewer.EhApplication.Companion.imageCache import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings import com.hippo.ehviewer.Settings.detailSize -import com.hippo.ehviewer.client.EhUtils -import com.hippo.ehviewer.client.data.GalleryInfo -import com.hippo.ehviewer.coil.read import com.hippo.ehviewer.dao.DownloadInfo import com.hippo.ehviewer.databinding.DrawerListRvBinding -import com.hippo.ehviewer.databinding.ItemDownloadBinding import com.hippo.ehviewer.databinding.ItemDrawerListBinding import com.hippo.ehviewer.databinding.SceneDownloadBinding import com.hippo.ehviewer.download.DownloadManager import com.hippo.ehviewer.download.DownloadService import com.hippo.ehviewer.download.DownloadService.Companion.clear -import com.hippo.ehviewer.download.downloadDir -import com.hippo.ehviewer.ktbuilder.imageRequest import com.hippo.ehviewer.ui.confirmRemoveDownload import com.hippo.ehviewer.ui.confirmRemoveDownloadRange import com.hippo.ehviewer.ui.legacy.AutoStaggeredGridLayoutManager @@ -103,23 +79,17 @@ import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder import com.hippo.ehviewer.ui.legacy.EditTextDialogBuilder import com.hippo.ehviewer.ui.legacy.HandlerDrawable import com.hippo.ehviewer.ui.legacy.ViewTransition -import com.hippo.ehviewer.ui.main.DEFAULT_ASPECT -import com.hippo.ehviewer.ui.main.requestOf +import com.hippo.ehviewer.ui.main.DownloadCard import com.hippo.ehviewer.ui.navToReader import com.hippo.ehviewer.ui.setMD3Content -import com.hippo.ehviewer.ui.tools.CropDefaults import com.hippo.ehviewer.ui.tools.DialogState -import com.hippo.ehviewer.util.FileUtils import com.hippo.ehviewer.util.containsIgnoreCase import com.hippo.ehviewer.util.mapToLongArray -import com.hippo.ehviewer.util.sendTo -import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.withNonCancellableContext import eu.kanade.tachiyomi.util.lang.withUIContext import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import rikka.core.res.resolveColor @@ -555,16 +525,6 @@ class DownloadsScene : SearchBarScene() { return drawerBinding.root } - fun onItemClick(position: Int): Boolean { - val context = context ?: return false - val list = mList ?: return false - if (position < 0 || position >= list.size) { - return false - } - context.navToReader(list[position].galleryInfo) - return true - } - private class DownloadLabelHolder(val binding: ItemDrawerListBinding) : RecyclerView.ViewHolder(binding.root) @@ -666,218 +626,58 @@ class DownloadsScene : SearchBarScene() { } } - @SuppressLint("ClickableViewAccessibility") - private inner class DownloadHolder( - private val binding: ItemDownloadBinding, - ) : RecyclerView.ViewHolder(binding.root), View.OnClickListener { - - init { - // TODO cancel on click listener when select items - binding.start.setOnClickListener(this) - binding.stop.setOnClickListener(this) - binding.thumb.setMD3Content { - val height by collectListThumbSizeAsState() - Spacer(modifier = Modifier.height(height).fillMaxWidth()) - } - binding.handle.setOnTouchListener { _, event -> - if (event.actionMasked == MotionEvent.ACTION_DOWN) { - mItemTouchHelper!!.startDrag(this) - } - false - } - } - - override fun onClick(v: View) { - val context = context - val activity: Activity? = mainActivity - val recyclerView = this@DownloadsScene.binding.recyclerView - if (null == context || null == activity || tracker.isInCustomChoice) { - return - } - val list = mList ?: return - val size = list.size - val index = recyclerView.getChildAdapterPosition(itemView) - if (index < 0 || index >= size) { - return - } - when (v) { - binding.thumb -> { - navAnimated( - R.id.galleryDetailScene, - bundleOf(GalleryDetailScene.KEY_ARGS to GalleryInfoArgs(list[index].galleryInfo)), - ) - } - - binding.start -> { - val intent = Intent(activity, DownloadService::class.java) - intent.action = DownloadService.ACTION_START - intent.putExtra(DownloadService.KEY_GALLERY_INFO, list[index].galleryInfo) - ContextCompat.startForegroundService(activity, intent) - } - - binding.stop -> { - lifecycleScope.launchIO { - DownloadManager.stopDownload(list[index].gid) - } - } - } - } - - fun bind(info: DownloadInfo, isChecked: Boolean) { - binding.root.isChecked = isChecked - binding.thumb.setMD3Content { - val height by collectListThumbSizeAsState() - Card(onClick = ::onClick.partially1(binding.thumb)) { - CompanionAsyncThumb( - info = info, - path = info.downloadDir?.subFile(".thumb"), - modifier = Modifier.height(height).aspectRatio(DEFAULT_ASPECT), - ) - } - } - binding.title.text = EhUtils.getSuitableTitle(info) - binding.uploader.text = info.uploader - binding.rating.rating = info.rating - binding.category.setMD3Content { - val categoryColor = EhUtils.getCategoryColor(info.category) - val categoryText = EhUtils.getCategory(info.category).uppercase() - Text( - text = categoryText, - modifier = Modifier.clip(ShapeDefaults.Small).background(categoryColor).padding(vertical = 2.dp, horizontal = 8.dp), - color = if (Settings.harmonizeCategoryColor) Color.Unspecified else EhUtils.categoryTextColor, - textAlign = TextAlign.Center, - style = MaterialTheme.typography.labelLarge, - ) - } - bindForState(info) - } - - fun bindForState(info: DownloadInfo) { - val context = context ?: return - when (info.state) { - DownloadInfo.STATE_NONE -> bindState( - info, - context.getString(R.string.download_state_none), - ) - - DownloadInfo.STATE_WAIT -> bindState( - info, - context.getString(R.string.download_state_wait), - ) - - DownloadInfo.STATE_DOWNLOAD -> bindProgress(info) - DownloadInfo.STATE_FAILED -> { - val text: String = if (info.legacy <= 0) { - context.getString(R.string.download_state_failed) - } else { - context.getString(R.string.download_state_failed_2, info.legacy) - } - bindState(info, text) - } - - DownloadInfo.STATE_FINISH -> bindState( - info, - context.getString(R.string.download_state_finish), - ) - } - } - - private fun bindState(info: DownloadInfo, newState: String) { - binding.run { - uploader.visibility = View.VISIBLE - rating.visibility = View.VISIBLE - category.visibility = View.VISIBLE - state.visibility = View.VISIBLE - progressBar.visibility = View.GONE - percent.visibility = View.GONE - speed.visibility = View.GONE - if (info.state == DownloadInfo.STATE_WAIT || info.state == DownloadInfo.STATE_DOWNLOAD) { - start.visibility = View.GONE - stop.visibility = View.VISIBLE - } else { - start.visibility = View.VISIBLE - stop.visibility = View.GONE - } - state.text = newState - } - } - - @SuppressLint("SetTextI18n") - private fun bindProgress(info: DownloadInfo) { - binding.run { - uploader.visibility = View.GONE - rating.visibility = View.GONE - category.visibility = View.GONE - state.visibility = View.GONE - progressBar.visibility = View.VISIBLE - percent.visibility = View.VISIBLE - speed.visibility = View.VISIBLE - if (info.state == DownloadInfo.STATE_WAIT || info.state == DownloadInfo.STATE_DOWNLOAD) { - start.visibility = View.GONE - stop.visibility = View.VISIBLE - } else { - start.visibility = View.VISIBLE - stop.visibility = View.GONE - } - if (info.total <= 0 || info.finished < 0) { - percent.text = null - progressBar.isIndeterminate = true - } else { - percent.text = info.finished.toString() + "/" + info.total - progressBar.isIndeterminate = false - progressBar.max = info.total - progressBar.progress = info.finished - } - speed.text = - FileUtils.humanReadableByteCount(info.speed.coerceAtLeast(0), false) + "/S" - } - } - } + class DownloadHolder(val view: ComposeView) : RecyclerView.ViewHolder(view) private val diffCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: DownloadInfo, newItem: DownloadInfo) = oldItem.gid == newItem.gid - override fun areContentsTheSame(oldItem: DownloadInfo, newItem: DownloadInfo) = oldItem == newItem - override fun getChangePayload(oldItem: DownloadInfo, newItem: DownloadInfo): Any? { - return if (oldItem.downloadInfo != newItem.downloadInfo) { - PAYLOAD_DOWNLOAD_INFO - } else { - super.getChangePayload(oldItem, newItem) - } - } + override fun areContentsTheSame(oldItem: DownloadInfo, newItem: DownloadInfo) = oldItem.gid == newItem.gid } private inner class DownloadAdapter : ListAdapter(diffCallback) { - private val mInflater: LayoutInflater = layoutInflater var tracker: GallerySelectionTracker? = null override fun getItemId(position: Int) = getItem(position).gid - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadHolder { - val holder = DownloadHolder(ItemDownloadBinding.inflate(mInflater, parent, false)) - holder.itemView.setOnClickListener { onItemClick(holder.bindingAdapterPosition) } - return holder - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = DownloadHolder(ComposeView(parent.context)) override fun onBindViewHolder(holder: DownloadHolder, position: Int) { val info = getItem(position) - holder.bind(info, tracker?.isSelected(info.gid) ?: false) - } - - override fun onBindViewHolder( - holder: DownloadHolder, - position: Int, - payloads: MutableList, - ) { - payloads.forEach { payload -> - when (payload) { - PAYLOAD_DOWNLOAD_INFO -> { - val info = getItem(position) - holder.bindForState(info) - return + val checked = tracker?.isSelected(info.gid) ?: false + holder.view.setMD3Content { + val context = LocalContext.current + val height by collectListThumbSizeAsState() + key(info.gid) { + CheckableItem(checked = checked) { + DownloadCard( + onClick = { + context.navToReader(info.galleryInfo) + }, + onThumbClick = { + navAnimated( + R.id.galleryDetailScene, + bundleOf(GalleryDetailScene.KEY_ARGS to GalleryInfoArgs(info.galleryInfo)), + ) + }, + onLongClick = { + }, + onStart = { + val intent = Intent(activity, DownloadService::class.java) + intent.action = DownloadService.ACTION_START + intent.putExtra(DownloadService.KEY_GALLERY_INFO, info.galleryInfo) + ContextCompat.startForegroundService(requireActivity(), intent) + }, + onStop = { + lifecycleScope.launchIO { + DownloadManager.stopDownload(info.gid) + } + }, + onDrag = { mItemTouchHelper?.startDrag(holder) }, + info = info, + modifier = Modifier.height(height), + ) } } } - super.onBindViewHolder(holder, position, payloads) } } @@ -1021,65 +821,3 @@ class DownloadsScene : SearchBarScene() { private const val PAYLOAD_DOWNLOAD_INFO = 0 } } - -@Composable -private fun CompanionAsyncThumb( - info: GalleryInfo, - path: UniFile?, - modifier: Modifier = Modifier, -) { - var contentScale by remember(info.gid) { mutableStateOf(ContentScale.Fit) } - val coroutineScope = rememberCoroutineScope { Dispatchers.IO } - val context = LocalContext.current - var localReq by remember(info.gid) { - path?.takeIf { it.isFile }?.uri?.let { - context.imageRequest { - data(it.toString()) - memoryCacheKey(info.thumbKey) - } - }.let { mutableStateOf(it) } - } - AsyncImage( - model = localReq ?: requestOf(info), - contentDescription = null, - modifier = modifier, - onState = { state -> - if (state is AsyncImagePainter.State.Success) { - state.result.drawable.run { - if (CropDefaults.shouldCrop(intrinsicWidth, intrinsicHeight)) { - contentScale = ContentScale.Crop - } - } - path?.let { - coroutineScope.launch { - runCatching { - if (!path.exists() && path.ensureFile()) { - val key = info.thumbKey!! - imageCache.read(key) { - UniFile.fromFile(data.toFile())!!.openFileDescriptor("r").use { src -> - path.openFileDescriptor("w").use { dst -> - src sendTo dst - } - } - } - } - }.onFailure { - it.printStackTrace() - } - } - } - } - if (state is AsyncImagePainter.State.Error) { - path?.let { - coroutineScope.launch { - if (path.exists()) { - path.delete() - localReq = null - } - } - } - } - }, - contentScale = contentScale, - ) -} diff --git a/app/src/main/res/drawable/big_sad_pandroid.xml b/app/src/main/res/drawable/big_sad_pandroid.xml deleted file mode 100644 index 3f5ef3d7c..000000000 --- a/app/src/main/res/drawable/big_sad_pandroid.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/v_help_circle_x24.xml b/app/src/main/res/drawable/v_help_circle_x24.xml deleted file mode 100644 index f2d65827d..000000000 --- a/app/src/main/res/drawable/v_help_circle_x24.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/v_play_x24.xml b/app/src/main/res/drawable/v_play_x24.xml deleted file mode 100644 index a0704fba5..000000000 --- a/app/src/main/res/drawable/v_play_x24.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/item_download.xml b/app/src/main/res/layout/item_download.xml deleted file mode 100644 index 3bf989b3a..000000000 --- a/app/src/main/res/layout/item_download.xml +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/menu/drawer_gallery_list.xml b/app/src/main/res/menu/drawer_gallery_list.xml deleted file mode 100644 index da298d8f5..000000000 --- a/app/src/main/res/menu/drawer_gallery_list.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml index 085af394c..035eb9eb2 100644 --- a/app/src/main/res/values-sw600dp/dimens.xml +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -23,6 +23,4 @@ 20dp - 20dp - diff --git a/app/src/main/res/values-sw720dp-land/dimens.xml b/app/src/main/res/values-sw720dp-land/dimens.xml index f71cc817c..3b6e84c0c 100644 --- a/app/src/main/res/values-sw720dp-land/dimens.xml +++ b/app/src/main/res/values-sw720dp-land/dimens.xml @@ -18,6 +18,4 @@ 76dp - 76dp - diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 76fba9608..b188d1f89 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -27,7 +27,6 @@ 16sp - 12sp 16dp @@ -39,12 +38,9 @@ 4dp - 10dp - 480dp 320dp - 4dp 4dp diff --git a/app/src/main/res/values/pathdata.xml b/app/src/main/res/values/pathdata.xml index c65d96b28..cf4928601 100644 --- a/app/src/main/res/values/pathdata.xml +++ b/app/src/main/res/values/pathdata.xml @@ -17,9 +17,7 @@ M14,19.14H18V5.14H14M6,19.14H10V5.14H6V19.14Z - M8,5.14V19.14L19,12.14L8,5.14Z M21,13H13V21H11V13H3V11H11V3H13V11H21V13Z - M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z M19,3H5C3.9,3,3,3.9,3,5V19C3,20.1,3.9,21,5,21H19C20.1,21,21,20.1,21,19V5C21,3.9,20.1,3,19,3ZM12,17L7,12H10V8H14V12H17L12,17Z M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M6,20H15L18,20V12L14,16L12,14L6,20M8,9A2,2 0 0,0 6,11A2,2 0 0,0 8,13A2,2 0 0,0 10,11A2,2 0 0,0 8,9Z diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 59016d6bd..327959c00 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -29,19 +29,6 @@ 48dp - - - -