Feat: allow long click to favorite on GlobalSearch
This commit is contained in:
parent
c30a8873a2
commit
3bc673ae17
4 changed files with 156 additions and 4 deletions
|
|
@ -62,7 +62,7 @@ fun GlobalSearchScreen(
|
|||
onToggleResults = onToggleResults,
|
||||
scrollBehavior = scrollBehavior,
|
||||
// KMK -->
|
||||
toggleBulkSelectionMode = screenModel::toggleSelectionMode
|
||||
toggleBulkSelectionMode = screenModel::toggleSelectionMode,
|
||||
// KMK <--
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@ open class BrowseSourceScreenModel(
|
|||
false -> Instant.now().toEpochMilli()
|
||||
},
|
||||
)
|
||||
|
||||
// TODO: also allow deleting chapters when remove favorite (just like in [MangaScreenModel])
|
||||
if (!new.favorite) {
|
||||
new = new.removeCovers(coverCache)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -7,17 +7,23 @@ import androidx.compose.runtime.collectAsState
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.browse.GlobalSearchScreen
|
||||
import eu.kanade.presentation.browse.components.RemoveMangaDialog
|
||||
import eu.kanade.presentation.category.components.ChangeCategoryDialog
|
||||
import eu.kanade.presentation.manga.AllowDuplicateDialog
|
||||
import eu.kanade.presentation.manga.DuplicateMangaDialog
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
|
||||
class GlobalSearchScreen(
|
||||
|
|
@ -41,6 +47,9 @@ class GlobalSearchScreen(
|
|||
}
|
||||
|
||||
// KMK -->
|
||||
val scope = rememberCoroutineScope()
|
||||
val haptic = LocalHapticFeedback.current
|
||||
|
||||
BackHandler(enabled = state.selectionMode) {
|
||||
when {
|
||||
state.selectionMode -> screenModel.toggleSelectionMode()
|
||||
|
|
@ -89,8 +98,28 @@ class GlobalSearchScreen(
|
|||
// KMK <--
|
||||
navigator.push(MangaScreen(it.id, true))
|
||||
},
|
||||
onLongClickItem = {
|
||||
navigator.push(MangaScreen(it.id, true))
|
||||
onLongClickItem = { manga ->
|
||||
// KMK -->
|
||||
if (state.selectionMode)
|
||||
// KMK <--
|
||||
navigator.push(MangaScreen(manga.id, true))
|
||||
// KMK -->
|
||||
else
|
||||
scope.launchIO {
|
||||
val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
|
||||
when {
|
||||
manga.favorite -> screenModel.setDialog(SearchScreenModel.Dialog.RemoveManga(manga))
|
||||
duplicateManga != null -> screenModel.setDialog(
|
||||
SearchScreenModel.Dialog.AddDuplicateManga(
|
||||
manga,
|
||||
duplicateManga,
|
||||
),
|
||||
)
|
||||
else -> screenModel.addFavorite(manga)
|
||||
}
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
// KMK <--
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -98,6 +127,33 @@ class GlobalSearchScreen(
|
|||
// KMK -->
|
||||
val onDismissRequest = { screenModel.setDialog(null) }
|
||||
when (val dialog = state.dialog) {
|
||||
is SearchScreenModel.Dialog.AddDuplicateManga -> {
|
||||
DuplicateMangaDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
||||
onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) },
|
||||
)
|
||||
}
|
||||
is SearchScreenModel.Dialog.RemoveManga -> {
|
||||
RemoveMangaDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirm = {
|
||||
screenModel.changeMangaFavorite(dialog.manga)
|
||||
},
|
||||
mangaToRemove = dialog.manga,
|
||||
)
|
||||
}
|
||||
is SearchScreenModel.Dialog.ChangeMangaCategory -> {
|
||||
ChangeCategoryDialog(
|
||||
initialSelection = dialog.initialSelection,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onEditCategories = { navigator.push(CategoryScreen()) },
|
||||
onConfirm = { include, _ ->
|
||||
screenModel.changeMangaFavorite(dialog.manga)
|
||||
screenModel.moveMangaToCategories(dialog.manga, include)
|
||||
},
|
||||
)
|
||||
}
|
||||
is SearchScreenModel.Dialog.ChangeMangasCategory -> {
|
||||
ChangeCategoryDialog(
|
||||
initialSelection = dialog.initialSelection,
|
||||
|
|
|
|||
|
|
@ -11,9 +11,12 @@ import cafe.adriel.voyager.core.model.screenModelScope
|
|||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.toDomainManga
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.domain.track.interactor.AddTracks
|
||||
import eu.kanade.presentation.util.ioCoroutineScope
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.PersistentMap
|
||||
|
|
@ -35,19 +38,23 @@ import kotlinx.coroutines.isActive
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import tachiyomi.core.common.preference.CheckboxState
|
||||
import tachiyomi.core.common.preference.mapAsCheckboxState
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.interactor.SetMangaCategories
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.toMangaUpdate
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
abstract class SearchScreenModel(
|
||||
|
|
@ -63,6 +70,9 @@ abstract class SearchScreenModel(
|
|||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val coverCache: CoverCache = Injekt.get(),
|
||||
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
||||
private val addTracks: AddTracks = Injekt.get(),
|
||||
// KMK <--
|
||||
) : StateScreenModel<SearchScreenModel.State>(initialState) {
|
||||
|
||||
|
|
@ -390,11 +400,97 @@ abstract class SearchScreenModel(
|
|||
.orEmpty()
|
||||
}
|
||||
|
||||
suspend fun getDuplicateLibraryManga(manga: Manga): Manga? {
|
||||
return getDuplicateLibraryManga.await(manga).getOrNull(0)
|
||||
}
|
||||
|
||||
private fun moveMangaToCategories(manga: Manga, vararg categories: Category) {
|
||||
moveMangaToCategories(manga, categories.filter { it.id != 0L }.map { it.id })
|
||||
}
|
||||
|
||||
fun moveMangaToCategories(manga: Manga, categoryIds: List<Long>) {
|
||||
screenModelScope.launchIO {
|
||||
setMangaCategories.await(
|
||||
mangaId = manga.id,
|
||||
categoryIds = categoryIds.toList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or removes a manga from the library.
|
||||
*
|
||||
* @param manga the manga to update.
|
||||
*/
|
||||
fun changeMangaFavorite(manga: Manga) {
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
|
||||
screenModelScope.launch {
|
||||
var new = manga.copy(
|
||||
favorite = !manga.favorite,
|
||||
dateAdded = when (manga.favorite) {
|
||||
true -> 0
|
||||
false -> Instant.now().toEpochMilli()
|
||||
},
|
||||
)
|
||||
// TODO: also allow deleting chapters when remove favorite (just like in [MangaScreenModel])
|
||||
if (!new.favorite) {
|
||||
new = new.removeCovers(coverCache)
|
||||
} else {
|
||||
setMangaDefaultChapterFlags.await(manga)
|
||||
addTracks.bindEnhancedTrackers(manga, source)
|
||||
}
|
||||
|
||||
updateManga.await(new.toMangaUpdate())
|
||||
}
|
||||
}
|
||||
|
||||
fun addFavorite(manga: Manga) {
|
||||
screenModelScope.launch {
|
||||
val categories = getCategories()
|
||||
val defaultCategoryId = libraryPreferences.defaultCategory().get()
|
||||
val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
|
||||
|
||||
when {
|
||||
// Default category set
|
||||
defaultCategory != null -> {
|
||||
moveMangaToCategories(manga, defaultCategory)
|
||||
|
||||
changeMangaFavorite(manga)
|
||||
}
|
||||
|
||||
// Automatic 'Default' or no categories
|
||||
defaultCategoryId == 0 || categories.isEmpty() -> {
|
||||
moveMangaToCategories(manga)
|
||||
|
||||
changeMangaFavorite(manga)
|
||||
}
|
||||
|
||||
// Choose a category
|
||||
else -> {
|
||||
val preselectedIds = getCategories.await(manga.id).map { it.id }
|
||||
setDialog(
|
||||
Dialog.ChangeMangaCategory(
|
||||
manga,
|
||||
categories.mapAsCheckboxState { it.id in preselectedIds }.toImmutableList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setDialog(dialog: Dialog?) {
|
||||
mutableState.update { it.copy(dialog = dialog) }
|
||||
}
|
||||
|
||||
sealed interface Dialog {
|
||||
data class RemoveManga(val manga: Manga) : Dialog
|
||||
data class AddDuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog
|
||||
data class ChangeMangaCategory(
|
||||
val manga: Manga,
|
||||
val initialSelection: ImmutableList<CheckboxState.State<Category>>,
|
||||
) : Dialog
|
||||
data class ChangeMangasCategory(
|
||||
val mangas: List<Manga>,
|
||||
val initialSelection: ImmutableList<CheckboxState<Category>>,
|
||||
|
|
|
|||
Loading…
Reference in a new issue