Replace LiveData with StateFlow

This commit is contained in:
Koitharu
2023-05-27 12:25:49 +03:00
parent 47f346b42c
commit 5a0c54e00f
147 changed files with 1047 additions and 1039 deletions

View File

@@ -1,9 +1,5 @@
package org.koitharu.kotatsu.shelf.domain
import dagger.Reusable
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.combine
@@ -18,19 +14,18 @@ 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.favourites.data.toMangaList
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.local.data.LocalManga
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.shelf.domain.model.ShelfContent
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import javax.inject.Inject
@Reusable
class ShelfRepository @Inject constructor(
class ShelfContentObserveUseCase @Inject constructor(
private val localMangaRepository: LocalMangaRepository,
private val historyRepository: HistoryRepository,
private val trackingRepository: TrackingRepository,
@@ -39,7 +34,7 @@ class ShelfRepository @Inject constructor(
@LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
) {
fun observeShelfContent(): Flow<ShelfContent> = combine(
operator fun invoke(): Flow<ShelfContent> = combine(
historyRepository.observeAllWithHistory(),
observeLocalManga(SortOrder.UPDATED),
observeFavourites(),
@@ -69,23 +64,6 @@ 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>>>(

View File

@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.shelf.domain
package org.koitharu.kotatsu.shelf.domain.model
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.history.domain.MangaWithHistory
import org.koitharu.kotatsu.history.domain.model.MangaWithHistory
import org.koitharu.kotatsu.parsers.model.Manga
class ShelfContent(

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.shelf.domain
package org.koitharu.kotatsu.shelf.domain.model
enum class ShelfSection {

View File

@@ -21,6 +21,8 @@ import org.koitharu.kotatsu.core.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.FragmentShelfBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver
@@ -84,9 +86,9 @@ class ShelfFragment :
addMenuProvider(ShelfMenuProvider(binding.root.context, childFragmentManager, viewModel))
viewModel.content.observe(viewLifecycleOwner, ::onListChanged)
viewModel.onError.observe(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.onActionDone.observe(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView))
viewModel.onDownloadStarted.observe(viewLifecycleOwner, DownloadStartedObserver(binding.recyclerView))
viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this))
viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView))
viewModel.onDownloadStarted.observeEvent(viewLifecycleOwner, DownloadStartedObserver(binding.recyclerView))
}
override fun onSaveInstanceState(outState: Bundle) {

View File

@@ -1,12 +1,15 @@
package org.koitharu.kotatsu.shelf.ui
import androidx.collection.ArraySet
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.os.NetworkState
@@ -15,13 +18,13 @@ import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.util.SingleLiveEvent
import org.koitharu.kotatsu.core.util.asFlowLiveData
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.MangaWithHistory
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.history.domain.model.MangaWithHistory
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.model.EmptyHint
import org.koitharu.kotatsu.list.ui.model.EmptyState
@@ -30,11 +33,12 @@ 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.DeleteLocalMangaUseCase
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.shelf.domain.ShelfContent
import org.koitharu.kotatsu.shelf.domain.ShelfRepository
import org.koitharu.kotatsu.shelf.domain.ShelfSection
import org.koitharu.kotatsu.shelf.domain.ShelfContentObserveUseCase
import org.koitharu.kotatsu.shelf.domain.model.ShelfContent
import org.koitharu.kotatsu.shelf.domain.model.ShelfSection
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.sync.domain.SyncController
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
@@ -42,30 +46,31 @@ import javax.inject.Inject
@HiltViewModel
class ShelfViewModel @Inject constructor(
private val repository: ShelfRepository,
private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository,
private val trackingRepository: TrackingRepository,
private val settings: AppSettings,
private val downloadScheduler: DownloadWorker.Scheduler,
private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,
shelfContentObserveUseCase: ShelfContentObserveUseCase,
syncController: SyncController,
networkState: NetworkState,
) : BaseViewModel(), ListExtraProvider {
val onActionDone = SingleLiveEvent<ReversibleAction>()
val onDownloadStarted = SingleLiveEvent<Unit>()
val onActionDone = MutableEventFlow<ReversibleAction>()
val onDownloadStarted = MutableEventFlow<Unit>()
val content: LiveData<List<ListModel>> = combine(
val content: StateFlow<List<ListModel>> = combine(
settings.observeAsFlow(AppSettings.KEY_SHELF_SECTIONS) { shelfSections },
settings.observeAsFlow(AppSettings.KEY_TRACKER_ENABLED) { isTrackerEnabled },
settings.observeAsFlow(AppSettings.KEY_SUGGESTIONS) { isSuggestionsEnabled },
networkState,
repository.observeShelfContent(),
shelfContentObserveUseCase(),
) { sections, isTrackerEnabled, isSuggestionsEnabled, isConnected, content ->
mapList(content, isTrackerEnabled, isSuggestionsEnabled, sections, isConnected)
}.catch { e ->
emit(listOf(e.toErrorState(canRetry = false)))
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
init {
launchJob(Dispatchers.Default) {
@@ -95,7 +100,7 @@ class ShelfViewModel @Inject constructor(
}
launchJob(Dispatchers.Default) {
val handle = favouritesRepository.removeFromCategory(category.id, ids)
onActionDone.emitCall(ReversibleAction(R.string.removed_from_favourites, handle))
onActionDone.call(ReversibleAction(R.string.removed_from_favourites, handle))
}
}
@@ -105,14 +110,14 @@ class ShelfViewModel @Inject constructor(
}
launchJob(Dispatchers.Default) {
val handle = historyRepository.delete(ids)
onActionDone.emitCall(ReversibleAction(R.string.removed_from_history, handle))
onActionDone.call(ReversibleAction(R.string.removed_from_history, handle))
}
}
fun deleteLocal(ids: Set<Long>) {
launchLoadingJob(Dispatchers.Default) {
repository.deleteLocalManga(ids)
onActionDone.emitCall(ReversibleAction(R.string.removal_completed, null))
deleteLocalMangaUseCase(ids)
onActionDone.call(ReversibleAction(R.string.removal_completed, null))
}
}
@@ -125,12 +130,12 @@ class ShelfViewModel @Inject constructor(
historyRepository.deleteAfter(minDate)
R.string.removed_from_history
}
onActionDone.emitCall(ReversibleAction(stringRes, null))
onActionDone.call(ReversibleAction(stringRes, null))
}
}
fun getManga(ids: Set<Long>): Set<Manga> {
val snapshot = content.value ?: return emptySet()
val snapshot = content.value
val result = ArraySet<Manga>(ids.size)
for (section in snapshot) {
if (section !is ShelfSectionModel) {
@@ -151,7 +156,7 @@ class ShelfViewModel @Inject constructor(
fun download(items: Set<Manga>) {
launchJob(Dispatchers.Default) {
downloadScheduler.schedule(items)
onDownloadStarted.emitCall(Unit)
onDownloadStarted.call(Unit)
}
}

View File

@@ -13,6 +13,7 @@ import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.databinding.ActivityShelfSettingsBinding
import com.google.android.material.R as materialR
@@ -40,8 +41,6 @@ class ShelfSettingsActivity :
it.attachToRecyclerView(this)
}
}
viewModel.content.observe(this) { settingsAdapter.items = it }
}

View File

@@ -10,7 +10,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.setChecked
import org.koitharu.kotatsu.databinding.ItemCategoryCheckableMultipleBinding
import org.koitharu.kotatsu.databinding.ItemShelfSectionDraggableBinding
import org.koitharu.kotatsu.shelf.domain.ShelfSection
import org.koitharu.kotatsu.shelf.domain.model.ShelfSection
@SuppressLint("ClickableViewAccessibility")
fun shelfSectionAD(

View File

@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.shelf.ui.config
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.shelf.domain.ShelfSection
import org.koitharu.kotatsu.shelf.domain.model.ShelfSection
sealed interface ShelfSettingsItemModel : ListModel {
@@ -19,9 +19,7 @@ sealed interface ShelfSettingsItemModel : ListModel {
other as Section
if (section != other.section) return false
if (isChecked != other.isChecked) return false
return true
return isChecked == other.isChecked
}
override fun hashCode(): Int {
@@ -45,9 +43,7 @@ sealed interface ShelfSettingsItemModel : ListModel {
if (id != other.id) return false
if (title != other.title) return false
if (isChecked != other.isChecked) return false
return true
return isChecked == other.isChecked
}
override fun hashCode(): Int {

View File

@@ -4,15 +4,17 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.util.asFlowLiveData
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.parsers.util.move
import org.koitharu.kotatsu.shelf.domain.ShelfSection
import org.koitharu.kotatsu.shelf.domain.model.ShelfSection
import javax.inject.Inject
@HiltViewModel
@@ -27,7 +29,7 @@ class ShelfSettingsViewModel @Inject constructor(
favouritesRepository.observeCategories(),
) { sections, isTrackerEnabled, categories ->
buildList(sections, isTrackerEnabled, categories)
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
private var updateJob: Job? = null
@@ -57,7 +59,7 @@ class ShelfSettingsViewModel @Inject constructor(
}
fun reorderSections(oldPos: Int, newPos: Int): Boolean {
val snapshot = content.value?.toMutableList() ?: return false
val snapshot = content.value.toMutableList()
snapshot.move(oldPos, newPos)
settings.shelfSections = snapshot.sections()
return true