Cleanup logic in UpdatesPresenter (#8035)

This commit is contained in:
AntsyLich 2022-09-20 09:56:28 +06:00 committed by GitHub
parent c740558327
commit c2a831dded
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 94 deletions

View file

@ -93,8 +93,7 @@ fun UpdateScreen(
onMultiBookmarkClicked = presenter::bookmarkUpdates, onMultiBookmarkClicked = presenter::bookmarkUpdates,
onMultiMarkAsReadClicked = presenter::markUpdatesRead, onMultiMarkAsReadClicked = presenter::markUpdatesRead,
onMultiDeleteClicked = { onMultiDeleteClicked = {
val updateItems = presenter.selected.map { it.item } presenter.dialog = Dialog.DeleteConfirmation(it)
presenter.dialog = Dialog.DeleteConfirmation(updateItems)
}, },
) )
}, },
@ -261,7 +260,7 @@ private fun UpdatesAppBar(
@Composable @Composable
private fun UpdatesBottomBar( private fun UpdatesBottomBar(
selected: List<UpdatesUiModel.Item>, selected: List<UpdatesItem>,
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit, onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit, onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit,
onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit, onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit,
@ -271,25 +270,25 @@ private fun UpdatesBottomBar(
visible = selected.isNotEmpty(), visible = selected.isNotEmpty(),
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
onBookmarkClicked = { onBookmarkClicked = {
onMultiBookmarkClicked.invoke(selected.map { it.item }, true) onMultiBookmarkClicked.invoke(selected, true)
}.takeIf { selected.any { !it.item.update.bookmark } }, }.takeIf { selected.any { !it.update.bookmark } },
onRemoveBookmarkClicked = { onRemoveBookmarkClicked = {
onMultiBookmarkClicked.invoke(selected.map { it.item }, false) onMultiBookmarkClicked.invoke(selected, false)
}.takeIf { selected.all { it.item.update.bookmark } }, }.takeIf { selected.all { it.update.bookmark } },
onMarkAsReadClicked = { onMarkAsReadClicked = {
onMultiMarkAsReadClicked(selected.map { it.item }, true) onMultiMarkAsReadClicked(selected, true)
}.takeIf { selected.any { !it.item.update.read } }, }.takeIf { selected.any { !it.update.read } },
onMarkAsUnreadClicked = { onMarkAsUnreadClicked = {
onMultiMarkAsReadClicked(selected.map { it.item }, false) onMultiMarkAsReadClicked(selected, false)
}.takeIf { selected.any { it.item.update.read } }, }.takeIf { selected.any { it.update.read } },
onDownloadClicked = { onDownloadClicked = {
onDownloadChapter(selected.map { it.item }, ChapterDownloadAction.START) onDownloadChapter(selected, ChapterDownloadAction.START)
}.takeIf { }.takeIf {
selected.any { it.item.downloadStateProvider() != Download.State.DOWNLOADED } selected.any { it.downloadStateProvider() != Download.State.DOWNLOADED }
}, },
onDeleteClicked = { onDeleteClicked = {
onMultiDeleteClicked(selected.map { it.item }) onMultiDeleteClicked(selected)
}.takeIf { selected.any { it.item.downloadStateProvider() == Download.State.DOWNLOADED } }, }.takeIf { selected.any { it.downloadStateProvider() == Download.State.DOWNLOADED } },
) )
} }

View file

@ -5,24 +5,47 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import eu.kanade.core.util.insertSeparators
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesItem
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesPresenter import eu.kanade.tachiyomi.ui.recent.updates.UpdatesPresenter
import eu.kanade.tachiyomi.util.lang.toDateKey
import java.util.Date
@Stable @Stable
interface UpdatesState { interface UpdatesState {
val isLoading: Boolean val isLoading: Boolean
val uiModels: List<UpdatesUiModel> val items: List<UpdatesItem>
val selected: List<UpdatesUiModel.Item> val selected: List<UpdatesItem>
val selectionMode: Boolean val selectionMode: Boolean
val uiModels: List<UpdatesUiModel>
var dialog: UpdatesPresenter.Dialog? var dialog: UpdatesPresenter.Dialog?
} }
fun UpdatesState(): UpdatesState = UpdatesStateImpl() fun UpdatesState(): UpdatesState = UpdatesStateImpl()
class UpdatesStateImpl : UpdatesState { class UpdatesStateImpl : UpdatesState {
override var isLoading: Boolean by mutableStateOf(true) override var isLoading: Boolean by mutableStateOf(true)
override var uiModels: List<UpdatesUiModel> by mutableStateOf(emptyList()) override var items: List<UpdatesItem> by mutableStateOf(emptyList())
override val selected: List<UpdatesUiModel.Item> by derivedStateOf { override val selected: List<UpdatesItem> by derivedStateOf {
uiModels.filterIsInstance<UpdatesUiModel.Item>() items.filter { it.selected }
.filter { it.item.selected }
} }
override val selectionMode: Boolean by derivedStateOf { selected.isNotEmpty() } override val selectionMode: Boolean by derivedStateOf { selected.isNotEmpty() }
override val uiModels: List<UpdatesUiModel> by derivedStateOf {
items.toUpdateUiModel()
}
override var dialog: UpdatesPresenter.Dialog? by mutableStateOf(null) override var dialog: UpdatesPresenter.Dialog? by mutableStateOf(null)
} }
fun List<UpdatesItem>.toUpdateUiModel(): List<UpdatesUiModel> {
return this.map {
UpdatesUiModel.Item(it)
}
.insertSeparators { before, after ->
val beforeDate = before?.item?.update?.dateFetch?.toDateKey() ?: Date(0)
val afterDate = after?.item?.update?.dateFetch?.toDateKey() ?: Date(0)
when {
beforeDate.time != afterDate.time && afterDate.time != 0L ->
UpdatesUiModel.Header(afterDate)
// Return null to avoid adding a separator between two items.
else -> null
}
}
}

View file

@ -4,7 +4,6 @@ import android.os.Bundle
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import eu.kanade.core.util.insertSeparators
import eu.kanade.domain.chapter.interactor.GetChapter import eu.kanade.domain.chapter.interactor.GetChapter
import eu.kanade.domain.chapter.interactor.SetReadStatus import eu.kanade.domain.chapter.interactor.SetReadStatus
import eu.kanade.domain.chapter.interactor.UpdateChapter import eu.kanade.domain.chapter.interactor.UpdateChapter
@ -17,7 +16,6 @@ import eu.kanade.domain.updates.model.UpdatesWithRelations
import eu.kanade.presentation.components.ChapterDownloadAction import eu.kanade.presentation.components.ChapterDownloadAction
import eu.kanade.presentation.updates.UpdatesState import eu.kanade.presentation.updates.UpdatesState
import eu.kanade.presentation.updates.UpdatesStateImpl import eu.kanade.presentation.updates.UpdatesStateImpl
import eu.kanade.presentation.updates.UpdatesUiModel
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.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
@ -26,7 +24,6 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchNonCancellableIO import eu.kanade.tachiyomi.util.lang.launchNonCancellableIO
import eu.kanade.tachiyomi.util.lang.toDateKey
import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -87,6 +84,8 @@ class UpdatesPresenter(
add(Calendar.MONTH, -3) add(Calendar.MONTH, -3)
} }
observeDownloads()
getUpdates.subscribe(calendar) getUpdates.subscribe(calendar)
.distinctUntilChanged() .distinctUntilChanged()
.catch { .catch {
@ -94,15 +93,13 @@ class UpdatesPresenter(
_events.send(Event.InternalError) _events.send(Event.InternalError)
} }
.collectLatest { updates -> .collectLatest { updates ->
state.uiModels = updates.toUpdateUiModels() state.items = updates.toUpdateItems()
state.isLoading = false state.isLoading = false
observeDownloads()
} }
} }
} }
private fun List<UpdatesWithRelations>.toUpdateUiModels(): List<UpdatesUiModel> { private fun List<UpdatesWithRelations>.toUpdateItems(): List<UpdatesItem> {
return this.map { update -> return this.map { update ->
val activeDownload = downloadManager.queue.find { update.chapterId == it.chapter.id } val activeDownload = downloadManager.queue.find { update.chapterId == it.chapter.id }
val downloaded = downloadManager.isChapterDownloaded( val downloaded = downloadManager.isChapterDownloaded(
@ -116,23 +113,12 @@ class UpdatesPresenter(
downloaded -> Download.State.DOWNLOADED downloaded -> Download.State.DOWNLOADED
else -> Download.State.NOT_DOWNLOADED else -> Download.State.NOT_DOWNLOADED
} }
val item = UpdatesItem( UpdatesItem(
update = update, update = update,
downloadStateProvider = { downloadState }, downloadStateProvider = { downloadState },
downloadProgressProvider = { activeDownload?.progress ?: 0 }, downloadProgressProvider = { activeDownload?.progress ?: 0 },
) )
UpdatesUiModel.Item(item)
} }
.insertSeparators { before, after ->
val beforeDate = before?.item?.update?.dateFetch?.toDateKey() ?: Date(0)
val afterDate = after?.item?.update?.dateFetch?.toDateKey() ?: Date(0)
when {
beforeDate.time != afterDate.time && afterDate.time != 0L ->
UpdatesUiModel.Header(afterDate)
// Return null to avoid adding a separator between two items.
else -> null
}
}
} }
private suspend fun observeDownloads() { private suspend fun observeDownloads() {
@ -165,21 +151,18 @@ class UpdatesPresenter(
* @param download download object containing progress. * @param download download object containing progress.
*/ */
private fun updateDownloadState(download: Download) { private fun updateDownloadState(download: Download) {
state.uiModels = uiModels.toMutableList().apply { state.items = items.toMutableList().apply {
val modifiedIndex = uiModels.indexOfFirst { val modifiedIndex = indexOfFirst {
it is UpdatesUiModel.Item && it.item.update.chapterId == download.chapter.id it.update.chapterId == download.chapter.id
} }
if (modifiedIndex < 0) return@apply if (modifiedIndex < 0) return@apply
var uiModel = removeAt(modifiedIndex) val item = removeAt(modifiedIndex)
if (uiModel is UpdatesUiModel.Item) { .copy(
val item = uiModel.item.copy(
downloadStateProvider = { download.status }, downloadStateProvider = { download.status },
downloadProgressProvider = { download.progress }, downloadProgressProvider = { download.progress },
) )
uiModel = UpdatesUiModel.Item(item) add(modifiedIndex, item)
}
add(modifiedIndex, uiModel)
} }
} }
@ -282,24 +265,20 @@ class UpdatesPresenter(
downloadManager.deleteChapters(chapters, manga, source).mapNotNull { it.id } downloadManager.deleteChapters(chapters, manga, source).mapNotNull { it.id }
} }
val deletedUpdates = uiModels.filter { val deletedUpdates = items.filter {
it is UpdatesUiModel.Item && deletedIds.contains(it.item.update.chapterId) deletedIds.contains(it.update.chapterId)
} }
if (deletedUpdates.isEmpty()) return@launchNonCancellableIO if (deletedUpdates.isEmpty()) return@launchNonCancellableIO
// TODO: Don't do this fake status update // TODO: Don't do this fake status update
state.uiModels = uiModels.toMutableList().apply { state.items = items.toMutableList().apply {
deletedUpdates.forEach { deletedUpdate -> deletedUpdates.forEach { deletedUpdate ->
val modifiedIndex = indexOf(deletedUpdate) val modifiedIndex = indexOf(deletedUpdate)
var uiModel = removeAt(modifiedIndex) val item = removeAt(modifiedIndex).copy(
if (uiModel is UpdatesUiModel.Item) { downloadStateProvider = { Download.State.NOT_DOWNLOADED },
val item = uiModel.item.copy( downloadProgressProvider = { 0 },
downloadStateProvider = { Download.State.NOT_DOWNLOADED }, )
downloadProgressProvider = { 0 }, add(modifiedIndex, item)
)
uiModel = UpdatesUiModel.Item(item)
}
add(modifiedIndex, uiModel)
} }
} }
} }
@ -311,18 +290,16 @@ class UpdatesPresenter(
userSelected: Boolean = false, userSelected: Boolean = false,
fromLongPress: Boolean = false, fromLongPress: Boolean = false,
) { ) {
state.uiModels = uiModels.toMutableList().apply { state.items = items.toMutableList().apply {
val modifiedIndex = indexOfFirst { val modifiedIndex = indexOfFirst { it == item }
it is UpdatesUiModel.Item && it.item == item
}
if (modifiedIndex < 0) return@apply if (modifiedIndex < 0) return@apply
val oldItem = (get(modifiedIndex) as? UpdatesUiModel.Item)?.item ?: return@apply val oldItem = get(modifiedIndex)
if ((oldItem.selected && selected) || (!oldItem.selected && !selected)) return@apply if (oldItem.selected == selected) return@apply
val firstSelection = none { it is UpdatesUiModel.Item && it.item.selected } val firstSelection = none { it.selected }
var newItem = (removeAt(modifiedIndex) as? UpdatesUiModel.Item)?.item?.copy(selected = selected) ?: return@apply var newItem = removeAt(modifiedIndex).copy(selected = selected)
add(modifiedIndex, UpdatesUiModel.Item(newItem)) add(modifiedIndex, newItem)
if (selected && userSelected && fromLongPress) { if (selected && userSelected && fromLongPress) {
if (firstSelection) { if (firstSelection) {
@ -343,20 +320,16 @@ class UpdatesPresenter(
} }
range.forEach { range.forEach {
var uiModel = removeAt(it) newItem = removeAt(it).copy(selected = true)
if (uiModel is UpdatesUiModel.Item) { add(it, newItem)
newItem = uiModel.item.copy(selected = true)
uiModel = UpdatesUiModel.Item(newItem)
}
add(it, uiModel)
} }
} }
} else if (userSelected && !fromLongPress) { } else if (userSelected && !fromLongPress) {
if (!selected) { if (!selected) {
if (modifiedIndex == selectedPositions[0]) { if (modifiedIndex == selectedPositions[0]) {
selectedPositions[0] = indexOfFirst { it is UpdatesUiModel.Item && it.item.selected } selectedPositions[0] = indexOfFirst { it.selected }
} else if (modifiedIndex == selectedPositions[1]) { } else if (modifiedIndex == selectedPositions[1]) {
selectedPositions[1] = indexOfLast { it is UpdatesUiModel.Item && it.item.selected } selectedPositions[1] = indexOfLast { it.selected }
} }
} else { } else {
if (modifiedIndex < selectedPositions[0]) { if (modifiedIndex < selectedPositions[0]) {
@ -370,28 +343,16 @@ class UpdatesPresenter(
} }
fun toggleAllSelection(selected: Boolean) { fun toggleAllSelection(selected: Boolean) {
state.uiModels = state.uiModels.map { state.items = items.map {
when (it) { it.copy(selected = selected)
is UpdatesUiModel.Header -> it
is UpdatesUiModel.Item -> {
val newItem = it.item.copy(selected = selected)
UpdatesUiModel.Item(newItem)
}
}
} }
selectedPositions[0] = -1 selectedPositions[0] = -1
selectedPositions[1] = -1 selectedPositions[1] = -1
} }
fun invertSelection() { fun invertSelection() {
state.uiModels = state.uiModels.map { state.items = items.map {
when (it) { it.copy(selected = !it.selected)
is UpdatesUiModel.Header -> it
is UpdatesUiModel.Item -> {
val newItem = it.item.let { item -> item.copy(selected = !item.selected) }
UpdatesUiModel.Item(newItem)
}
}
} }
selectedPositions[0] = -1 selectedPositions[0] = -1
selectedPositions[1] = -1 selectedPositions[1] = -1