Download control buttons in list
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.download.ui
|
package org.koitharu.kotatsu.download.ui
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
@@ -9,20 +10,33 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.databinding.ItemDownloadBinding
|
import org.koitharu.kotatsu.databinding.ItemDownloadBinding
|
||||||
|
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||||
import org.koitharu.kotatsu.download.domain.DownloadState
|
import org.koitharu.kotatsu.download.domain.DownloadState
|
||||||
import org.koitharu.kotatsu.parsers.util.format
|
import org.koitharu.kotatsu.parsers.util.format
|
||||||
import org.koitharu.kotatsu.utils.ext.*
|
import org.koitharu.kotatsu.utils.ext.*
|
||||||
import org.koitharu.kotatsu.utils.progress.ProgressJob
|
|
||||||
|
|
||||||
fun downloadItemAD(
|
fun downloadItemAD(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
) = adapterDelegateViewBinding<ProgressJob<DownloadState>, ProgressJob<DownloadState>, ItemDownloadBinding>(
|
) = adapterDelegateViewBinding<DownloadItem, DownloadItem, ItemDownloadBinding>(
|
||||||
{ inflater, parent -> ItemDownloadBinding.inflate(inflater, parent, false) },
|
{ inflater, parent -> ItemDownloadBinding.inflate(inflater, parent, false) },
|
||||||
) {
|
) {
|
||||||
var job: Job? = null
|
var job: Job? = null
|
||||||
val percentPattern = context.resources.getString(R.string.percent_string_pattern)
|
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 {
|
bind {
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
job = item.progressAsFlow().onFirst { state ->
|
job = item.progressAsFlow().onFirst { state ->
|
||||||
@@ -43,6 +57,8 @@ fun downloadItemAD(
|
|||||||
binding.progressBar.isVisible = true
|
binding.progressBar.isVisible = true
|
||||||
binding.textViewPercent.isVisible = false
|
binding.textViewPercent.isVisible = false
|
||||||
binding.textViewDetails.isVisible = false
|
binding.textViewDetails.isVisible = false
|
||||||
|
binding.buttonCancel.isVisible = false
|
||||||
|
binding.buttonResume.isVisible = false
|
||||||
}
|
}
|
||||||
is DownloadState.Done -> {
|
is DownloadState.Done -> {
|
||||||
binding.textViewStatus.setText(R.string.download_complete)
|
binding.textViewStatus.setText(R.string.download_complete)
|
||||||
@@ -50,6 +66,8 @@ fun downloadItemAD(
|
|||||||
binding.progressBar.isVisible = false
|
binding.progressBar.isVisible = false
|
||||||
binding.textViewPercent.isVisible = false
|
binding.textViewPercent.isVisible = false
|
||||||
binding.textViewDetails.isVisible = false
|
binding.textViewDetails.isVisible = false
|
||||||
|
binding.buttonCancel.isVisible = false
|
||||||
|
binding.buttonResume.isVisible = false
|
||||||
}
|
}
|
||||||
is DownloadState.Error -> {
|
is DownloadState.Error -> {
|
||||||
binding.textViewStatus.setText(R.string.error_occurred)
|
binding.textViewStatus.setText(R.string.error_occurred)
|
||||||
@@ -58,6 +76,8 @@ fun downloadItemAD(
|
|||||||
binding.textViewPercent.isVisible = false
|
binding.textViewPercent.isVisible = false
|
||||||
binding.textViewDetails.text = state.error.getDisplayMessage(context.resources)
|
binding.textViewDetails.text = state.error.getDisplayMessage(context.resources)
|
||||||
binding.textViewDetails.isVisible = true
|
binding.textViewDetails.isVisible = true
|
||||||
|
binding.buttonCancel.isVisible = state.canRetry
|
||||||
|
binding.buttonResume.isVisible = state.canRetry
|
||||||
}
|
}
|
||||||
is DownloadState.PostProcessing -> {
|
is DownloadState.PostProcessing -> {
|
||||||
binding.textViewStatus.setText(R.string.processing_)
|
binding.textViewStatus.setText(R.string.processing_)
|
||||||
@@ -65,6 +85,8 @@ fun downloadItemAD(
|
|||||||
binding.progressBar.isVisible = true
|
binding.progressBar.isVisible = true
|
||||||
binding.textViewPercent.isVisible = false
|
binding.textViewPercent.isVisible = false
|
||||||
binding.textViewDetails.isVisible = false
|
binding.textViewDetails.isVisible = false
|
||||||
|
binding.buttonCancel.isVisible = false
|
||||||
|
binding.buttonResume.isVisible = false
|
||||||
}
|
}
|
||||||
is DownloadState.Preparing -> {
|
is DownloadState.Preparing -> {
|
||||||
binding.textViewStatus.setText(R.string.preparing_)
|
binding.textViewStatus.setText(R.string.preparing_)
|
||||||
@@ -72,6 +94,8 @@ fun downloadItemAD(
|
|||||||
binding.progressBar.isVisible = true
|
binding.progressBar.isVisible = true
|
||||||
binding.textViewPercent.isVisible = false
|
binding.textViewPercent.isVisible = false
|
||||||
binding.textViewDetails.isVisible = false
|
binding.textViewDetails.isVisible = false
|
||||||
|
binding.buttonCancel.isVisible = true
|
||||||
|
binding.buttonResume.isVisible = false
|
||||||
}
|
}
|
||||||
is DownloadState.Progress -> {
|
is DownloadState.Progress -> {
|
||||||
binding.textViewStatus.setText(R.string.manga_downloading_)
|
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.text = percentPattern.format((state.percent * 100f).format(1))
|
||||||
binding.textViewPercent.isVisible = true
|
binding.textViewPercent.isVisible = true
|
||||||
binding.textViewDetails.isVisible = false
|
binding.textViewDetails.isVisible = false
|
||||||
|
binding.buttonCancel.isVisible = true
|
||||||
|
binding.buttonResume.isVisible = false
|
||||||
}
|
}
|
||||||
is DownloadState.Queued -> {
|
is DownloadState.Queued -> {
|
||||||
binding.textViewStatus.setText(R.string.queued)
|
binding.textViewStatus.setText(R.string.queued)
|
||||||
@@ -89,6 +115,8 @@ fun downloadItemAD(
|
|||||||
binding.progressBar.isVisible = false
|
binding.progressBar.isVisible = false
|
||||||
binding.textViewPercent.isVisible = false
|
binding.textViewPercent.isVisible = false
|
||||||
binding.textViewDetails.isVisible = false
|
binding.textViewDetails.isVisible = false
|
||||||
|
binding.buttonCancel.isVisible = true
|
||||||
|
binding.buttonResume.isVisible = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import coil.ImageLoader
|
|||||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import org.koitharu.kotatsu.download.domain.DownloadState
|
import org.koitharu.kotatsu.download.domain.DownloadState
|
||||||
import org.koitharu.kotatsu.utils.progress.ProgressJob
|
import org.koitharu.kotatsu.utils.progress.PausingProgressJob
|
||||||
|
|
||||||
|
typealias DownloadItem = PausingProgressJob<DownloadState>
|
||||||
|
|
||||||
class DownloadsAdapter(
|
class DownloadsAdapter(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
coil: ImageLoader,
|
coil: ImageLoader,
|
||||||
) : AsyncListDifferDelegationAdapter<ProgressJob<DownloadState>>(DiffCallback()) {
|
) : AsyncListDifferDelegationAdapter<DownloadItem>(DiffCallback()) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
delegatesManager.addDelegate(downloadItemAD(scope, coil))
|
delegatesManager.addDelegate(downloadItemAD(scope, coil))
|
||||||
@@ -21,18 +23,18 @@ class DownloadsAdapter(
|
|||||||
return items[position].progressValue.startId.toLong()
|
return items[position].progressValue.startId.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DiffCallback : DiffUtil.ItemCallback<ProgressJob<DownloadState>>() {
|
private class DiffCallback : DiffUtil.ItemCallback<DownloadItem>() {
|
||||||
|
|
||||||
override fun areItemsTheSame(
|
override fun areItemsTheSame(
|
||||||
oldItem: ProgressJob<DownloadState>,
|
oldItem: DownloadItem,
|
||||||
newItem: ProgressJob<DownloadState>,
|
newItem: DownloadItem,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return oldItem.progressValue.startId == newItem.progressValue.startId
|
return oldItem.progressValue.startId == newItem.progressValue.startId
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(
|
override fun areContentsTheSame(
|
||||||
oldItem: ProgressJob<DownloadState>,
|
oldItem: DownloadItem,
|
||||||
newItem: ProgressJob<DownloadState>,
|
newItem: DownloadItem,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return oldItem.progressValue == newItem.progressValue
|
return oldItem.progressValue == newItem.progressValue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import androidx.core.text.HtmlCompat
|
|||||||
import androidx.core.text.htmlEncode
|
import androidx.core.text.htmlEncode
|
||||||
import androidx.core.text.parseAsHtml
|
import androidx.core.text.parseAsHtml
|
||||||
import androidx.core.util.forEach
|
import androidx.core.util.forEach
|
||||||
|
import androidx.core.util.isNotEmpty
|
||||||
import androidx.core.util.size
|
import androidx.core.util.size
|
||||||
import com.google.android.material.R as materialR
|
import com.google.android.material.R as materialR
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
@@ -98,7 +99,7 @@ class DownloadNotification(private val context: Context) {
|
|||||||
style.addLine(
|
style.addLine(
|
||||||
context.getString(
|
context.getString(
|
||||||
R.string.download_summary_pattern,
|
R.string.download_summary_pattern,
|
||||||
state.manga.title.ellipsize(10).htmlEncode(),
|
state.manga.title.ellipsize(16).htmlEncode(),
|
||||||
summary.htmlEncode(),
|
summary.htmlEncode(),
|
||||||
).parseAsHtml(HtmlCompat.FROM_HTML_MODE_LEGACY),
|
).parseAsHtml(HtmlCompat.FROM_HTML_MODE_LEGACY),
|
||||||
)
|
)
|
||||||
@@ -113,15 +114,17 @@ class DownloadNotification(private val context: Context) {
|
|||||||
when (progress) {
|
when (progress) {
|
||||||
1f -> groupBuilder.setProgress(0, 0, false)
|
1f -> groupBuilder.setProgress(0, 0, false)
|
||||||
0f -> groupBuilder.setProgress(1, 0, true)
|
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()
|
return groupBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun detach() {
|
fun detach() {
|
||||||
manager.cancel(ID_GROUP)
|
manager.cancel(ID_GROUP)
|
||||||
val notification = buildGroupNotification()
|
if (states.isNotEmpty() && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||||
manager.notify(ID_GROUP_DETACHED, notification)
|
val notification = buildGroupNotification()
|
||||||
|
manager.notify(ID_GROUP_DETACHED, notification)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun newItem(startId: Int) = Item(startId)
|
fun newItem(startId: Int) = Item(startId)
|
||||||
@@ -171,6 +174,8 @@ class DownloadNotification(private val context: Context) {
|
|||||||
builder.setStyle(null)
|
builder.setStyle(null)
|
||||||
builder.setLargeIcon(state.cover?.toBitmap())
|
builder.setLargeIcon(state.cover?.toBitmap())
|
||||||
builder.clearActions()
|
builder.clearActions()
|
||||||
|
builder.setSubText(null)
|
||||||
|
builder.setShowWhen(false)
|
||||||
builder.setVisibility(
|
builder.setVisibility(
|
||||||
if (state.manga.isNsfw) {
|
if (state.manga.isNsfw) {
|
||||||
NotificationCompat.VISIBILITY_PRIVATE
|
NotificationCompat.VISIBILITY_PRIVATE
|
||||||
@@ -196,6 +201,8 @@ class DownloadNotification(private val context: Context) {
|
|||||||
builder.setCategory(null)
|
builder.setCategory(null)
|
||||||
builder.setStyle(null)
|
builder.setStyle(null)
|
||||||
builder.setOngoing(false)
|
builder.setOngoing(false)
|
||||||
|
builder.setShowWhen(true)
|
||||||
|
builder.setWhen(System.currentTimeMillis())
|
||||||
builder.priority = NotificationCompat.PRIORITY_DEFAULT
|
builder.priority = NotificationCompat.PRIORITY_DEFAULT
|
||||||
}
|
}
|
||||||
is DownloadState.Error -> {
|
is DownloadState.Error -> {
|
||||||
@@ -207,6 +214,8 @@ class DownloadNotification(private val context: Context) {
|
|||||||
builder.setAutoCancel(!state.canRetry)
|
builder.setAutoCancel(!state.canRetry)
|
||||||
builder.setOngoing(state.canRetry)
|
builder.setOngoing(state.canRetry)
|
||||||
builder.setCategory(NotificationCompat.CATEGORY_ERROR)
|
builder.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||||
|
builder.setShowWhen(true)
|
||||||
|
builder.setWhen(System.currentTimeMillis())
|
||||||
builder.setStyle(NotificationCompat.BigTextStyle().bigText(message))
|
builder.setStyle(NotificationCompat.BigTextStyle().bigText(message))
|
||||||
if (state.canRetry) {
|
if (state.canRetry) {
|
||||||
builder.addAction(cancelAction)
|
builder.addAction(cancelAction)
|
||||||
@@ -239,12 +248,13 @@ class DownloadNotification(private val context: Context) {
|
|||||||
}
|
}
|
||||||
is DownloadState.Progress -> {
|
is DownloadState.Progress -> {
|
||||||
builder.setProgress(state.max, state.progress, false)
|
builder.setProgress(state.max, state.progress, false)
|
||||||
|
val percent = context.getString(R.string.percent_string_pattern, (state.percent * 100).format())
|
||||||
if (timeLeft > 0L) {
|
if (timeLeft > 0L) {
|
||||||
val eta = DateUtils.getRelativeTimeSpanString(timeLeft, 0L, DateUtils.SECOND_IN_MILLIS)
|
val eta = DateUtils.getRelativeTimeSpanString(timeLeft, 0L, DateUtils.SECOND_IN_MILLIS)
|
||||||
builder.setContentText(eta)
|
builder.setContentText(eta)
|
||||||
|
builder.setSubText(percent)
|
||||||
} else {
|
} else {
|
||||||
val percent = (state.percent * 100).format()
|
builder.setContentText(percent)
|
||||||
builder.setContentText(context.getString(R.string.percent_string_pattern, percent))
|
|
||||||
}
|
}
|
||||||
builder.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
builder.setCategory(NotificationCompat.CATEGORY_PROGRESS)
|
||||||
builder.setStyle(null)
|
builder.setStyle(null)
|
||||||
|
|||||||
@@ -129,6 +129,8 @@ class DownloadService : BaseService() {
|
|||||||
}
|
}
|
||||||
if (job.isCancelled) {
|
if (job.isCancelled) {
|
||||||
notificationItem.dismiss()
|
notificationItem.dismiss()
|
||||||
|
jobs.remove(startId)
|
||||||
|
jobCount.value = jobs.size
|
||||||
} else {
|
} else {
|
||||||
notificationItem.notify(job.progressValue, -1L)
|
notificationItem.notify(job.progressValue, -1L)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,10 +77,10 @@
|
|||||||
android:id="@+id/textView_percent"
|
android:id="@+id/textView_percent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||||
app:layout_constraintBaseline_toBaselineOf="@id/textView_status"
|
app:layout_constraintBaseline_toBaselineOf="@id/textView_status"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
tools:text="25%" />
|
tools:text="25%" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -98,4 +98,30 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@id/textView_status"
|
app:layout_constraintTop_toBottomOf="@id/textView_status"
|
||||||
tools:text="@tools:sample/lorem[3]" />
|
tools:text="@tools:sample/lorem[3]" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_cancel"
|
||||||
|
style="?buttonBarButtonStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@android:string/cancel"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/textView_details"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_resume"
|
||||||
|
style="?buttonBarButtonStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/try_again"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/button_cancel"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/textView_details"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -317,5 +317,4 @@
|
|||||||
<string name="data_deletion">Удаление данных</string>
|
<string name="data_deletion">Удаление данных</string>
|
||||||
<string name="clear_cookies_summary">Может помочь в случае каких-либо проблем. Все авторизации будут аннулированы</string>
|
<string name="clear_cookies_summary">Может помочь в случае каких-либо проблем. Все авторизации будут аннулированы</string>
|
||||||
<string name="show_all">Показать все</string>
|
<string name="show_all">Показать все</string>
|
||||||
<string name="downloading_manga">Downloading manga</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user