diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt index c91517b42..d568e2547 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadItemAD.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.download.ui +import android.view.View import androidx.core.view.isVisible import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding @@ -9,20 +10,33 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.koitharu.kotatsu.R import org.koitharu.kotatsu.databinding.ItemDownloadBinding +import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.download.domain.DownloadState import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.utils.ext.* -import org.koitharu.kotatsu.utils.progress.ProgressJob fun downloadItemAD( scope: CoroutineScope, coil: ImageLoader, -) = adapterDelegateViewBinding, ProgressJob, ItemDownloadBinding>( +) = adapterDelegateViewBinding( { inflater, parent -> ItemDownloadBinding.inflate(inflater, parent, false) }, ) { var job: Job? = null val percentPattern = context.resources.getString(R.string.percent_string_pattern) + val clickListener = View.OnClickListener { v -> + when (v.id) { + R.id.button_cancel -> item.cancel() + R.id.button_resume -> item.resume() + else -> context.startActivity( + DetailsActivity.newIntent(context, item.progressValue.manga), + ) + } + } + binding.buttonCancel.setOnClickListener(clickListener) + binding.buttonResume.setOnClickListener(clickListener) + itemView.setOnClickListener(clickListener) + bind { job?.cancel() job = item.progressAsFlow().onFirst { state -> @@ -43,6 +57,8 @@ fun downloadItemAD( binding.progressBar.isVisible = true binding.textViewPercent.isVisible = false binding.textViewDetails.isVisible = false + binding.buttonCancel.isVisible = false + binding.buttonResume.isVisible = false } is DownloadState.Done -> { binding.textViewStatus.setText(R.string.download_complete) @@ -50,6 +66,8 @@ fun downloadItemAD( binding.progressBar.isVisible = false binding.textViewPercent.isVisible = false binding.textViewDetails.isVisible = false + binding.buttonCancel.isVisible = false + binding.buttonResume.isVisible = false } is DownloadState.Error -> { binding.textViewStatus.setText(R.string.error_occurred) @@ -58,6 +76,8 @@ fun downloadItemAD( binding.textViewPercent.isVisible = false binding.textViewDetails.text = state.error.getDisplayMessage(context.resources) binding.textViewDetails.isVisible = true + binding.buttonCancel.isVisible = state.canRetry + binding.buttonResume.isVisible = state.canRetry } is DownloadState.PostProcessing -> { binding.textViewStatus.setText(R.string.processing_) @@ -65,6 +85,8 @@ fun downloadItemAD( binding.progressBar.isVisible = true binding.textViewPercent.isVisible = false binding.textViewDetails.isVisible = false + binding.buttonCancel.isVisible = false + binding.buttonResume.isVisible = false } is DownloadState.Preparing -> { binding.textViewStatus.setText(R.string.preparing_) @@ -72,6 +94,8 @@ fun downloadItemAD( binding.progressBar.isVisible = true binding.textViewPercent.isVisible = false binding.textViewDetails.isVisible = false + binding.buttonCancel.isVisible = true + binding.buttonResume.isVisible = false } is DownloadState.Progress -> { binding.textViewStatus.setText(R.string.manga_downloading_) @@ -82,6 +106,8 @@ fun downloadItemAD( binding.textViewPercent.text = percentPattern.format((state.percent * 100f).format(1)) binding.textViewPercent.isVisible = true binding.textViewDetails.isVisible = false + binding.buttonCancel.isVisible = true + binding.buttonResume.isVisible = false } is DownloadState.Queued -> { binding.textViewStatus.setText(R.string.queued) @@ -89,6 +115,8 @@ fun downloadItemAD( binding.progressBar.isVisible = false binding.textViewPercent.isVisible = false binding.textViewDetails.isVisible = false + binding.buttonCancel.isVisible = true + binding.buttonResume.isVisible = false } } }.launchIn(scope) diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsAdapter.kt index 0c3629d1b..eda0c1a1e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsAdapter.kt @@ -5,12 +5,14 @@ import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import kotlinx.coroutines.CoroutineScope import org.koitharu.kotatsu.download.domain.DownloadState -import org.koitharu.kotatsu.utils.progress.ProgressJob +import org.koitharu.kotatsu.utils.progress.PausingProgressJob + +typealias DownloadItem = PausingProgressJob class DownloadsAdapter( scope: CoroutineScope, coil: ImageLoader, -) : AsyncListDifferDelegationAdapter>(DiffCallback()) { +) : AsyncListDifferDelegationAdapter(DiffCallback()) { init { delegatesManager.addDelegate(downloadItemAD(scope, coil)) @@ -21,18 +23,18 @@ class DownloadsAdapter( return items[position].progressValue.startId.toLong() } - private class DiffCallback : DiffUtil.ItemCallback>() { + private class DiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame( - oldItem: ProgressJob, - newItem: ProgressJob, + oldItem: DownloadItem, + newItem: DownloadItem, ): Boolean { return oldItem.progressValue.startId == newItem.progressValue.startId } override fun areContentsTheSame( - oldItem: ProgressJob, - newItem: ProgressJob, + oldItem: DownloadItem, + newItem: DownloadItem, ): Boolean { return oldItem.progressValue == newItem.progressValue } diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt index 175376ed0..0aac1895e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadNotification.kt @@ -16,6 +16,7 @@ import androidx.core.text.HtmlCompat import androidx.core.text.htmlEncode import androidx.core.text.parseAsHtml import androidx.core.util.forEach +import androidx.core.util.isNotEmpty import androidx.core.util.size import com.google.android.material.R as materialR import org.koitharu.kotatsu.R @@ -98,7 +99,7 @@ class DownloadNotification(private val context: Context) { style.addLine( context.getString( R.string.download_summary_pattern, - state.manga.title.ellipsize(10).htmlEncode(), + state.manga.title.ellipsize(16).htmlEncode(), summary.htmlEncode(), ).parseAsHtml(HtmlCompat.FROM_HTML_MODE_LEGACY), ) @@ -113,15 +114,17 @@ class DownloadNotification(private val context: Context) { when (progress) { 1f -> groupBuilder.setProgress(0, 0, false) 0f -> groupBuilder.setProgress(1, 0, true) - else -> groupBuilder.setProgress(100, (progress * 100f).toInt(), progress == 0f) + else -> groupBuilder.setProgress(100, (progress * 100f).toInt(), false) } return groupBuilder.build() } fun detach() { manager.cancel(ID_GROUP) - val notification = buildGroupNotification() - manager.notify(ID_GROUP_DETACHED, notification) + if (states.isNotEmpty() && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + val notification = buildGroupNotification() + manager.notify(ID_GROUP_DETACHED, notification) + } } fun newItem(startId: Int) = Item(startId) @@ -171,6 +174,8 @@ class DownloadNotification(private val context: Context) { builder.setStyle(null) builder.setLargeIcon(state.cover?.toBitmap()) builder.clearActions() + builder.setSubText(null) + builder.setShowWhen(false) builder.setVisibility( if (state.manga.isNsfw) { NotificationCompat.VISIBILITY_PRIVATE @@ -196,6 +201,8 @@ class DownloadNotification(private val context: Context) { builder.setCategory(null) builder.setStyle(null) builder.setOngoing(false) + builder.setShowWhen(true) + builder.setWhen(System.currentTimeMillis()) builder.priority = NotificationCompat.PRIORITY_DEFAULT } is DownloadState.Error -> { @@ -207,6 +214,8 @@ class DownloadNotification(private val context: Context) { builder.setAutoCancel(!state.canRetry) builder.setOngoing(state.canRetry) builder.setCategory(NotificationCompat.CATEGORY_ERROR) + builder.setShowWhen(true) + builder.setWhen(System.currentTimeMillis()) builder.setStyle(NotificationCompat.BigTextStyle().bigText(message)) if (state.canRetry) { builder.addAction(cancelAction) @@ -239,12 +248,13 @@ class DownloadNotification(private val context: Context) { } is DownloadState.Progress -> { builder.setProgress(state.max, state.progress, false) + val percent = context.getString(R.string.percent_string_pattern, (state.percent * 100).format()) if (timeLeft > 0L) { val eta = DateUtils.getRelativeTimeSpanString(timeLeft, 0L, DateUtils.SECOND_IN_MILLIS) builder.setContentText(eta) + builder.setSubText(percent) } else { - val percent = (state.percent * 100).format() - builder.setContentText(context.getString(R.string.percent_string_pattern, percent)) + builder.setContentText(percent) } builder.setCategory(NotificationCompat.CATEGORY_PROGRESS) builder.setStyle(null) diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt index 3c6a60b4f..e8382f17f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/service/DownloadService.kt @@ -129,6 +129,8 @@ class DownloadService : BaseService() { } if (job.isCancelled) { notificationItem.dismiss() + jobs.remove(startId) + jobCount.value = jobs.size } else { notificationItem.notify(job.progressValue, -1L) } diff --git a/app/src/main/res/layout/item_download.xml b/app/src/main/res/layout/item_download.xml index 101df297e..6d5e549be 100644 --- a/app/src/main/res/layout/item_download.xml +++ b/app/src/main/res/layout/item_download.xml @@ -77,10 +77,10 @@ android:id="@+id/textView_percent" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginEnd="8dp" android:textAppearance="?attr/textAppearanceBodyMedium" app:layout_constraintBaseline_toBaselineOf="@id/textView_status" app:layout_constraintEnd_toEndOf="parent" - android:layout_marginEnd="8dp" tools:text="25%" /> +