mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-10 12:47:26 +01:00
Use Stable interface for Updates screen + Cleanup (#7627)
* Use Stable interface for Updates screen + Cleanup Co-Authored-By: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> * Disable swipe refresh in selection mode * Review Changes Co-Authored-By: Andreas <6576096+ghostbear@users.noreply.github.com> * Review Changes 2 Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Co-authored-by: Andreas <6576096+ghostbear@users.noreply.github.com>
This commit is contained in:
parent
d49ec41f3a
commit
4774deb1ef
@ -0,0 +1,34 @@
|
|||||||
|
package eu.kanade.presentation.updates
|
||||||
|
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UpdatesDeleteConfirmationDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onConfirm: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(R.string.confirm_delete_chapters))
|
||||||
|
},
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = {
|
||||||
|
onConfirm()
|
||||||
|
onDismissRequest()
|
||||||
|
},) {
|
||||||
|
Text(text = stringResource(android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(android.R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.presentation.updates
|
package eu.kanade.presentation.updates
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
@ -20,9 +21,9 @@ import androidx.compose.material.icons.filled.SelectAll
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.toMutableStateList
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||||
@ -38,97 +39,78 @@ import eu.kanade.presentation.util.bottomNavPaddingValues
|
|||||||
import eu.kanade.presentation.util.plus
|
import eu.kanade.presentation.util.plus
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesItem
|
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesItem
|
||||||
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesState
|
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesPresenter
|
||||||
import uy.kohesive.injekt.Injekt
|
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesPresenter.Dialog
|
||||||
import uy.kohesive.injekt.api.get
|
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesPresenter.Event
|
||||||
import java.text.DateFormat
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpdateScreen(
|
fun UpdateScreen(
|
||||||
state: UpdatesState.Success,
|
presenter: UpdatesPresenter,
|
||||||
onClickCover: (UpdatesItem) -> Unit,
|
onClickCover: (UpdatesItem) -> Unit,
|
||||||
onClickUpdate: (UpdatesItem) -> Unit,
|
|
||||||
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
|
|
||||||
onUpdateLibrary: () -> Unit,
|
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
// For bottom action menu
|
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
|
||||||
onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit,
|
|
||||||
onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit,
|
|
||||||
onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
|
|
||||||
// Miscellaneous
|
|
||||||
preferences: PreferencesHelper = Injekt.get(),
|
|
||||||
) {
|
) {
|
||||||
val updatesListState = rememberLazyListState()
|
val updatesListState = rememberLazyListState()
|
||||||
val insetPaddingValue = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
val insetPaddingValue = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
||||||
|
|
||||||
val relativeTime: Int = remember { preferences.relativeTime().get() }
|
|
||||||
val dateFormat: DateFormat = remember { preferences.dateFormat() }
|
|
||||||
|
|
||||||
val uiModels = remember(state) {
|
|
||||||
state.uiModels
|
|
||||||
}
|
|
||||||
val itemUiModels = remember(uiModels) {
|
|
||||||
uiModels.filterIsInstance<UpdatesUiModel.Item>()
|
|
||||||
}
|
|
||||||
// To prevent selection from getting removed during an update to a item in list
|
|
||||||
val updateIdList = remember(itemUiModels) {
|
|
||||||
itemUiModels.map { it.item.update.chapterId }
|
|
||||||
}
|
|
||||||
val selected = remember(updateIdList) {
|
|
||||||
emptyList<UpdatesUiModel.Item>().toMutableStateList()
|
|
||||||
}
|
|
||||||
// First and last selected index in list
|
|
||||||
val selectedPositions = remember(uiModels) { arrayOf(-1, -1) }
|
|
||||||
|
|
||||||
val internalOnBackPressed = {
|
val internalOnBackPressed = {
|
||||||
if (selected.isNotEmpty()) {
|
if (presenter.selectionMode) {
|
||||||
selected.clear()
|
presenter.toggleAllSelection(false)
|
||||||
} else {
|
} else {
|
||||||
onBackClicked()
|
onBackClicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BackHandler(onBack = internalOnBackPressed)
|
BackHandler(onBack = internalOnBackPressed)
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val onUpdateLibrary = {
|
||||||
|
if (LibraryUpdateService.start(context)) {
|
||||||
|
context.toast(R.string.updating_library)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(insetPaddingValue),
|
.padding(insetPaddingValue),
|
||||||
topBar = {
|
topBar = {
|
||||||
UpdatesAppBar(
|
UpdatesAppBar(
|
||||||
selected = selected,
|
incognitoMode = presenter.isIncognitoMode,
|
||||||
incognitoMode = state.isIncognitoMode,
|
downloadedOnlyMode = presenter.isDownloadOnly,
|
||||||
downloadedOnlyMode = state.isDownloadedOnlyMode,
|
|
||||||
onUpdateLibrary = onUpdateLibrary,
|
onUpdateLibrary = onUpdateLibrary,
|
||||||
actionModeCounter = selected.size,
|
actionModeCounter = presenter.selected.size,
|
||||||
onSelectAll = {
|
onSelectAll = { presenter.toggleAllSelection(true) },
|
||||||
selected.clear()
|
onInvertSelection = { presenter.invertSelection() },
|
||||||
selected.addAll(itemUiModels)
|
onCancelActionMode = { presenter.toggleAllSelection(false) },
|
||||||
},
|
|
||||||
onInvertSelection = {
|
|
||||||
val toSelect = itemUiModels - selected
|
|
||||||
selected.clear()
|
|
||||||
selected.addAll(toSelect)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
UpdatesBottomBar(
|
UpdatesBottomBar(
|
||||||
selected = selected,
|
selected = presenter.selected,
|
||||||
onDownloadChapter = onDownloadChapter,
|
onDownloadChapter = onDownloadChapter,
|
||||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
onMultiBookmarkClicked = presenter::bookmarkUpdates,
|
||||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
onMultiMarkAsReadClicked = presenter::markUpdatesRead,
|
||||||
onMultiDeleteClicked = onMultiDeleteClicked,
|
onMultiDeleteClicked = {
|
||||||
|
val updateItems = presenter.selected.map { it.item }
|
||||||
|
presenter.dialog = Dialog.DeleteConfirmation(updateItems)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
val contentPaddingWithNavBar = bottomNavPaddingValues + contentPadding +
|
// During selection mode bottom nav is not visible
|
||||||
WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()
|
val contentPaddingWithNavBar = (if (presenter.selectionMode) PaddingValues() else bottomNavPaddingValues) +
|
||||||
|
contentPadding + WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()
|
||||||
|
|
||||||
SwipeRefresh(
|
SwipeRefresh(
|
||||||
state = rememberSwipeRefreshState(state.showSwipeRefreshIndicator),
|
state = rememberSwipeRefreshState(isRefreshing = false),
|
||||||
onRefresh = onUpdateLibrary,
|
onRefresh = onUpdateLibrary,
|
||||||
|
swipeEnabled = presenter.selectionMode.not(),
|
||||||
indicatorPadding = contentPaddingWithNavBar,
|
indicatorPadding = contentPaddingWithNavBar,
|
||||||
indicator = { s, trigger ->
|
indicator = { s, trigger ->
|
||||||
SwipeRefreshIndicator(
|
SwipeRefreshIndicator(
|
||||||
@ -137,7 +119,7 @@ fun UpdateScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (uiModels.isEmpty()) {
|
if (presenter.uiModels.isEmpty()) {
|
||||||
EmptyScreen(textResource = R.string.information_no_recent)
|
EmptyScreen(textResource = R.string.information_no_recent)
|
||||||
} else {
|
} else {
|
||||||
VerticalFastScroller(
|
VerticalFastScroller(
|
||||||
@ -152,27 +134,49 @@ fun UpdateScreen(
|
|||||||
contentPadding = contentPaddingWithNavBar,
|
contentPadding = contentPaddingWithNavBar,
|
||||||
) {
|
) {
|
||||||
updatesUiItems(
|
updatesUiItems(
|
||||||
uiModels = uiModels,
|
uiModels = presenter.uiModels,
|
||||||
itemUiModels = itemUiModels,
|
selectionMode = presenter.selectionMode,
|
||||||
selected = selected,
|
onUpdateSelected = presenter::toggleSelection,
|
||||||
selectedPositions = selectedPositions,
|
|
||||||
onClickCover = onClickCover,
|
onClickCover = onClickCover,
|
||||||
onClickUpdate = onClickUpdate,
|
onClickUpdate = {
|
||||||
|
val intent = ReaderActivity.newIntent(context, it.update.mangaId, it.update.chapterId)
|
||||||
|
context.startActivity(intent)
|
||||||
|
},
|
||||||
onDownloadChapter = onDownloadChapter,
|
onDownloadChapter = onDownloadChapter,
|
||||||
relativeTime = relativeTime,
|
relativeTime = presenter.relativeTime,
|
||||||
dateFormat = dateFormat,
|
dateFormat = presenter.dateFormat,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val onDismissDialog = { presenter.dialog = null }
|
||||||
|
when (val dialog = presenter.dialog) {
|
||||||
|
is Dialog.DeleteConfirmation -> {
|
||||||
|
UpdatesDeleteConfirmationDialog(
|
||||||
|
onDismissRequest = onDismissDialog,
|
||||||
|
onConfirm = {
|
||||||
|
presenter.deleteChapters(dialog.toDelete)
|
||||||
|
presenter.toggleAllSelection(false)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
null -> {}
|
||||||
|
}
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
presenter.events.collectLatest { event ->
|
||||||
|
when (event) {
|
||||||
|
Event.InternalError -> context.toast(R.string.internal_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpdatesAppBar(
|
fun UpdatesAppBar(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
selected: MutableList<UpdatesUiModel.Item>,
|
|
||||||
incognitoMode: Boolean,
|
incognitoMode: Boolean,
|
||||||
downloadedOnlyMode: Boolean,
|
downloadedOnlyMode: Boolean,
|
||||||
onUpdateLibrary: () -> Unit,
|
onUpdateLibrary: () -> Unit,
|
||||||
@ -180,6 +184,7 @@ fun UpdatesAppBar(
|
|||||||
actionModeCounter: Int,
|
actionModeCounter: Int,
|
||||||
onSelectAll: () -> Unit,
|
onSelectAll: () -> Unit,
|
||||||
onInvertSelection: () -> Unit,
|
onInvertSelection: () -> Unit,
|
||||||
|
onCancelActionMode: () -> Unit,
|
||||||
) {
|
) {
|
||||||
AppBar(
|
AppBar(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
@ -193,7 +198,7 @@ fun UpdatesAppBar(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actionModeCounter = actionModeCounter,
|
actionModeCounter = actionModeCounter,
|
||||||
onCancelActionMode = { selected.clear() },
|
onCancelActionMode = onCancelActionMode,
|
||||||
actionModeActions = {
|
actionModeActions = {
|
||||||
IconButton(onClick = onSelectAll) {
|
IconButton(onClick = onSelectAll) {
|
||||||
Icon(
|
Icon(
|
||||||
@ -215,7 +220,7 @@ fun UpdatesAppBar(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpdatesBottomBar(
|
fun UpdatesBottomBar(
|
||||||
selected: MutableList<UpdatesUiModel.Item>,
|
selected: List<UpdatesUiModel.Item>,
|
||||||
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,
|
||||||
@ -226,29 +231,23 @@ fun UpdatesBottomBar(
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onBookmarkClicked = {
|
onBookmarkClicked = {
|
||||||
onMultiBookmarkClicked.invoke(selected.map { it.item }, true)
|
onMultiBookmarkClicked.invoke(selected.map { it.item }, true)
|
||||||
selected.clear()
|
|
||||||
}.takeIf { selected.any { !it.item.update.bookmark } },
|
}.takeIf { selected.any { !it.item.update.bookmark } },
|
||||||
onRemoveBookmarkClicked = {
|
onRemoveBookmarkClicked = {
|
||||||
onMultiBookmarkClicked.invoke(selected.map { it.item }, false)
|
onMultiBookmarkClicked.invoke(selected.map { it.item }, false)
|
||||||
selected.clear()
|
|
||||||
}.takeIf { selected.all { it.item.update.bookmark } },
|
}.takeIf { selected.all { it.item.update.bookmark } },
|
||||||
onMarkAsReadClicked = {
|
onMarkAsReadClicked = {
|
||||||
onMultiMarkAsReadClicked(selected.map { it.item }, true)
|
onMultiMarkAsReadClicked(selected.map { it.item }, true)
|
||||||
selected.clear()
|
|
||||||
}.takeIf { selected.any { !it.item.update.read } },
|
}.takeIf { selected.any { !it.item.update.read } },
|
||||||
onMarkAsUnreadClicked = {
|
onMarkAsUnreadClicked = {
|
||||||
onMultiMarkAsReadClicked(selected.map { it.item }, false)
|
onMultiMarkAsReadClicked(selected.map { it.item }, false)
|
||||||
selected.clear()
|
|
||||||
}.takeIf { selected.any { it.item.update.read } },
|
}.takeIf { selected.any { it.item.update.read } },
|
||||||
onDownloadClicked = {
|
onDownloadClicked = {
|
||||||
onDownloadChapter(selected.map { it.item }, ChapterDownloadAction.START)
|
onDownloadChapter(selected.map { it.item }, ChapterDownloadAction.START)
|
||||||
selected.clear()
|
|
||||||
}.takeIf {
|
}.takeIf {
|
||||||
selected.any { it.item.downloadStateProvider() != Download.State.DOWNLOADED }
|
selected.any { it.item.downloadStateProvider() != Download.State.DOWNLOADED }
|
||||||
},
|
},
|
||||||
onDeleteClicked = {
|
onDeleteClicked = {
|
||||||
onMultiDeleteClicked(selected.map { it.item })
|
onMultiDeleteClicked(selected.map { it.item })
|
||||||
selected.clear()
|
|
||||||
}.takeIf { selected.any { it.item.downloadStateProvider() == Download.State.DOWNLOADED } },
|
}.takeIf { selected.any { it.item.downloadStateProvider() == Download.State.DOWNLOADED } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package eu.kanade.presentation.updates
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import eu.kanade.tachiyomi.ui.recent.updates.UpdatesPresenter
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
interface UpdatesState {
|
||||||
|
val isLoading: Boolean
|
||||||
|
val uiModels: List<UpdatesUiModel>
|
||||||
|
val selected: List<UpdatesUiModel.Item>
|
||||||
|
val selectionMode: Boolean
|
||||||
|
var dialog: UpdatesPresenter.Dialog?
|
||||||
|
}
|
||||||
|
fun UpdatesState(): UpdatesState = UpdatesStateImpl()
|
||||||
|
class UpdatesStateImpl : UpdatesState {
|
||||||
|
override var isLoading: Boolean by mutableStateOf(true)
|
||||||
|
override var uiModels: List<UpdatesUiModel> by mutableStateOf(emptyList())
|
||||||
|
override val selected: List<UpdatesUiModel.Item> by derivedStateOf {
|
||||||
|
uiModels.filterIsInstance<UpdatesUiModel.Item>()
|
||||||
|
.filter { it.item.selected }
|
||||||
|
}
|
||||||
|
override val selectionMode: Boolean by derivedStateOf { selected.isNotEmpty() }
|
||||||
|
override var dialog: UpdatesPresenter.Dialog? by mutableStateOf(null)
|
||||||
|
}
|
@ -26,7 +26,9 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -44,9 +46,8 @@ import java.text.DateFormat
|
|||||||
|
|
||||||
fun LazyListScope.updatesUiItems(
|
fun LazyListScope.updatesUiItems(
|
||||||
uiModels: List<UpdatesUiModel>,
|
uiModels: List<UpdatesUiModel>,
|
||||||
itemUiModels: List<UpdatesUiModel.Item>,
|
selectionMode: Boolean,
|
||||||
selected: MutableList<UpdatesUiModel.Item>,
|
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
|
||||||
selectedPositions: Array<Int>,
|
|
||||||
onClickCover: (UpdatesItem) -> Unit,
|
onClickCover: (UpdatesItem) -> Unit,
|
||||||
onClickUpdate: (UpdatesItem) -> Unit,
|
onClickUpdate: (UpdatesItem) -> Unit,
|
||||||
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
|
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
|
||||||
@ -78,35 +79,27 @@ fun LazyListScope.updatesUiItems(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is UpdatesUiModel.Item -> {
|
is UpdatesUiModel.Item -> {
|
||||||
val value = item.item
|
val updatesItem = item.item
|
||||||
val update = value.update
|
val update = updatesItem.update
|
||||||
UpdatesUiItem(
|
UpdatesUiItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemPlacement(),
|
||||||
update = update,
|
update = update,
|
||||||
selected = selected.contains(item),
|
selected = updatesItem.selected,
|
||||||
onClick = {
|
|
||||||
onUpdatesItemClick(
|
|
||||||
updatesItem = item,
|
|
||||||
selected = selected,
|
|
||||||
updates = itemUiModels,
|
|
||||||
selectedPositions = selectedPositions,
|
|
||||||
onUpdateClicked = onClickUpdate,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
onUpdatesItemLongClick(
|
onUpdateSelected(updatesItem, !updatesItem.selected, true, true)
|
||||||
updatesItem = item,
|
|
||||||
selected = selected,
|
|
||||||
updates = itemUiModels,
|
|
||||||
selectedPositions = selectedPositions,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
onClickCover = { if (selected.size == 0) onClickCover(value) },
|
onClick = {
|
||||||
|
when {
|
||||||
|
selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, true, false)
|
||||||
|
else -> onClickUpdate(updatesItem)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClickCover = { if (selectionMode.not()) onClickCover(updatesItem) },
|
||||||
onDownloadChapter = {
|
onDownloadChapter = {
|
||||||
if (selected.size == 0) onDownloadChapter(listOf(value), it)
|
if (selectionMode.not()) onDownloadChapter(listOf(updatesItem), it)
|
||||||
},
|
},
|
||||||
downloadStateProvider = value.downloadStateProvider,
|
downloadStateProvider = updatesItem.downloadStateProvider,
|
||||||
downloadProgressProvider = value.downloadProgressProvider,
|
downloadProgressProvider = updatesItem.downloadProgressProvider,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,12 +119,16 @@ fun UpdatesUiItem(
|
|||||||
downloadStateProvider: () -> Download.State,
|
downloadStateProvider: () -> Download.State,
|
||||||
downloadProgressProvider: () -> Int,
|
downloadProgressProvider: () -> Int,
|
||||||
) {
|
) {
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(if (selected) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent)
|
.background(if (selected) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = {
|
||||||
|
onLongClick()
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.height(56.dp)
|
.height(56.dp)
|
||||||
.padding(horizontal = horizontalPadding),
|
.padding(horizontal = horizontalPadding),
|
||||||
@ -198,73 +195,3 @@ fun UpdatesUiItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onUpdatesItemLongClick(
|
|
||||||
updatesItem: UpdatesUiModel.Item,
|
|
||||||
selected: MutableList<UpdatesUiModel.Item>,
|
|
||||||
updates: List<UpdatesUiModel.Item>,
|
|
||||||
selectedPositions: Array<Int>,
|
|
||||||
): Boolean {
|
|
||||||
if (!selected.contains(updatesItem)) {
|
|
||||||
val selectedIndex = updates.indexOf(updatesItem)
|
|
||||||
if (selected.isEmpty()) {
|
|
||||||
selected.add(updatesItem)
|
|
||||||
selectedPositions[0] = selectedIndex
|
|
||||||
selectedPositions[1] = selectedIndex
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to select the items in-between when possible
|
|
||||||
val range: IntRange
|
|
||||||
if (selectedIndex < selectedPositions[0]) {
|
|
||||||
range = selectedIndex until selectedPositions[0]
|
|
||||||
selectedPositions[0] = selectedIndex
|
|
||||||
} else if (selectedIndex > selectedPositions[1]) {
|
|
||||||
range = (selectedPositions[1] + 1)..selectedIndex
|
|
||||||
selectedPositions[1] = selectedIndex
|
|
||||||
} else {
|
|
||||||
// Just select itself
|
|
||||||
range = selectedIndex..selectedIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
range.forEach {
|
|
||||||
val toAdd = updates[it]
|
|
||||||
if (!selected.contains(toAdd)) {
|
|
||||||
selected.add(toAdd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onUpdatesItemClick(
|
|
||||||
updatesItem: UpdatesUiModel.Item,
|
|
||||||
selected: MutableList<UpdatesUiModel.Item>,
|
|
||||||
updates: List<UpdatesUiModel.Item>,
|
|
||||||
selectedPositions: Array<Int>,
|
|
||||||
onUpdateClicked: (UpdatesItem) -> Unit,
|
|
||||||
) {
|
|
||||||
val selectedIndex = updates.indexOf(updatesItem)
|
|
||||||
when {
|
|
||||||
selected.contains(updatesItem) -> {
|
|
||||||
val removedIndex = updates.indexOf(updatesItem)
|
|
||||||
selected.remove(updatesItem)
|
|
||||||
|
|
||||||
if (removedIndex == selectedPositions[0]) {
|
|
||||||
selectedPositions[0] = updates.indexOfFirst { selected.contains(it) }
|
|
||||||
} else if (removedIndex == selectedPositions[1]) {
|
|
||||||
selectedPositions[1] = updates.indexOfLast { selected.contains(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selected.isNotEmpty() -> {
|
|
||||||
if (selectedIndex < selectedPositions[0]) {
|
|
||||||
selectedPositions[0] = selectedIndex
|
|
||||||
} else if (selectedIndex > selectedPositions[1]) {
|
|
||||||
selectedPositions[1] = selectedIndex
|
|
||||||
}
|
|
||||||
selected.add(updatesItem)
|
|
||||||
}
|
|
||||||
else -> onUpdateClicked(updatesItem.item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,28 +1,19 @@
|
|||||||
package eu.kanade.tachiyomi.ui.recent.updates
|
package eu.kanade.tachiyomi.ui.recent.updates
|
||||||
|
|
||||||
import androidx.activity.OnBackPressedDispatcherOwner
|
import androidx.activity.OnBackPressedDispatcherOwner
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import eu.kanade.presentation.components.ChapterDownloadAction
|
import eu.kanade.presentation.components.ChapterDownloadAction
|
||||||
import eu.kanade.presentation.components.LoadingScreen
|
import eu.kanade.presentation.components.LoadingScreen
|
||||||
import eu.kanade.presentation.updates.UpdateScreen
|
import eu.kanade.presentation.updates.UpdateScreen
|
||||||
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.data.library.LibraryUpdateService
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import eu.kanade.tachiyomi.widget.materialdialogs.await
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,39 +27,27 @@ class UpdatesController :
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun ComposeContent() {
|
override fun ComposeContent() {
|
||||||
val state by presenter.state.collectAsState()
|
Crossfade(targetState = presenter.isLoading) { isLoading ->
|
||||||
when (state) {
|
if (isLoading) {
|
||||||
is UpdatesState.Loading -> LoadingScreen()
|
LoadingScreen()
|
||||||
is UpdatesState.Error -> Text(text = (state as UpdatesState.Error).error.message.orEmpty())
|
} else {
|
||||||
is UpdatesState.Success ->
|
|
||||||
UpdateScreen(
|
UpdateScreen(
|
||||||
state = (state as UpdatesState.Success),
|
presenter = presenter,
|
||||||
onClickCover = this::openManga,
|
onClickCover = { item ->
|
||||||
onClickUpdate = this::openChapter,
|
router.pushController(MangaController(item.update.mangaId))
|
||||||
onDownloadChapter = this::downloadChapters,
|
},
|
||||||
onUpdateLibrary = this::updateLibrary,
|
|
||||||
onBackClicked = this::onBackClicked,
|
onBackClicked = this::onBackClicked,
|
||||||
// For bottom action menu
|
onDownloadChapter = this::downloadChapters,
|
||||||
onMultiBookmarkClicked = { updatesItems, bookmark ->
|
|
||||||
presenter.bookmarkUpdates(updatesItems, bookmark)
|
|
||||||
},
|
|
||||||
onMultiMarkAsReadClicked = { updatesItems, read ->
|
|
||||||
presenter.markUpdatesRead(updatesItems, read)
|
|
||||||
},
|
|
||||||
onMultiDeleteClicked = this::deleteChaptersWithConfirmation,
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
LaunchedEffect(state) {
|
|
||||||
if (state !is UpdatesState.Loading) {
|
|
||||||
(activity as? MainActivity)?.ready = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
LaunchedEffect(presenter.selectionMode) {
|
||||||
|
val activity = (activity as? MainActivity) ?: return@LaunchedEffect
|
||||||
private fun updateLibrary() {
|
activity.showBottomNav(presenter.selectionMode.not())
|
||||||
activity?.let {
|
}
|
||||||
if (LibraryUpdateService.start(it)) {
|
LaunchedEffect(presenter.isLoading) {
|
||||||
it.toast(R.string.updating_library)
|
if (presenter.isLoading.not()) {
|
||||||
|
(activity as? MainActivity)?.ready = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,26 +84,7 @@ class UpdatesController :
|
|||||||
presenter.deleteChapters(items)
|
presenter.deleteChapters(items)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
presenter.toggleAllSelection(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteChaptersWithConfirmation(items: List<UpdatesItem>) {
|
|
||||||
if (items.isEmpty()) return
|
|
||||||
viewScope.launch {
|
|
||||||
val result = MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setMessage(R.string.confirm_delete_chapters)
|
|
||||||
.await(android.R.string.ok, android.R.string.cancel)
|
|
||||||
if (result == AlertDialog.BUTTON_POSITIVE) presenter.deleteChapters(items)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openChapter(item: UpdatesItem) {
|
|
||||||
val activity = activity ?: return
|
|
||||||
val intent = ReaderActivity.newIntent(activity, item.update.mangaId, item.update.chapterId)
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openManga(item: UpdatesItem) {
|
|
||||||
router.pushController(MangaController(item.update.mangaId))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.ui.recent.updates
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import eu.kanade.core.util.insertSeparators
|
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
|
||||||
@ -11,6 +13,8 @@ import eu.kanade.domain.chapter.model.toDbChapter
|
|||||||
import eu.kanade.domain.manga.interactor.GetManga
|
import eu.kanade.domain.manga.interactor.GetManga
|
||||||
import eu.kanade.domain.updates.interactor.GetUpdates
|
import eu.kanade.domain.updates.interactor.GetUpdates
|
||||||
import eu.kanade.domain.updates.model.UpdatesWithRelations
|
import eu.kanade.domain.updates.model.UpdatesWithRelations
|
||||||
|
import eu.kanade.presentation.updates.UpdatesState
|
||||||
|
import eu.kanade.presentation.updates.UpdatesStateImpl
|
||||||
import eu.kanade.presentation.updates.UpdatesUiModel
|
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.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
@ -20,23 +24,22 @@ 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.toDateKey
|
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.preference.asHotFlow
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.text.DateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class UpdatesPresenter(
|
class UpdatesPresenter(
|
||||||
|
private val state: UpdatesStateImpl = UpdatesState() as UpdatesStateImpl,
|
||||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||||
private val setReadStatus: SetReadStatus = Injekt.get(),
|
private val setReadStatus: SetReadStatus = Injekt.get(),
|
||||||
private val getUpdates: GetUpdates = Injekt.get(),
|
private val getUpdates: GetUpdates = Injekt.get(),
|
||||||
@ -44,29 +47,22 @@ class UpdatesPresenter(
|
|||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
private val downloadManager: DownloadManager = Injekt.get(),
|
private val downloadManager: DownloadManager = Injekt.get(),
|
||||||
private val getChapter: GetChapter = Injekt.get(),
|
private val getChapter: GetChapter = Injekt.get(),
|
||||||
private val preferences: PreferencesHelper = Injekt.get(),
|
preferences: PreferencesHelper = Injekt.get(),
|
||||||
) : BasePresenter<UpdatesController>() {
|
) : BasePresenter<UpdatesController>(), UpdatesState by state {
|
||||||
|
|
||||||
private val _state: MutableStateFlow<UpdatesState> = MutableStateFlow(UpdatesState.Loading)
|
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState()
|
||||||
val state: StateFlow<UpdatesState> = _state.asStateFlow()
|
|
||||||
|
|
||||||
/**
|
val isIncognitoMode: Boolean by preferences.incognitoMode().asState()
|
||||||
* Helper function to update the UI state only if it's currently in success state
|
|
||||||
*/
|
|
||||||
private fun updateSuccessState(func: (UpdatesState.Success) -> UpdatesState.Success) {
|
|
||||||
_state.update { if (it is UpdatesState.Success) func(it) else it }
|
|
||||||
}
|
|
||||||
|
|
||||||
private var incognitoMode = false
|
val relativeTime: Int by preferences.relativeTime().asState()
|
||||||
set(value) {
|
|
||||||
updateSuccessState { it.copy(isIncognitoMode = value) }
|
val dateFormat: DateFormat by mutableStateOf(preferences.dateFormat())
|
||||||
field = value
|
|
||||||
}
|
private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
|
||||||
private var downloadOnlyMode = false
|
val events: Flow<Event> = _events.receiveAsFlow()
|
||||||
set(value) {
|
|
||||||
updateSuccessState { it.copy(isDownloadedOnlyMode = value) }
|
// First and last selected index in list
|
||||||
field = value
|
private val selectedPositions: Array<Int> = arrayOf(-1, -1)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription to observe download status changes.
|
* Subscription to observe download status changes.
|
||||||
@ -85,38 +81,17 @@ class UpdatesPresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUpdates.subscribe(calendar)
|
getUpdates.subscribe(calendar)
|
||||||
.catch { exception ->
|
.catch {
|
||||||
_state.value = UpdatesState.Error(exception)
|
logcat(LogPriority.ERROR, it)
|
||||||
|
_events.send(Event.InternalError)
|
||||||
}
|
}
|
||||||
.collectLatest { updates ->
|
.collectLatest { updates ->
|
||||||
val uiModels = updates.toUpdateUiModels()
|
state.uiModels = updates.toUpdateUiModels()
|
||||||
_state.update { currentState ->
|
state.isLoading = false
|
||||||
when (currentState) {
|
|
||||||
is UpdatesState.Success -> currentState.copy(uiModels)
|
|
||||||
is UpdatesState.Loading, is UpdatesState.Error ->
|
|
||||||
UpdatesState.Success(
|
|
||||||
uiModels = uiModels,
|
|
||||||
isIncognitoMode = incognitoMode,
|
|
||||||
isDownloadedOnlyMode = downloadOnlyMode,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
observeDownloads()
|
observeDownloads()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences.incognitoMode()
|
|
||||||
.asHotFlow { incognito ->
|
|
||||||
incognitoMode = incognito
|
|
||||||
}
|
|
||||||
.launchIn(presenterScope)
|
|
||||||
|
|
||||||
preferences.downloadedOnly()
|
|
||||||
.asHotFlow { downloadedOnly ->
|
|
||||||
downloadOnlyMode = downloadedOnly
|
|
||||||
}
|
|
||||||
.launchIn(presenterScope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<UpdatesWithRelations>.toUpdateUiModels(): List<UpdatesUiModel> {
|
private fun List<UpdatesWithRelations>.toUpdateUiModels(): List<UpdatesUiModel> {
|
||||||
@ -182,24 +157,22 @@ class UpdatesPresenter(
|
|||||||
* @param download download object containing progress.
|
* @param download download object containing progress.
|
||||||
*/
|
*/
|
||||||
private fun updateDownloadState(download: Download) {
|
private fun updateDownloadState(download: Download) {
|
||||||
updateSuccessState { successState ->
|
val uiModels = state.uiModels
|
||||||
val modifiedIndex = successState.uiModels.indexOfFirst {
|
val modifiedIndex = uiModels.indexOfFirst {
|
||||||
it is UpdatesUiModel.Item && it.item.update.chapterId == download.chapter.id
|
it is UpdatesUiModel.Item && it.item.update.chapterId == download.chapter.id
|
||||||
}
|
}
|
||||||
if (modifiedIndex < 0) return@updateSuccessState successState
|
if (modifiedIndex < 0) return
|
||||||
|
|
||||||
val newUiModels = successState.uiModels.toMutableList().apply {
|
state.uiModels = uiModels.toMutableList().apply {
|
||||||
var uiModel = removeAt(modifiedIndex)
|
var uiModel = removeAt(modifiedIndex)
|
||||||
if (uiModel is UpdatesUiModel.Item) {
|
if (uiModel is UpdatesUiModel.Item) {
|
||||||
val item = uiModel.item.copy(
|
val item = uiModel.item.copy(
|
||||||
downloadStateProvider = { download.status },
|
downloadStateProvider = { download.status },
|
||||||
downloadProgressProvider = { download.progress },
|
downloadProgressProvider = { download.progress },
|
||||||
)
|
)
|
||||||
uiModel = UpdatesUiModel.Item(item)
|
uiModel = UpdatesUiModel.Item(item)
|
||||||
}
|
|
||||||
add(modifiedIndex, uiModel)
|
|
||||||
}
|
}
|
||||||
successState.copy(uiModels = newUiModels)
|
add(modifiedIndex, uiModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,42 +248,131 @@ class UpdatesPresenter(
|
|||||||
val chapters = updates.mapNotNull { getChapter.await(it.update.chapterId)?.toDbChapter() }
|
val chapters = updates.mapNotNull { getChapter.await(it.update.chapterId)?.toDbChapter() }
|
||||||
downloadManager.deleteChapters(chapters, manga, source).mapNotNull { it.id }
|
downloadManager.deleteChapters(chapters, manga, source).mapNotNull { it.id }
|
||||||
}
|
}
|
||||||
updateSuccessState { successState ->
|
|
||||||
val deletedUpdates = successState.uiModels.filter {
|
|
||||||
it is UpdatesUiModel.Item && deletedIds.contains(it.item.update.chapterId)
|
|
||||||
}
|
|
||||||
if (deletedUpdates.isEmpty()) return@updateSuccessState successState
|
|
||||||
|
|
||||||
// TODO: Don't do this fake status update
|
val uiModels = state.uiModels
|
||||||
val newUiModels = successState.uiModels.toMutableList().apply {
|
val deletedUpdates = uiModels.filter {
|
||||||
deletedUpdates.forEach { deletedUpdate ->
|
it is UpdatesUiModel.Item && deletedIds.contains(it.item.update.chapterId)
|
||||||
val modifiedIndex = indexOf(deletedUpdate)
|
}
|
||||||
var uiModel = removeAt(modifiedIndex)
|
if (deletedUpdates.isEmpty()) return@launchIO
|
||||||
if (uiModel is UpdatesUiModel.Item) {
|
|
||||||
val item = uiModel.item.copy(
|
// TODO: Don't do this fake status update
|
||||||
downloadStateProvider = { Download.State.NOT_DOWNLOADED },
|
state.uiModels = uiModels.toMutableList().apply {
|
||||||
downloadProgressProvider = { 0 },
|
deletedUpdates.forEach { deletedUpdate ->
|
||||||
)
|
val modifiedIndex = indexOf(deletedUpdate)
|
||||||
uiModel = UpdatesUiModel.Item(item)
|
var uiModel = removeAt(modifiedIndex)
|
||||||
}
|
if (uiModel is UpdatesUiModel.Item) {
|
||||||
add(modifiedIndex, uiModel)
|
val item = uiModel.item.copy(
|
||||||
|
downloadStateProvider = { Download.State.NOT_DOWNLOADED },
|
||||||
|
downloadProgressProvider = { 0 },
|
||||||
|
)
|
||||||
|
uiModel = UpdatesUiModel.Item(item)
|
||||||
}
|
}
|
||||||
|
add(modifiedIndex, uiModel)
|
||||||
}
|
}
|
||||||
successState.copy(uiModels = newUiModels)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sealed class UpdatesState {
|
fun toggleSelection(
|
||||||
object Loading : UpdatesState()
|
item: UpdatesItem,
|
||||||
data class Error(val error: Throwable) : UpdatesState()
|
selected: Boolean,
|
||||||
data class Success(
|
userSelected: Boolean = false,
|
||||||
val uiModels: List<UpdatesUiModel>,
|
fromLongPress: Boolean = false,
|
||||||
val isIncognitoMode: Boolean = false,
|
) {
|
||||||
val isDownloadedOnlyMode: Boolean = false,
|
val uiModels = state.uiModels
|
||||||
val showSwipeRefreshIndicator: Boolean = false,
|
val modifiedIndex = uiModels.indexOfFirst {
|
||||||
) : UpdatesState()
|
it is UpdatesUiModel.Item && it.item.update.chapterId == item.update.chapterId
|
||||||
|
}
|
||||||
|
if (modifiedIndex < 0) return
|
||||||
|
|
||||||
|
val oldItem = (uiModels[modifiedIndex] as? UpdatesUiModel.Item)?.item ?: return
|
||||||
|
if ((oldItem.selected && selected) || (!oldItem.selected && !selected)) return
|
||||||
|
|
||||||
|
state.uiModels = uiModels.toMutableList().apply {
|
||||||
|
val firstSelection = none { it is UpdatesUiModel.Item && it.item.selected }
|
||||||
|
var newItem = (removeAt(modifiedIndex) as? UpdatesUiModel.Item)?.item?.copy(selected = selected) ?: return@apply
|
||||||
|
add(modifiedIndex, UpdatesUiModel.Item(newItem))
|
||||||
|
|
||||||
|
if (selected && userSelected && fromLongPress) {
|
||||||
|
if (firstSelection) {
|
||||||
|
selectedPositions[0] = modifiedIndex
|
||||||
|
selectedPositions[1] = modifiedIndex
|
||||||
|
} else {
|
||||||
|
// Try to select the items in-between when possible
|
||||||
|
val range: IntRange
|
||||||
|
if (modifiedIndex < selectedPositions[0]) {
|
||||||
|
range = modifiedIndex + 1 until selectedPositions[0]
|
||||||
|
selectedPositions[0] = modifiedIndex
|
||||||
|
} else if (modifiedIndex > selectedPositions[1]) {
|
||||||
|
range = (selectedPositions[1] + 1) until modifiedIndex
|
||||||
|
selectedPositions[1] = modifiedIndex
|
||||||
|
} else {
|
||||||
|
// Just select itself
|
||||||
|
range = IntRange.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
range.forEach {
|
||||||
|
var uiModel = removeAt(it)
|
||||||
|
if (uiModel is UpdatesUiModel.Item) {
|
||||||
|
newItem = uiModel.item.copy(selected = true)
|
||||||
|
uiModel = UpdatesUiModel.Item(newItem)
|
||||||
|
}
|
||||||
|
add(it, uiModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (userSelected && !fromLongPress) {
|
||||||
|
if (!selected) {
|
||||||
|
if (modifiedIndex == selectedPositions[0]) {
|
||||||
|
selectedPositions[0] = indexOfFirst { it is UpdatesUiModel.Item && it.item.selected }
|
||||||
|
} else if (modifiedIndex == selectedPositions[1]) {
|
||||||
|
selectedPositions[1] = indexOfLast { it is UpdatesUiModel.Item && it.item.selected }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (modifiedIndex < selectedPositions[0]) {
|
||||||
|
selectedPositions[0] = modifiedIndex
|
||||||
|
} else if (modifiedIndex > selectedPositions[1]) {
|
||||||
|
selectedPositions[1] = modifiedIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleAllSelection(selected: Boolean) {
|
||||||
|
state.uiModels = state.uiModels.map {
|
||||||
|
when (it) {
|
||||||
|
is UpdatesUiModel.Header -> it
|
||||||
|
is UpdatesUiModel.Item -> {
|
||||||
|
val newItem = it.item.copy(selected = selected)
|
||||||
|
UpdatesUiModel.Item(newItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectedPositions[0] = -1
|
||||||
|
selectedPositions[1] = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invertSelection() {
|
||||||
|
state.uiModels = state.uiModels.map {
|
||||||
|
when (it) {
|
||||||
|
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[1] = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Dialog {
|
||||||
|
data class DeleteConfirmation(val toDelete: List<UpdatesItem>) : Dialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Event {
|
||||||
|
object InternalError : Event()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@ -318,4 +380,5 @@ data class UpdatesItem(
|
|||||||
val update: UpdatesWithRelations,
|
val update: UpdatesWithRelations,
|
||||||
val downloadStateProvider: () -> Download.State,
|
val downloadStateProvider: () -> Download.State,
|
||||||
val downloadProgressProvider: () -> Int,
|
val downloadProgressProvider: () -> Int,
|
||||||
|
val selected: Boolean = false,
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user