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,4 +1,4 @@
package org.koitharu.kotatsu.local.domain
package org.koitharu.kotatsu.local.data
import android.net.Uri
import androidx.core.net.toFile
@@ -15,13 +15,10 @@ import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.util.CompositeMutex
import org.koitharu.kotatsu.core.util.ext.deleteAwait
import org.koitharu.kotatsu.local.data.LocalManga
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.data.LocalStorageManager
import org.koitharu.kotatsu.local.data.TempFileFilter
import org.koitharu.kotatsu.local.data.input.LocalMangaInput
import org.koitharu.kotatsu.local.data.output.LocalMangaOutput
import org.koitharu.kotatsu.local.data.output.LocalMangaUtil
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaPage

View File

@@ -16,10 +16,10 @@ import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
import org.koitharu.kotatsu.core.util.ext.resolveName
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
import org.koitharu.kotatsu.local.data.CbzFilter
import org.koitharu.kotatsu.local.data.LocalManga
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.data.LocalStorageManager
import org.koitharu.kotatsu.local.data.input.LocalMangaInput
import org.koitharu.kotatsu.local.domain.model.LocalManga
import java.io.File
import java.io.IOException
import javax.inject.Inject

View File

@@ -9,9 +9,9 @@ import org.koitharu.kotatsu.core.util.ext.longHashCode
import org.koitharu.kotatsu.core.util.ext.toListSorted
import org.koitharu.kotatsu.local.data.CbzFilter
import org.koitharu.kotatsu.local.data.ImageFileFilter
import org.koitharu.kotatsu.local.data.LocalManga
import org.koitharu.kotatsu.local.data.MangaIndex
import org.koitharu.kotatsu.local.data.output.LocalMangaOutput
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaPage

View File

@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.local.data.input
import android.net.Uri
import androidx.core.net.toFile
import org.koitharu.kotatsu.local.data.LocalManga
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaPage

View File

@@ -10,9 +10,9 @@ import kotlinx.coroutines.runInterruptible
import org.koitharu.kotatsu.core.util.ext.longHashCode
import org.koitharu.kotatsu.core.util.ext.readText
import org.koitharu.kotatsu.core.util.ext.toListSorted
import org.koitharu.kotatsu.local.data.LocalManga
import org.koitharu.kotatsu.local.data.MangaIndex
import org.koitharu.kotatsu.local.data.output.LocalMangaOutput
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaPage

View File

@@ -0,0 +1,42 @@
package org.koitharu.kotatsu.local.domain
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
import java.io.IOException
import javax.inject.Inject
class DeleteLocalMangaUseCase @Inject constructor(
private val localMangaRepository: LocalMangaRepository,
private val historyRepository: HistoryRepository,
) {
suspend operator fun invoke(manga: Manga) {
val victim = if (manga.isLocal) manga else localMangaRepository.findSavedManga(manga)?.manga
checkNotNull(victim) { "Cannot find saved manga for ${manga.title}" }
val original = if (manga.isLocal) localMangaRepository.getRemoteManga(manga) else manga
localMangaRepository.delete(victim) || throw IOException("Unable to delete file")
runCatchingCancellable {
historyRepository.deleteOrSwap(victim, original)
}.onFailure {
it.printStackTraceDebug()
}
}
suspend operator fun invoke(ids: Set<Long>) {
val list = localMangaRepository.getList(0, null, null)
var removed = 0
for (manga in list) {
if (manga.id in ids) {
invoke(manga)
removed++
}
}
check(removed == ids.size) {
"Removed $removed files but ${ids.size} requested"
}
}
}

View File

@@ -1,66 +0,0 @@
package org.koitharu.kotatsu.local.domain
import dagger.Reusable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.core.model.DoubleManga
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaIntent
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import javax.inject.Inject
@Reusable
class DoubleMangaLoader @Inject constructor(
private val mangaDataRepository: MangaDataRepository,
private val localMangaRepository: LocalMangaRepository,
private val mangaRepositoryFactory: MangaRepository.Factory,
) {
suspend fun load(manga: Manga): DoubleManga = coroutineScope {
val remoteDeferred = async(Dispatchers.Default) { loadRemote(manga) }
val localDeferred = async(Dispatchers.Default) { loadLocal(manga) }
DoubleManga(
remoteManga = remoteDeferred.await(),
localManga = localDeferred.await(),
)
}
suspend fun load(mangaId: Long): DoubleManga {
val manga = mangaDataRepository.findMangaById(mangaId) ?: throwNFE()
return load(manga)
}
suspend fun load(intent: MangaIntent): DoubleManga {
val manga = mangaDataRepository.resolveIntent(intent) ?: throwNFE()
return load(manga)
}
private suspend fun loadLocal(manga: Manga): Result<Manga>? {
return runCatchingCancellable {
if (manga.isLocal) {
localMangaRepository.getDetails(manga)
} else {
localMangaRepository.findSavedManga(manga)?.manga
} ?: return null
}
}
private suspend fun loadRemote(manga: Manga): Result<Manga>? {
return runCatchingCancellable {
val seed = if (manga.isLocal) {
localMangaRepository.getRemoteManga(manga)
} else {
manga
} ?: return null
val repository = mangaRepositoryFactory.create(seed.source)
repository.getDetails(seed)
}
}
private fun throwNFE(): Nothing = throw NotFoundException("Cannot find manga", "")
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.local.data
package org.koitharu.kotatsu.local.domain.model
import androidx.core.net.toFile
import androidx.core.net.toUri
@@ -38,9 +38,7 @@ class LocalManga(
other as LocalManga
if (manga != other.manga) return false
if (file != other.file) return false
return true
return file == other.file
}
override fun hashCode(): Int {

View File

@@ -15,9 +15,9 @@ import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat
import org.koitharu.kotatsu.local.data.LocalManga
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 javax.inject.Inject

View File

@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.util.ShareHelper
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.parsers.model.SortOrder
@@ -26,7 +27,7 @@ class LocalListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickListener
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
addMenuProvider(LocalListMenuProvider(this::onEmptyActionClick))
viewModel.onMangaRemoved.observe(viewLifecycleOwner) { onItemRemoved() }
viewModel.onMangaRemoved.observeEvent(viewLifecycleOwner) { onItemRemoved() }
}
override fun onEmptyActionClick() {

View File

@@ -1,7 +1,5 @@
package org.koitharu.kotatsu.local.ui
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asFlow
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CancellationException
@@ -10,18 +8,20 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
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.history.domain.HistoryRepository
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.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
@@ -29,15 +29,14 @@ import org.koitharu.kotatsu.list.ui.model.ListHeader2
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.local.data.LocalManga
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.DeleteLocalMangaUseCase
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import java.io.IOException
import java.util.LinkedList
import javax.inject.Inject
@@ -49,11 +48,12 @@ class LocalListViewModel @Inject constructor(
private val settings: AppSettings,
private val tagHighlighter: MangaTagHighlighter,
@LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,
downloadScheduler: DownloadWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler), ListExtraProvider {
val onMangaRemoved = SingleLiveEvent<Unit>()
val sortOrder = MutableLiveData(settings.localListOrder)
val onMangaRemoved = MutableEventFlow<Unit>()
val sortOrder = MutableStateFlow(settings.localListOrder)
private val listError = MutableStateFlow<Throwable?>(null)
private val mangaList = MutableStateFlow<List<Manga>?>(null)
private val selectedTags = MutableStateFlow<Set<MangaTag>>(emptySet())
@@ -61,8 +61,8 @@ class LocalListViewModel @Inject constructor(
override val content = combine(
mangaList,
listModeFlow,
sortOrder.asFlow(),
listMode,
sortOrder,
selectedTags,
listError,
) { list, mode, order, tags, error ->
@@ -83,7 +83,7 @@ class LocalListViewModel @Inject constructor(
list.toUi(this, mode, this@LocalListViewModel, tagHighlighter)
}
}
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
init {
onRefresh()
@@ -120,18 +120,8 @@ class LocalListViewModel @Inject constructor(
fun delete(ids: Set<Long>) {
launchLoadingJob(Dispatchers.Default) {
val itemsToRemove = checkNotNull(mangaList.value).filter { it.id in ids }
for (manga in itemsToRemove) {
val original = repository.getRemoteManga(manga)
repository.delete(manga) || throw IOException("Unable to delete file")
runCatchingCancellable {
historyRepository.deleteOrSwap(manga, original)
}
mangaList.update { list ->
list?.filterNot { it.id == manga.id }
}
}
onMangaRemoved.emitCall(Unit)
deleteLocalMangaUseCase(ids)
onMangaRemoved.call(Unit)
}
}

View File

@@ -11,7 +11,7 @@ import androidx.work.WorkManager
import androidx.work.WorkerParameters
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import java.util.concurrent.TimeUnit
@HiltWorker