Cleanup logic in UpdatesPresenter (#8035)
This commit is contained in:
parent
c740558327
commit
c2a831dded
3 changed files with 77 additions and 94 deletions
|
@ -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 } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Reference in a new issue