mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-19 15:31:13 +01:00
Use Voyager on Updates tab (#8603)
* Use Voyager on Updates tab * Fix back press * Fix selection
This commit is contained in:
@@ -485,9 +485,8 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
// Updates screen has custom back handler
|
||||
if (router.getControllerWithTag("${R.id.nav_updates}") != null) {
|
||||
router.handleBack()
|
||||
if (router.handleBack()) {
|
||||
// A Router is consuming back press
|
||||
return
|
||||
}
|
||||
val backstackSize = router.backstackSize
|
||||
@@ -495,12 +494,10 @@ class MainActivity : BaseActivity() {
|
||||
if (backstackSize == 1 && startScreen == null) {
|
||||
// Return to start screen
|
||||
moveToStartScreen()
|
||||
} else if (startScreen != null && router.handleBack()) {
|
||||
// Clear selection for Library screen
|
||||
} else if (shouldHandleExitConfirmation()) {
|
||||
// Exit confirmation (resets after 2 seconds)
|
||||
lifecycleScope.launchUI { resetExitConfirmation() }
|
||||
} else if (backstackSize == 1 || !router.handleBack()) {
|
||||
} else if (backstackSize == 1) {
|
||||
// Regular back (i.e. closing the app)
|
||||
if (libraryPreferences.autoClearChapterCache().get()) {
|
||||
chapterCache.clear()
|
||||
|
||||
@@ -1,39 +1,13 @@
|
||||
package eu.kanade.tachiyomi.ui.updates
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import eu.kanade.presentation.updates.UpdateScreen
|
||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||
import cafe.adriel.voyager.navigator.Navigator
|
||||
import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
|
||||
class UpdatesController :
|
||||
FullComposeController<UpdatesPresenter>(),
|
||||
RootController {
|
||||
|
||||
override fun createPresenter() = UpdatesPresenter()
|
||||
|
||||
class UpdatesController : BasicFullComposeController(), RootController {
|
||||
@Composable
|
||||
override fun ComposeContent() {
|
||||
UpdateScreen(
|
||||
presenter = presenter,
|
||||
onClickCover = { item ->
|
||||
router.pushController(MangaController(item.update.mangaId))
|
||||
},
|
||||
onBackClicked = {
|
||||
(activity as? MainActivity)?.moveToStartScreen()
|
||||
},
|
||||
)
|
||||
|
||||
LaunchedEffect(presenter.selectionMode) {
|
||||
(activity as? MainActivity)?.showBottomNav(presenter.selectionMode.not())
|
||||
}
|
||||
LaunchedEffect(presenter.isLoading) {
|
||||
if (!presenter.isLoading) {
|
||||
(activity as? MainActivity)?.ready = true
|
||||
}
|
||||
}
|
||||
Navigator(screen = UpdatesScreen)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package eu.kanade.tachiyomi.ui.updates
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.updates.UpdateScreen
|
||||
import eu.kanade.presentation.updates.UpdatesDeleteConfirmationDialog
|
||||
import eu.kanade.presentation.util.LocalRouter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel.Event
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
||||
object UpdatesScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val router = LocalRouter.currentOrThrow
|
||||
val screenModel = rememberScreenModel { UpdatesScreenModel() }
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
UpdateScreen(
|
||||
state = state,
|
||||
snackbarHostState = screenModel.snackbarHostState,
|
||||
incognitoMode = screenModel.isIncognitoMode,
|
||||
downloadedOnlyMode = screenModel.isDownloadOnly,
|
||||
lastUpdated = screenModel.lastUpdated,
|
||||
relativeTime = screenModel.relativeTime,
|
||||
onClickCover = { item -> router.pushController(MangaController(item.update.mangaId)) },
|
||||
onSelectAll = screenModel::toggleAllSelection,
|
||||
onInvertSelection = screenModel::invertSelection,
|
||||
onUpdateLibrary = screenModel::updateLibrary,
|
||||
onDownloadChapter = screenModel::downloadChapters,
|
||||
onMultiBookmarkClicked = screenModel::bookmarkUpdates,
|
||||
onMultiMarkAsReadClicked = screenModel::markUpdatesRead,
|
||||
onMultiDeleteClicked = screenModel::showConfirmDeleteChapters,
|
||||
onUpdateSelected = screenModel::toggleSelection,
|
||||
onOpenChapter = {
|
||||
val intent = ReaderActivity.newIntent(context, it.update.mangaId, it.update.chapterId)
|
||||
context.startActivity(intent)
|
||||
},
|
||||
)
|
||||
|
||||
val onDismissDialog = { screenModel.setDialog(null) }
|
||||
when (val dialog = state.dialog) {
|
||||
is UpdatesScreenModel.Dialog.DeleteConfirmation -> {
|
||||
UpdatesDeleteConfirmationDialog(
|
||||
onDismissRequest = onDismissDialog,
|
||||
onConfirm = { screenModel.deleteChapters(dialog.toDelete) },
|
||||
)
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
screenModel.events.collectLatest { event ->
|
||||
when (event) {
|
||||
Event.InternalError -> screenModel.snackbarHostState.showSnackbar(context.getString(R.string.internal_error))
|
||||
is Event.LibraryUpdateTriggered -> {
|
||||
val msg = if (event.started) {
|
||||
R.string.updating_library
|
||||
} else {
|
||||
R.string.update_already_running
|
||||
}
|
||||
screenModel.snackbarHostState.showSnackbar(context.getString(msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(state.selectionMode) {
|
||||
(context as? MainActivity)?.showBottomNav(!state.selectionMode)
|
||||
}
|
||||
LaunchedEffect(state.isLoading) {
|
||||
if (!state.isLoading) {
|
||||
(context as? MainActivity)?.ready = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
package eu.kanade.tachiyomi.ui.updates
|
||||
|
||||
import android.os.Bundle
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.coroutineScope
|
||||
import eu.kanade.core.prefs.asState
|
||||
import eu.kanade.core.util.addOrRemove
|
||||
import eu.kanade.core.util.insertSeparators
|
||||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.domain.chapter.interactor.GetChapter
|
||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||
@@ -16,27 +22,27 @@ import eu.kanade.domain.ui.UiPreferences
|
||||
import eu.kanade.domain.updates.interactor.GetUpdates
|
||||
import eu.kanade.domain.updates.model.UpdatesWithRelations
|
||||
import eu.kanade.presentation.components.ChapterDownloadAction
|
||||
import eu.kanade.presentation.updates.UpdatesState
|
||||
import eu.kanade.presentation.updates.UpdatesStateImpl
|
||||
import eu.kanade.presentation.updates.UpdatesUiModel
|
||||
import eu.kanade.tachiyomi.data.download.DownloadCache
|
||||
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.library.LibraryUpdateService
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.lang.toDateKey
|
||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import uy.kohesive.injekt.Injekt
|
||||
@@ -45,8 +51,7 @@ import java.text.DateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
|
||||
class UpdatesPresenter(
|
||||
private val state: UpdatesStateImpl = UpdatesState() as UpdatesStateImpl,
|
||||
class UpdatesScreenModel(
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val downloadManager: DownloadManager = Injekt.get(),
|
||||
private val downloadCache: DownloadCache = Injekt.get(),
|
||||
@@ -55,30 +60,29 @@ class UpdatesPresenter(
|
||||
private val getUpdates: GetUpdates = Injekt.get(),
|
||||
private val getManga: GetManga = Injekt.get(),
|
||||
private val getChapter: GetChapter = Injekt.get(),
|
||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||
basePreferences: BasePreferences = Injekt.get(),
|
||||
uiPreferences: UiPreferences = Injekt.get(),
|
||||
libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
) : BasePresenter<UpdatesController>(), UpdatesState by state {
|
||||
|
||||
val isDownloadOnly: Boolean by basePreferences.downloadedOnly().asState()
|
||||
val isIncognitoMode: Boolean by basePreferences.incognitoMode().asState()
|
||||
|
||||
val lastUpdated by libraryPreferences.libraryUpdateLastTimestamp().asState()
|
||||
|
||||
val relativeTime: Int by uiPreferences.relativeTime().asState()
|
||||
val dateFormat: DateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get()))
|
||||
) : StateScreenModel<UpdatesState>(UpdatesState()) {
|
||||
|
||||
private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
|
||||
val events: Flow<Event> = _events.receiveAsFlow()
|
||||
|
||||
val isDownloadOnly: Boolean by basePreferences.downloadedOnly().asState(coroutineScope)
|
||||
val isIncognitoMode: Boolean by basePreferences.incognitoMode().asState(coroutineScope)
|
||||
|
||||
val lastUpdated by libraryPreferences.libraryUpdateLastTimestamp().asState(coroutineScope)
|
||||
|
||||
val relativeTime: Int by uiPreferences.relativeTime().asState(coroutineScope)
|
||||
val dateFormat: DateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get()))
|
||||
|
||||
// First and last selected index in list
|
||||
private val selectedPositions: Array<Int> = arrayOf(-1, -1)
|
||||
private val selectedChapterIds: HashSet<Long> = HashSet()
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
|
||||
presenterScope.launchIO {
|
||||
init {
|
||||
coroutineScope.launchIO {
|
||||
// Set date limit for recent chapters
|
||||
val calendar = Calendar.getInstance().apply {
|
||||
time = Date()
|
||||
@@ -89,35 +93,24 @@ class UpdatesPresenter(
|
||||
getUpdates.subscribe(calendar).distinctUntilChanged(),
|
||||
downloadCache.changes,
|
||||
) { updates, _ -> updates }
|
||||
.onStart { delay(500) } // Defer to avoid crashing on initial render
|
||||
.catch {
|
||||
logcat(LogPriority.ERROR, it)
|
||||
_events.send(Event.InternalError)
|
||||
}
|
||||
.collectLatest { updates ->
|
||||
state.items = updates.toUpdateItems()
|
||||
state.isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
presenterScope.launchIO {
|
||||
downloadManager.queue.statusFlow()
|
||||
.catch { logcat(LogPriority.ERROR, it) }
|
||||
.collect {
|
||||
withUIContext {
|
||||
updateDownloadState(it)
|
||||
mutableState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
items = updates.toUpdateItems(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
presenterScope.launchIO {
|
||||
downloadManager.queue.progressFlow()
|
||||
coroutineScope.launchIO {
|
||||
merge(downloadManager.queue.statusFlow(), downloadManager.queue.progressFlow())
|
||||
.catch { logcat(LogPriority.ERROR, it) }
|
||||
.collect {
|
||||
withUIContext {
|
||||
updateDownloadState(it)
|
||||
}
|
||||
}
|
||||
.collect(this@UpdatesScreenModel::updateDownloadState)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,37 +137,46 @@ class UpdatesPresenter(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateLibrary(): Boolean {
|
||||
val started = LibraryUpdateService.start(Injekt.get<Application>())
|
||||
coroutineScope.launch {
|
||||
_events.send(Event.LibraryUpdateTriggered(started))
|
||||
}
|
||||
return started
|
||||
}
|
||||
|
||||
/**
|
||||
* Update status of chapters.
|
||||
*
|
||||
* @param download download object containing progress.
|
||||
*/
|
||||
private fun updateDownloadState(download: Download) {
|
||||
state.items = items.toMutableList().apply {
|
||||
val modifiedIndex = indexOfFirst {
|
||||
it.update.chapterId == download.chapter.id
|
||||
}
|
||||
if (modifiedIndex < 0) return@apply
|
||||
mutableState.update { state ->
|
||||
val newItems = state.items.toMutableList().apply {
|
||||
val modifiedIndex = indexOfFirst { it.update.chapterId == download.chapter.id }
|
||||
if (modifiedIndex < 0) return@apply
|
||||
|
||||
val item = get(modifiedIndex)
|
||||
set(
|
||||
modifiedIndex,
|
||||
item.copy(
|
||||
downloadStateProvider = { download.status },
|
||||
downloadProgressProvider = { download.progress },
|
||||
),
|
||||
)
|
||||
val item = get(modifiedIndex)
|
||||
set(
|
||||
modifiedIndex,
|
||||
item.copy(
|
||||
downloadStateProvider = { download.status },
|
||||
downloadProgressProvider = { download.progress },
|
||||
),
|
||||
)
|
||||
}
|
||||
state.copy(items = newItems)
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadChapters(items: List<UpdatesItem>, action: ChapterDownloadAction) {
|
||||
if (items.isEmpty()) return
|
||||
presenterScope.launch {
|
||||
coroutineScope.launch {
|
||||
when (action) {
|
||||
ChapterDownloadAction.START -> {
|
||||
downloadChapters(items)
|
||||
if (items.any { it.downloadStateProvider() == Download.State.ERROR }) {
|
||||
DownloadService.start(view!!.activity!!)
|
||||
DownloadService.start(Injekt.get<Application>())
|
||||
}
|
||||
}
|
||||
ChapterDownloadAction.START_NOW -> {
|
||||
@@ -209,7 +211,7 @@ class UpdatesPresenter(
|
||||
* @param read whether to mark chapters as read or unread.
|
||||
*/
|
||||
fun markUpdatesRead(updates: List<UpdatesItem>, read: Boolean) {
|
||||
presenterScope.launchIO {
|
||||
coroutineScope.launchIO {
|
||||
setReadStatus.await(
|
||||
read = read,
|
||||
chapters = updates
|
||||
@@ -217,6 +219,7 @@ class UpdatesPresenter(
|
||||
.toTypedArray(),
|
||||
)
|
||||
}
|
||||
toggleAllSelection(false)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,20 +227,21 @@ class UpdatesPresenter(
|
||||
* @param updates the list of chapters to bookmark.
|
||||
*/
|
||||
fun bookmarkUpdates(updates: List<UpdatesItem>, bookmark: Boolean) {
|
||||
presenterScope.launchIO {
|
||||
coroutineScope.launchIO {
|
||||
updates
|
||||
.filterNot { it.update.bookmark == bookmark }
|
||||
.map { ChapterUpdate(id = it.update.chapterId, bookmark = bookmark) }
|
||||
.let { updateChapter.awaitAll(it) }
|
||||
}
|
||||
toggleAllSelection(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the given list of chapters with the manager.
|
||||
* @param updatesItem the list of chapters to download.
|
||||
*/
|
||||
fun downloadChapters(updatesItem: List<UpdatesItem>) {
|
||||
presenterScope.launchNonCancellable {
|
||||
private fun downloadChapters(updatesItem: List<UpdatesItem>) {
|
||||
coroutineScope.launchNonCancellable {
|
||||
val groupedUpdates = updatesItem.groupBy { it.update.mangaId }.values
|
||||
for (updates in groupedUpdates) {
|
||||
val mangaId = updates.first().update.mangaId
|
||||
@@ -256,7 +260,7 @@ class UpdatesPresenter(
|
||||
* @param updatesItem list of chapters
|
||||
*/
|
||||
fun deleteChapters(updatesItem: List<UpdatesItem>) {
|
||||
presenterScope.launchNonCancellable {
|
||||
coroutineScope.launchNonCancellable {
|
||||
updatesItem
|
||||
.groupBy { it.update.mangaId }
|
||||
.entries
|
||||
@@ -267,6 +271,11 @@ class UpdatesPresenter(
|
||||
downloadManager.deleteChapters(chapters, manga, source)
|
||||
}
|
||||
}
|
||||
toggleAllSelection(false)
|
||||
}
|
||||
|
||||
fun showConfirmDeleteChapters(updatesItem: List<UpdatesItem>) {
|
||||
setDialog(Dialog.DeleteConfirmation(updatesItem))
|
||||
}
|
||||
|
||||
fun toggleSelection(
|
||||
@@ -275,85 +284,132 @@ class UpdatesPresenter(
|
||||
userSelected: Boolean = false,
|
||||
fromLongPress: Boolean = false,
|
||||
) {
|
||||
state.items = items.toMutableList().apply {
|
||||
val selectedIndex = indexOfFirst { it.update.chapterId == item.update.chapterId }
|
||||
if (selectedIndex < 0) return@apply
|
||||
mutableState.update { state ->
|
||||
val newItems = state.items.toMutableList().apply {
|
||||
val selectedIndex = indexOfFirst { it.update.chapterId == item.update.chapterId }
|
||||
if (selectedIndex < 0) return@apply
|
||||
|
||||
val selectedItem = get(selectedIndex)
|
||||
if (selectedItem.selected == selected) return@apply
|
||||
val selectedItem = get(selectedIndex)
|
||||
if (selectedItem.selected == selected) return@apply
|
||||
|
||||
val firstSelection = none { it.selected }
|
||||
set(selectedIndex, selectedItem.copy(selected = selected))
|
||||
selectedChapterIds.addOrRemove(item.update.chapterId, selected)
|
||||
val firstSelection = none { it.selected }
|
||||
set(selectedIndex, selectedItem.copy(selected = selected))
|
||||
selectedChapterIds.addOrRemove(item.update.chapterId, selected)
|
||||
|
||||
if (selected && userSelected && fromLongPress) {
|
||||
if (firstSelection) {
|
||||
selectedPositions[0] = selectedIndex
|
||||
selectedPositions[1] = selectedIndex
|
||||
} else {
|
||||
// Try to select the items in-between when possible
|
||||
val range: IntRange
|
||||
if (selectedIndex < selectedPositions[0]) {
|
||||
range = selectedIndex + 1 until selectedPositions[0]
|
||||
if (selected && userSelected && fromLongPress) {
|
||||
if (firstSelection) {
|
||||
selectedPositions[0] = selectedIndex
|
||||
} else if (selectedIndex > selectedPositions[1]) {
|
||||
range = (selectedPositions[1] + 1) until selectedIndex
|
||||
selectedPositions[1] = selectedIndex
|
||||
} else {
|
||||
// Just select itself
|
||||
range = IntRange.EMPTY
|
||||
}
|
||||
// Try to select the items in-between when possible
|
||||
val range: IntRange
|
||||
if (selectedIndex < selectedPositions[0]) {
|
||||
range = selectedIndex + 1 until selectedPositions[0]
|
||||
selectedPositions[0] = selectedIndex
|
||||
} else if (selectedIndex > selectedPositions[1]) {
|
||||
range = (selectedPositions[1] + 1) until selectedIndex
|
||||
selectedPositions[1] = selectedIndex
|
||||
} else {
|
||||
// Just select itself
|
||||
range = IntRange.EMPTY
|
||||
}
|
||||
|
||||
range.forEach {
|
||||
val inbetweenItem = get(it)
|
||||
if (!inbetweenItem.selected) {
|
||||
selectedChapterIds.add(inbetweenItem.update.chapterId)
|
||||
set(it, inbetweenItem.copy(selected = true))
|
||||
range.forEach {
|
||||
val inbetweenItem = get(it)
|
||||
if (!inbetweenItem.selected) {
|
||||
selectedChapterIds.add(inbetweenItem.update.chapterId)
|
||||
set(it, inbetweenItem.copy(selected = true))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (userSelected && !fromLongPress) {
|
||||
if (!selected) {
|
||||
if (selectedIndex == selectedPositions[0]) {
|
||||
selectedPositions[0] = indexOfFirst { it.selected }
|
||||
} else if (selectedIndex == selectedPositions[1]) {
|
||||
selectedPositions[1] = indexOfLast { it.selected }
|
||||
}
|
||||
} else {
|
||||
if (selectedIndex < selectedPositions[0]) {
|
||||
selectedPositions[0] = selectedIndex
|
||||
} else if (selectedIndex > selectedPositions[1]) {
|
||||
selectedPositions[1] = selectedIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (userSelected && !fromLongPress) {
|
||||
if (!selected) {
|
||||
if (selectedIndex == selectedPositions[0]) {
|
||||
selectedPositions[0] = indexOfFirst { it.selected }
|
||||
} else if (selectedIndex == selectedPositions[1]) {
|
||||
selectedPositions[1] = indexOfLast { it.selected }
|
||||
}
|
||||
} else {
|
||||
if (selectedIndex < selectedPositions[0]) {
|
||||
selectedPositions[0] = selectedIndex
|
||||
} else if (selectedIndex > selectedPositions[1]) {
|
||||
selectedPositions[1] = selectedIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
state.copy(items = newItems)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleAllSelection(selected: Boolean) {
|
||||
state.items = items.map {
|
||||
selectedChapterIds.addOrRemove(it.update.chapterId, selected)
|
||||
it.copy(selected = selected)
|
||||
mutableState.update { state ->
|
||||
val newItems = state.items.map {
|
||||
selectedChapterIds.addOrRemove(it.update.chapterId, selected)
|
||||
it.copy(selected = selected)
|
||||
}
|
||||
state.copy(items = newItems)
|
||||
}
|
||||
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
}
|
||||
|
||||
fun invertSelection() {
|
||||
state.items = items.map {
|
||||
selectedChapterIds.addOrRemove(it.update.chapterId, !it.selected)
|
||||
it.copy(selected = !it.selected)
|
||||
mutableState.update { state ->
|
||||
val newItems = state.items.map {
|
||||
selectedChapterIds.addOrRemove(it.update.chapterId, !it.selected)
|
||||
it.copy(selected = !it.selected)
|
||||
}
|
||||
state.copy(items = newItems)
|
||||
}
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
}
|
||||
|
||||
fun setDialog(dialog: Dialog?) {
|
||||
mutableState.update { it.copy(dialog = dialog) }
|
||||
}
|
||||
|
||||
sealed class Dialog {
|
||||
data class DeleteConfirmation(val toDelete: List<UpdatesItem>) : Dialog()
|
||||
}
|
||||
|
||||
sealed class Event {
|
||||
object InternalError : Event()
|
||||
data class LibraryUpdateTriggered(val started: Boolean) : Event()
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class UpdatesState(
|
||||
val isLoading: Boolean = true,
|
||||
val items: List<UpdatesItem> = emptyList(),
|
||||
val dialog: UpdatesScreenModel.Dialog? = null,
|
||||
) {
|
||||
val selected = items.filter { it.selected }
|
||||
val selectionMode = selected.isNotEmpty()
|
||||
|
||||
fun getUiModel(context: Context, relativeTime: Int): List<UpdatesUiModel> {
|
||||
val dateFormat = UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat().get())
|
||||
return items
|
||||
.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 -> {
|
||||
val text = afterDate.toRelativeString(
|
||||
context = context,
|
||||
range = relativeTime,
|
||||
dateFormat = dateFormat,
|
||||
)
|
||||
UpdatesUiModel.Header(text)
|
||||
}
|
||||
// Return null to avoid adding a separator between two items.
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user