From 652351f79aa59c312ec4547febcf3129b83e0a80 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Tue, 14 Feb 2023 08:04:17 +0200 Subject: [PATCH] Improve downloads binding --- .../kotatsu/download/domain/DownloadState.kt | 7 ++ .../kotatsu/download/ui/DownloadsActivity.kt | 61 +++------------ .../download/ui/DownloadsConnection.kt | 76 +++++++++++++++++++ .../download/ui/service/DownloadService.kt | 5 +- .../main/res/layout/activity_downloads.xml | 6 +- app/src/main/res/layout/item_download.xml | 21 ++--- 6 files changed, 108 insertions(+), 68 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsConnection.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadState.kt b/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadState.kt index 4ed75b514..0b874f6df 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadState.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadState.kt @@ -9,6 +9,13 @@ sealed interface DownloadState { val manga: Manga val cover: Drawable? + override fun equals(other: Any?): Boolean + + override fun hashCode(): Int + + val isTerminal: Boolean + get() = this is Done || this is Cancelled || (this is Error && !canRetry) + class Queued( override val startId: Int, override val manga: Manga, diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt index 6ff8e2223..6fb479251 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt @@ -1,27 +1,19 @@ package org.koitharu.kotatsu.download.ui -import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.ServiceConnection import android.os.Bundle -import android.os.IBinder import androidx.core.graphics.Insets import androidx.core.view.isVisible import androidx.core.view.updatePadding -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import coil.ImageLoader import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.databinding.ActivityDownloadsBinding -import org.koitharu.kotatsu.download.ui.service.DownloadService +import javax.inject.Inject @AndroidEntryPoint class DownloadsActivity : BaseActivity() { @@ -29,6 +21,8 @@ class DownloadsActivity : BaseActivity() { @Inject lateinit var coil: ImageLoader + private lateinit var serviceConnection: DownloadsConnection + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(ActivityDownloadsBinding.inflate(layoutInflater)) @@ -38,9 +32,12 @@ class DownloadsActivity : BaseActivity() { binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing)) binding.recyclerView.setHasFixedSize(true) binding.recyclerView.adapter = adapter - val connection = DownloadServiceConnection(adapter) - bindService(Intent(this, DownloadService::class.java), connection, 0) - lifecycle.addObserver(connection) + serviceConnection = DownloadsConnection(this, this) + serviceConnection.items.observe(this) { items -> + adapter.items = items + binding.textViewHolder.isVisible = items.isNullOrEmpty() + } + serviceConnection.bind() } override fun onWindowInsetsChanged(insets: Insets) { @@ -55,46 +52,6 @@ class DownloadsActivity : BaseActivity() { ) } - private inner class DownloadServiceConnection( - private val adapter: DownloadsAdapter, - ) : ServiceConnection, DefaultLifecycleObserver { - - private var collectJob: Job? = null - - override fun onServiceConnected(name: ComponentName?, service: IBinder?) { - collectJob?.cancel() - val binder = (service as? DownloadService.DownloadBinder) - collectJob = if (binder == null) { - null - } else { - lifecycleScope.launch { - binder.downloads.collect { - setItems(it) - } - } - } - } - - override fun onServiceDisconnected(name: ComponentName?) { - collectJob?.cancel() - collectJob = null - setItems(null) - } - - override fun onDestroy(owner: LifecycleOwner) { - super.onDestroy(owner) - collectJob?.cancel() - collectJob = null - owner.lifecycle.removeObserver(this) - unbindService(this) - } - - private fun setItems(items: Collection?) { - adapter.items = items?.toList().orEmpty() - binding.textViewHolder.isVisible = items.isNullOrEmpty() - } - } - companion object { fun newIntent(context: Context) = Intent(context, DownloadsActivity::class.java) diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsConnection.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsConnection.kt new file mode 100644 index 000000000..f2577ec26 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsConnection.kt @@ -0,0 +1,76 @@ +package org.koitharu.kotatsu.download.ui + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import org.koitharu.kotatsu.download.domain.DownloadState +import org.koitharu.kotatsu.download.ui.service.DownloadService +import org.koitharu.kotatsu.utils.asFlowLiveData +import org.koitharu.kotatsu.utils.progress.PausingProgressJob + +class DownloadsConnection( + private val context: Context, + private val lifecycleOwner: LifecycleOwner, +) : ServiceConnection { + + private var bindingObserver: BindingLifecycleObserver? = null + private var collectJob: Job? = null + private val itemsFlow = MutableStateFlow>>(emptyList()) + + val items + get() = itemsFlow.asFlowLiveData() + + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + collectJob?.cancel() + val binder = (service as? DownloadService.DownloadBinder) + collectJob = if (binder == null) { + null + } else { + lifecycleOwner.lifecycleScope.launch { + binder.downloads.collect { + itemsFlow.value = it + } + } + } + } + + override fun onServiceDisconnected(name: ComponentName?) { + collectJob?.cancel() + collectJob = null + itemsFlow.value = itemsFlow.value.filter { it.progressValue.isTerminal } + } + + fun bind() { + if (bindingObserver != null) { + return + } + bindingObserver = BindingLifecycleObserver().also { + lifecycleOwner.lifecycle.addObserver(it) + } + context.bindService(Intent(context, DownloadService::class.java), this, 0) + } + + fun unbind() { + bindingObserver?.let { + lifecycleOwner.lifecycle.removeObserver(it) + } + bindingObserver = null + context.unbindService(this) + } + + private inner class BindingLifecycleObserver : DefaultLifecycleObserver { + + override fun onDestroy(owner: LifecycleOwner) { + super.onDestroy(owner) + unbind() + } + } +} 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 34be0abb2..365c51c39 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 @@ -66,7 +66,7 @@ class DownloadService : BaseService() { val intentFilter = IntentFilter() intentFilter.addAction(ACTION_DOWNLOAD_CANCEL) intentFilter.addAction(ACTION_DOWNLOAD_RESUME) - registerReceiver(controlReceiver, intentFilter) + ContextCompat.registerReceiver(this, controlReceiver, intentFilter, ContextCompat.RECEIVER_NOT_EXPORTED) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -155,9 +155,6 @@ class DownloadService : BaseService() { !state.isTerminal } - private val DownloadState.isTerminal: Boolean - get() = this is DownloadState.Done || this is DownloadState.Cancelled || (this is DownloadState.Error && !canRetry) - @MainThread private fun stopSelfIfIdle() { if (jobs.any { (_, job) -> job.isActive }) { diff --git a/app/src/main/res/layout/activity_downloads.xml b/app/src/main/res/layout/activity_downloads.xml index b26fe5d07..64c63cffe 100644 --- a/app/src/main/res/layout/activity_downloads.xml +++ b/app/src/main/res/layout/activity_downloads.xml @@ -14,9 +14,8 @@ @@ -39,7 +38,8 @@ android:paddingHorizontal="@dimen/list_spacing" android:scrollbars="vertical" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" - app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> + app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" + tools:listitem="@layout/item_download" /> + android:orientation="horizontal"> + tools:progress="25" />