Show local section in shelf
This commit is contained in:
@@ -1,19 +1,44 @@
|
||||
package org.koitharu.kotatsu.shelf.domain
|
||||
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.db.entity.toManga
|
||||
import org.koitharu.kotatsu.core.db.entity.toMangaTags
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
|
||||
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import javax.inject.Inject
|
||||
|
||||
class ShelfRepository @Inject constructor(
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val db: MangaDatabase,
|
||||
) {
|
||||
|
||||
fun observeLocalManga(sortOrder: SortOrder): Flow<List<Manga>> {
|
||||
return flow {
|
||||
emit(null)
|
||||
emitAll(localMangaRepository.watchReadableDirs())
|
||||
}.mapLatest {
|
||||
localMangaRepository.getList(0, null, sortOrder)
|
||||
}
|
||||
}
|
||||
|
||||
fun observeFavourites(): Flow<Map<FavouriteCategory, List<Manga>>> {
|
||||
return db.favouriteCategoriesDao.observeAll()
|
||||
.flatMapLatest { categories ->
|
||||
@@ -26,6 +51,23 @@ class ShelfRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteLocalManga(ids: Set<Long>) {
|
||||
val list = localMangaRepository.getList(0, null, null)
|
||||
.filter { x -> x.id in ids }
|
||||
coroutineScope {
|
||||
list.map { manga ->
|
||||
async {
|
||||
val original = localMangaRepository.getRemoteManga(manga)
|
||||
if (localMangaRepository.delete(manga)) {
|
||||
runCatchingCancellable {
|
||||
historyRepository.deleteOrSwap(manga, original)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeCategoriesContent(
|
||||
categories: List<FavouriteCategoryEntity>,
|
||||
) = combine<Pair<FavouriteCategory, List<Manga>>, Map<FavouriteCategory, List<Manga>>>(
|
||||
|
||||
@@ -26,6 +26,8 @@ import org.koitharu.kotatsu.list.ui.ItemSizeResolver
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
||||
import org.koitharu.kotatsu.shelf.ui.adapter.ShelfAdapter
|
||||
import org.koitharu.kotatsu.shelf.ui.adapter.ShelfListEventListener
|
||||
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
|
||||
@@ -104,6 +106,7 @@ class ShelfFragment :
|
||||
is ShelfSectionModel.History -> HistoryActivity.newIntent(view.context)
|
||||
is ShelfSectionModel.Favourites -> FavouritesActivity.newIntent(view.context, section.category)
|
||||
is ShelfSectionModel.Updated -> UpdatesActivity.newIntent(view.context)
|
||||
is ShelfSectionModel.Local -> MangaListActivity.newIntent(view.context, MangaSource.LOCAL)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.view.MenuItem
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
|
||||
@@ -41,9 +42,10 @@ class ShelfSelectionCallback(
|
||||
mode: ActionMode,
|
||||
menu: Menu,
|
||||
): Boolean {
|
||||
val checkedIds = controller.peekCheckedIds()
|
||||
menu.findItem(R.id.action_remove).isVisible = checkedIds.none { (key, _) -> key is ShelfSectionModel.Updated }
|
||||
&& checkedIds.count { (_, v) -> v.isNotEmpty() } == 1
|
||||
val checkedIds = controller.peekCheckedIds().entries
|
||||
val singleKey = checkedIds.singleOrNull { (_, ids) -> ids.isNotEmpty() }?.key
|
||||
menu.findItem(R.id.action_remove)?.isVisible = singleKey != null && singleKey !is ShelfSectionModel.Updated
|
||||
menu.findItem(R.id.action_save)?.isVisible = singleKey !is ShelfSectionModel.Local
|
||||
return super.onPrepareActionMode(controller, mode, menu)
|
||||
}
|
||||
|
||||
@@ -77,6 +79,10 @@ class ShelfSelectionCallback(
|
||||
is ShelfSectionModel.Favourites -> viewModel.removeFromFavourites(group.category, ids)
|
||||
is ShelfSectionModel.History -> viewModel.removeFromHistory(ids)
|
||||
is ShelfSectionModel.Updated -> return false
|
||||
is ShelfSectionModel.Local -> {
|
||||
showDeletionConfirm(ids, mode)
|
||||
return true
|
||||
}
|
||||
}
|
||||
mode.finish()
|
||||
true
|
||||
@@ -114,4 +120,19 @@ class ShelfSelectionCallback(
|
||||
}
|
||||
return viewModel.getManga(snapshot.values.flattenTo(HashSet()))
|
||||
}
|
||||
|
||||
private fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode) {
|
||||
if (ids.isEmpty()) {
|
||||
return
|
||||
}
|
||||
MaterialAlertDialogBuilder(context ?: return)
|
||||
.setTitle(R.string.delete_manga)
|
||||
.setMessage(context.getString(R.string.text_delete_local_manga_batch))
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
viewModel.deleteLocal(ids)
|
||||
mode.finish()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
|
||||
@@ -24,8 +25,8 @@ import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
||||
import org.koitharu.kotatsu.list.ui.model.toGridModel
|
||||
import org.koitharu.kotatsu.list.ui.model.toUi
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.shelf.domain.ShelfRepository
|
||||
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
@@ -35,11 +36,10 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ShelfViewModel @Inject constructor(
|
||||
repository: ShelfRepository,
|
||||
private val repository: ShelfRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val favouritesRepository: FavouritesRepository,
|
||||
private val trackingRepository: TrackingRepository,
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val settings: AppSettings,
|
||||
) : BaseViewModel(), ListExtraProvider {
|
||||
|
||||
@@ -47,13 +47,15 @@ class ShelfViewModel @Inject constructor(
|
||||
|
||||
val content: LiveData<List<ListModel>> = combine(
|
||||
historyRepository.observeAllWithHistory(),
|
||||
repository.observeLocalManga(SortOrder.UPDATED),
|
||||
repository.observeFavourites(),
|
||||
trackingRepository.observeUpdatedManga(),
|
||||
) { history, favourites, updated ->
|
||||
mapList(history, favourites, updated)
|
||||
}.catch { e ->
|
||||
emit(listOf(e.toErrorState(canRetry = false)))
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
||||
) { history, local, favourites, updated ->
|
||||
mapList(history, favourites, updated, local)
|
||||
}.debounce(500)
|
||||
.catch { e ->
|
||||
emit(listOf(e.toErrorState(canRetry = false)))
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
||||
|
||||
override suspend fun getCounter(mangaId: Long): Int {
|
||||
return trackingRepository.getNewChaptersCount(mangaId)
|
||||
@@ -87,6 +89,13 @@ class ShelfViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteLocal(ids: Set<Long>) {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
repository.deleteLocalManga(ids)
|
||||
onActionDone.postCall(ReversibleAction(R.string.removal_completed, null))
|
||||
}
|
||||
}
|
||||
|
||||
fun clearHistory(minDate: Long) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
val stringRes = if (minDate <= 0) {
|
||||
@@ -123,11 +132,15 @@ class ShelfViewModel @Inject constructor(
|
||||
history: List<MangaWithHistory>,
|
||||
favourites: Map<FavouriteCategory, List<Manga>>,
|
||||
updated: Map<Manga, Int>,
|
||||
local: List<Manga>,
|
||||
): List<ListModel> {
|
||||
val result = ArrayList<ListModel>(favourites.keys.size + 2)
|
||||
if (history.isNotEmpty()) {
|
||||
mapHistory(result, history)
|
||||
}
|
||||
if (local.isNotEmpty()) {
|
||||
mapLocal(result, local)
|
||||
}
|
||||
if (updated.isNotEmpty()) {
|
||||
mapUpdated(result, updated)
|
||||
}
|
||||
@@ -174,6 +187,16 @@ class ShelfViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun mapLocal(
|
||||
destination: MutableList<in ShelfSectionModel.Local>,
|
||||
local: List<Manga>,
|
||||
) {
|
||||
destination += ShelfSectionModel.Local(
|
||||
items = local.toUi(ListMode.GRID, this),
|
||||
showAllButtonText = R.string.show_all,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun mapFavourites(
|
||||
destination: MutableList<in ShelfSectionModel.Favourites>,
|
||||
favourites: Map<FavouriteCategory, List<Manga>>,
|
||||
|
||||
@@ -111,4 +111,34 @@ sealed interface ShelfSectionModel : ListModel {
|
||||
|
||||
override fun toString(): String = key
|
||||
}
|
||||
|
||||
class Local(
|
||||
override val items: List<MangaItemModel>,
|
||||
override val showAllButtonText: Int,
|
||||
) : ShelfSectionModel {
|
||||
|
||||
override val key = "local"
|
||||
|
||||
override fun getTitle(resources: Resources) = resources.getString(R.string.local_storage)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Local
|
||||
|
||||
if (items != other.items) return false
|
||||
if (showAllButtonText != other.showAllButtonText) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = items.hashCode()
|
||||
result = 31 * result + showAllButtonText
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String = key
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user