Show local section in shelf

pull/242/head
Koitharu 4 years ago
parent 7b36c64b34
commit 9b54ed6bc7
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -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
}
}

Loading…
Cancel
Save