DownloadController: Partial Compose conversion (#7969)

Item list is not changed as currently there is no fitting Compose component to
replace the drag-drop behavior.
This commit is contained in:
Ivan Iskandar 2022-09-10 09:29:40 +07:00 committed by GitHub
parent 07d1b9f3ba
commit fb9791f597
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 335 additions and 221 deletions

View file

@ -73,6 +73,7 @@ android {
signingConfig = debugType.signingConfig signingConfig = debugType.signingConfig
versionNameSuffix = debugType.versionNameSuffix versionNameSuffix = debugType.versionNameSuffix
applicationIdSuffix = debugType.applicationIdSuffix applicationIdSuffix = debugType.applicationIdSuffix
matchingFallbacks.add("release")
} }
} }
@ -252,6 +253,7 @@ dependencies {
implementation(libs.insetter) implementation(libs.insetter)
implementation(libs.markwon) implementation(libs.markwon)
implementation(libs.aboutLibraries.compose) implementation(libs.aboutLibraries.compose)
implementation(libs.cascade)
// Conductor // Conductor
implementation(libs.bundles.conductor) implementation(libs.bundles.conductor)

View file

@ -25,6 +25,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -47,6 +49,9 @@ class DownloadService : Service() {
*/ */
val runningRelay: BehaviorRelay<Boolean> = BehaviorRelay.create(false) val runningRelay: BehaviorRelay<Boolean> = BehaviorRelay.create(false)
private val _isRunning = MutableStateFlow(false)
val isRunning = _isRunning.asStateFlow()
/** /**
* Starts this service. * Starts this service.
* *
@ -98,6 +103,7 @@ class DownloadService : Service() {
startForeground(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS, getPlaceholderNotification()) startForeground(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS, getPlaceholderNotification())
wakeLock = acquireWakeLock(javaClass.name) wakeLock = acquireWakeLock(javaClass.name)
runningRelay.call(true) runningRelay.call(true)
_isRunning.value = true
subscriptions = CompositeSubscription() subscriptions = CompositeSubscription()
listenDownloaderState() listenDownloaderState()
listenNetworkChanges() listenNetworkChanges()
@ -109,6 +115,7 @@ class DownloadService : Service() {
override fun onDestroy() { override fun onDestroy() {
ioScope?.cancel() ioScope?.cancel()
runningRelay.call(false) runningRelay.call(false)
_isRunning.value = false
subscriptions.unsubscribe() subscriptions.unsubscribe()
downloadManager.stopDownloads() downloadManager.stopDownloads()
wakeLock.releaseIfNeeded() wakeLock.releaseIfNeeded()

View file

@ -83,6 +83,8 @@ class DownloadQueue(
.startWith(Unit) .startWith(Unit)
.map { this } .map { this }
fun getUpdatedAsFlow(): Flow<List<Download>> = getUpdatedObservable().asFlow()
private fun setPagesFor(download: Download) { private fun setPagesFor(download: Download) {
if (download.status == Download.State.DOWNLOADED || download.status == Download.State.ERROR) { if (download.status == Download.State.DOWNLOADED || download.status == Download.State.ERROR) {
setPagesSubject(download.pages, null) setPagesSubject(download.pages, null)

View file

@ -1,132 +1,316 @@
package eu.kanade.tachiyomi.ui.download package eu.kanade.tachiyomi.ui.download
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.core.view.isVisible import android.view.ViewGroup.MarginLayoutParams
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Pause
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
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.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.ViewCompat
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import eu.kanade.presentation.components.AppBar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import eu.kanade.presentation.components.EmptyScreen
import dev.chrisbanes.insetter.applyInsetter import eu.kanade.presentation.components.ExtendedFloatingActionButton
import eu.kanade.presentation.components.Pill
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.databinding.DownloadControllerBinding import eu.kanade.tachiyomi.databinding.DownloadListBinding
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.base.controller.FabController import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.view.shrinkOnScroll import me.saket.cascade.CascadeDropdownMenu
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
/** /**
* Controller that shows the currently active downloads. * Controller that shows the currently active downloads.
* Uses R.layout.fragment_download_queue. * Uses R.layout.fragment_download_queue.
*/ */
class DownloadController : class DownloadController :
NucleusController<DownloadControllerBinding, DownloadPresenter>(), FullComposeController<DownloadPresenter>(),
FabController,
DownloadAdapter.DownloadItemListener { DownloadAdapter.DownloadItemListener {
private lateinit var controllerBinding: DownloadListBinding
/** /**
* Adapter containing the active downloads. * Adapter containing the active downloads.
*/ */
private var adapter: DownloadAdapter? = null private var adapter: DownloadAdapter? = null
private var actionFab: ExtendedFloatingActionButton? = null
private var actionFabScrollListener: RecyclerView.OnScrollListener? = null
/** /**
* Map of subscriptions for active downloads. * Map of subscriptions for active downloads.
*/ */
private val progressSubscriptions by lazy { mutableMapOf<Download, Subscription>() } private val progressSubscriptions by lazy { mutableMapOf<Download, Subscription>() }
/** override fun createPresenter() = DownloadPresenter()
* Whether the download queue is running or not.
*/
private var isRunning: Boolean = false
init {
setHasOptionsMenu(true)
}
override fun createBinding(inflater: LayoutInflater) = DownloadControllerBinding.inflate(inflater)
override fun createPresenter(): DownloadPresenter {
return DownloadPresenter()
}
override fun getTitle(): String? {
return resources?.getString(R.string.label_download_queue)
}
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
binding.recycler.applyInsetter { viewScope.launchUI {
type(navigationBars = true) { presenter.getDownloadStatusFlow()
padding() .collect(this@DownloadController::onStatusChange)
}
viewScope.launchUI {
presenter.getDownloadProgressFlow()
.collect(this@DownloadController::onUpdateDownloadedPages)
} }
} }
// Check if download queue is empty and update information accordingly. @Composable
setInformationView() override fun ComposeContent() {
val context = LocalContext.current
val downloadList by presenter.state.collectAsState()
// Initialize adapter. val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
adapter = DownloadAdapter(this@DownloadController) var fabExpanded by remember { mutableStateOf(true) }
binding.recycler.adapter = adapter val nestedScrollConnection = remember {
adapter?.isHandleDragEnabled = true // All this lines just for fab state :/
adapter?.fastScroller = binding.fastScroller object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
fabExpanded = available.y >= 0
return scrollBehavior.nestedScrollConnection.onPreScroll(available, source)
}
// Set the layout manager for the recycler and fixed size. override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
binding.recycler.layoutManager = LinearLayoutManager(view.context) return scrollBehavior.nestedScrollConnection.onPostScroll(consumed, available, source)
binding.recycler.setHasFixedSize(true) }
actionFabScrollListener = actionFab?.shrinkOnScroll(binding.recycler) override suspend fun onPreFling(available: Velocity): Velocity {
return scrollBehavior.nestedScrollConnection.onPreFling(available)
}
// Subscribe to changes override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
DownloadService.runningRelay return scrollBehavior.nestedScrollConnection.onPostFling(consumed, available)
.observeOn(AndroidSchedulers.mainThread()) }
.subscribeUntilDestroy { onQueueStatusChange(it) }
presenter.getDownloadStatusObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy { onStatusChange(it) }
presenter.getDownloadProgressObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy { onUpdateDownloadedPages(it) }
presenter.downloadQueue.getUpdatedObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy {
updateTitle(it.size)
} }
} }
override fun configureFab(fab: ExtendedFloatingActionButton) { Scaffold(
actionFab = fab topBar = {
fab.setOnClickListener { AppBar(
val context = applicationContext ?: return@setOnClickListener titleContent = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResource(R.string.label_download_queue),
maxLines = 1,
modifier = Modifier.weight(1f, false),
overflow = TextOverflow.Ellipsis,
)
if (downloadList.isNotEmpty()) {
val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f
Pill(
text = "${downloadList.size}",
modifier = Modifier.padding(start = 4.dp),
color = MaterialTheme.colorScheme.onBackground
.copy(alpha = pillAlpha),
fontSize = 14.sp,
)
}
}
},
navigateUp = router::popCurrentController,
actions = {
if (downloadList.isNotEmpty()) {
val (expanded, onExpanded) = remember { mutableStateOf(false) }
Box {
IconButton(onClick = { onExpanded(!expanded) }) {
Icon(
imageVector = Icons.Outlined.MoreVert,
contentDescription = stringResource(R.string.label_more),
)
}
CascadeDropdownMenu(
expanded = expanded,
onDismissRequest = { onExpanded(false) },
) {
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_reorganize_by)) },
children = {
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_order_by_upload_date)) },
children = {
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_newest)) },
onClick = {
reorderQueue({ it.download.chapter.date_upload }, true)
onExpanded(false)
},
)
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_oldest)) },
onClick = {
reorderQueue({ it.download.chapter.date_upload }, false)
onExpanded(false)
},
)
},
)
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_order_by_chapter_number)) },
children = {
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_asc)) },
onClick = {
reorderQueue({ it.download.chapter.chapter_number }, false)
onExpanded(false)
},
)
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_desc)) },
onClick = {
reorderQueue({ it.download.chapter.chapter_number }, true)
onExpanded(false)
},
)
},
)
},
)
DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_cancel_all)) },
onClick = {
presenter.clearQueue(context)
onExpanded(false)
},
)
}
}
}
},
scrollBehavior = scrollBehavior,
)
},
floatingActionButton = {
AnimatedVisibility(
visible = downloadList.isNotEmpty(),
enter = fadeIn(),
exit = fadeOut(),
) {
val isRunning by DownloadService.isRunning.collectAsState()
ExtendedFloatingActionButton(
text = {
val id = if (isRunning) {
R.string.action_pause
} else {
R.string.action_resume
}
Text(text = stringResource(id))
},
icon = {
val icon = if (isRunning) {
Icons.Default.Pause
} else {
Icons.Default.PlayArrow
}
Icon(imageVector = icon, contentDescription = null)
},
onClick = {
if (isRunning) { if (isRunning) {
DownloadService.stop(context) DownloadService.stop(context)
presenter.pauseDownloads() presenter.pauseDownloads()
} else { } else {
DownloadService.start(context) DownloadService.start(context)
} }
},
setInformationView() expanded = fabExpanded,
modifier = Modifier.navigationBarsPadding(),
)
} }
},
) { contentPadding ->
if (downloadList.isEmpty()) {
EmptyScreen(textResource = R.string.information_no_downloads)
return@Scaffold
}
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
val left = with(density) { contentPadding.calculateLeftPadding(layoutDirection).toPx().roundToInt() }
val top = with(density) { contentPadding.calculateTopPadding().toPx().roundToInt() }
val right = with(density) { contentPadding.calculateRightPadding(layoutDirection).toPx().roundToInt() }
val bottom = with(density) { contentPadding.calculateBottomPadding().toPx().roundToInt() }
Box(modifier = Modifier.nestedScroll(nestedScrollConnection)) {
AndroidView(
factory = { context ->
controllerBinding = DownloadListBinding.inflate(LayoutInflater.from(context))
adapter = DownloadAdapter(this@DownloadController)
controllerBinding.recycler.adapter = adapter
adapter?.isHandleDragEnabled = true
adapter?.fastScroller = controllerBinding.fastScroller
controllerBinding.recycler.layoutManager = LinearLayoutManager(context)
ViewCompat.setNestedScrollingEnabled(controllerBinding.root, true)
controllerBinding.root
},
update = {
controllerBinding.recycler
.updatePadding(
left = left,
top = top,
right = right,
bottom = bottom,
)
controllerBinding.fastScroller
.updateLayoutParams<MarginLayoutParams> {
leftMargin = left
topMargin = top
rightMargin = right
bottomMargin = bottom
} }
override fun cleanupFab(fab: ExtendedFloatingActionButton) { adapter?.updateDataSet(downloadList)
fab.setOnClickListener(null) },
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) } )
actionFab = null }
}
} }
override fun onDestroyView(view: View) { override fun onDestroyView(view: View) {
@ -138,32 +322,6 @@ class DownloadController :
super.onDestroyView(view) super.onDestroyView(view)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.download_queue, menu)
}
override fun onPrepareOptionsMenu(menu: Menu) {
menu.findItem(R.id.clear_queue).isVisible = !presenter.downloadQueue.isEmpty()
menu.findItem(R.id.reorder).isVisible = !presenter.downloadQueue.isEmpty()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val context = applicationContext ?: return false
when (item.itemId) {
R.id.clear_queue -> {
DownloadService.stop(context)
presenter.clearQueue()
}
R.id.newest, R.id.oldest -> {
reorderQueue({ it.download.chapter.date_upload }, item.itemId == R.id.newest)
}
R.id.asc, R.id.desc -> {
reorderQueue({ it.download.chapter.chapter_number }, item.itemId == R.id.desc)
}
}
return super.onOptionsItemSelected(item)
}
private fun <R : Comparable<R>> reorderQueue(selector: (DownloadItem) -> R, reverse: Boolean = false) { private fun <R : Comparable<R>> reorderQueue(selector: (DownloadItem) -> R, reverse: Boolean = false) {
val adapter = adapter ?: return val adapter = adapter ?: return
val newDownloads = mutableListOf<Download>() val newDownloads = mutableListOf<Download>()
@ -242,30 +400,6 @@ class DownloadController :
progressSubscriptions.remove(download)?.unsubscribe() progressSubscriptions.remove(download)?.unsubscribe()
} }
/**
* Called when the queue's status has changed. Updates the visibility of the buttons.
*
* @param running whether the queue is now running or not.
*/
private fun onQueueStatusChange(running: Boolean) {
isRunning = running
activity?.invalidateOptionsMenu()
// Check if download queue is empty and update information accordingly.
setInformationView()
}
/**
* Called from the presenter to assign the downloads for the adapter.
*
* @param downloads the downloads from the queue.
*/
fun onNextDownloads(downloads: List<DownloadHeaderItem>) {
activity?.invalidateOptionsMenu()
setInformationView()
adapter?.updateDataSet(downloads)
}
/** /**
* Called when the progress of a download changes. * Called when the progress of a download changes.
* *
@ -291,39 +425,7 @@ class DownloadController :
* @return the holder of the download or null if it's not bound. * @return the holder of the download or null if it's not bound.
*/ */
private fun getHolder(download: Download): DownloadHolder? { private fun getHolder(download: Download): DownloadHolder? {
return binding.recycler.findViewHolderForItemId(download.chapter.id!!) as? DownloadHolder return controllerBinding.recycler.findViewHolderForItemId(download.chapter.id!!) as? DownloadHolder
}
/**
* Set information view when queue is empty
*/
private fun setInformationView() {
if (presenter.downloadQueue.isEmpty()) {
binding.emptyView.show(R.string.information_no_downloads)
actionFab?.isVisible = false
updateTitle()
} else {
binding.emptyView.hide()
actionFab?.apply {
isVisible = true
setText(
if (isRunning) {
R.string.action_pause
} else {
R.string.action_resume
},
)
setIconResource(
if (isRunning) {
R.drawable.ic_pause_24dp
} else {
R.drawable.ic_play_arrow_24dp
},
)
}
}
} }
/** /**
@ -373,7 +475,7 @@ class DownloadController :
?.filterIsInstance<DownloadItem>() ?.filterIsInstance<DownloadItem>()
?.map(DownloadItem::download) ?.map(DownloadItem::download)
?.partition { item.download.manga.id == it.manga.id } ?.partition { item.download.manga.id == it.manga.id }
?: Pair(listOf<Download>(), listOf<Download>()) ?: Pair(listOf(), listOf())
presenter.reorder(selectedSeries + otherSeries) presenter.reorder(selectedSeries + otherSeries)
} }
R.id.cancel_download -> { R.id.cancel_download -> {
@ -391,14 +493,4 @@ class DownloadController :
} }
} }
} }
private fun updateTitle(queueSize: Int = 0) {
val defaultTitle = getTitle()
if (queueSize == 0) {
setTitle(defaultTitle)
} else {
setTitle("$defaultTitle ($queueSize)")
}
}
} }

View file

@ -35,14 +35,25 @@ data class DownloadHeaderItem(
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other is DownloadHeaderItem) { if (javaClass != other?.javaClass) return false
return id == other.id && name == other.name
} other as DownloadHeaderItem
return false
if (id != other.id) return false
if (name != other.name) return false
if (size != other.size) return false
if (subItemsCount != other.subItemsCount) return false
if (subItems !== other.subItems) return false
return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return id.hashCode() var result = id.hashCode()
result = 31 * result + name.hashCode()
result = 31 * result + size
result = 31 * result + subItems.hashCode()
return result
} }
init { init {

View file

@ -1,14 +1,21 @@
package eu.kanade.tachiyomi.ui.download package eu.kanade.tachiyomi.ui.download
import android.content.Context
import android.os.Bundle import android.os.Bundle
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import logcat.LogPriority import logcat.LogPriority
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
/** /**
@ -21,14 +28,18 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
/** /**
* Property to get the queue from the download manager. * Property to get the queue from the download manager.
*/ */
val downloadQueue: DownloadQueue private val downloadQueue: DownloadQueue
get() = downloadManager.queue get() = downloadManager.queue
private val _state = MutableStateFlow(emptyList<DownloadHeaderItem>())
val state = _state.asStateFlow()
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
downloadQueue.getUpdatedObservable() presenterScope.launch {
.observeOn(AndroidSchedulers.mainThread()) downloadQueue.getUpdatedAsFlow()
.catch { error -> logcat(LogPriority.ERROR, error) }
.map { downloads -> .map { downloads ->
downloads downloads
.groupBy { it.source } .groupBy { it.source }
@ -38,20 +49,13 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
} }
} }
} }
.subscribeLatestCache(DownloadController::onNextDownloads) { _, error -> .collect { newList -> _state.update { newList } }
logcat(LogPriority.ERROR, error)
} }
} }
fun getDownloadStatusObservable(): Observable<Download> { fun getDownloadStatusFlow() = downloadQueue.getStatusAsFlow()
return downloadQueue.getStatusObservable()
.startWith(downloadQueue.getActiveDownloads())
}
fun getDownloadProgressObservable(): Observable<Download> { fun getDownloadProgressFlow() = downloadQueue.getProgressAsFlow()
return downloadQueue.getProgressObservable()
.onBackpressureBuffer()
}
/** /**
* Pauses the download queue. * Pauses the download queue.
@ -63,7 +67,8 @@ class DownloadPresenter : BasePresenter<DownloadController>() {
/** /**
* Clears the download queue. * Clears the download queue.
*/ */
fun clearQueue() { fun clearQueue(context: Context) {
DownloadService.stop(context)
downloadManager.clearQueue() downloadManager.clearQueue()
} }

View file

@ -30,6 +30,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="start" android:layout_gravity="start"
android:layout_marginEnd="4dp"
android:paddingHorizontal="10dp" android:paddingHorizontal="10dp"
android:paddingVertical="8dp" android:paddingVertical="8dp"
android:scaleType="center" android:scaleType="center"

View file

@ -87,6 +87,7 @@
android:id="@+id/menu" android:id="@+id/menu"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_toEndOf="@id/download_progress_text" android:layout_toEndOf="@id/download_progress_text"
android:background="?attr/selectableItemBackgroundBorderless" android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_menu" android:contentDescription="@string/action_menu"

View file

@ -11,7 +11,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="@dimen/fab_list_padding"
tools:listitem="@layout/download_item" /> tools:listitem="@layout/download_item" />
<eu.kanade.tachiyomi.widget.MaterialFastScroll <eu.kanade.tachiyomi.widget.MaterialFastScroll
@ -22,11 +21,4 @@
app:fastScrollerBubbleEnabled="false" app:fastScrollerBubbleEnabled="false"
tools:visibility="visible" /> tools:visibility="visible" />
<eu.kanade.tachiyomi.widget.EmptyView
android:id="@+id/empty_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout> </FrameLayout>

View file

@ -62,6 +62,7 @@ flexible-adapter-ui = "com.github.arkon.FlexibleAdapter:flexible-adapter-ui:c801
photoview = "com.github.chrisbanes:PhotoView:2.3.0" photoview = "com.github.chrisbanes:PhotoView:2.3.0"
directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0" directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0"
insetter = "dev.chrisbanes.insetter:insetter:0.6.1" insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
cascade = "me.saket.cascade:cascade-compose:2.0.0-beta1"
conductor-core = { module = "com.bluelinelabs:conductor", version.ref = "conductor_version" } conductor-core = { module = "com.bluelinelabs:conductor", version.ref = "conductor_version" }
conductor-support-preference = { module = "com.github.tachiyomiorg:conductor-support-preference", version.ref = "conductor_version" } conductor-support-preference = { module = "com.github.tachiyomiorg:conductor-support-preference", version.ref = "conductor_version" }