MVVM for SourceRepository

This commit is contained in:
Syer10
2022-11-04 20:39:29 -04:00
parent 0bb8ab4fcd
commit 4e692d6007
13 changed files with 349 additions and 49 deletions

View File

@@ -0,0 +1,33 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.interactor
import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.domain.source.service.SourceRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class GetFilterList @Inject constructor(private val sourceRepository: SourceRepository) {
suspend fun await(source: Source, reset: Boolean) = asFlow(source.id, reset)
.catch { log.warn(it) { "Failed to get filter list for ${source.displayName} with reset = $reset" } }
.singleOrNull()
suspend fun await(sourceId: Long, reset: Boolean) = asFlow(sourceId, reset)
.catch { log.warn(it) { "Failed to get filter list for $sourceId with reset = $reset" } }
.singleOrNull()
fun asFlow(source: Source, reset: Boolean) = sourceRepository.getFilterList(source.id, reset)
fun asFlow(sourceId: Long, reset: Boolean) = sourceRepository.getFilterList(sourceId, reset)
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,33 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.interactor
import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.domain.source.service.SourceRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class GetLatestManga @Inject constructor(private val sourceRepository: SourceRepository) {
suspend fun await(source: Source, page: Int) = asFlow(source.id, page)
.catch { log.warn(it) { "Failed to get latest manga from ${source.displayName} on page $page" } }
.singleOrNull()
suspend fun await(sourceId: Long, page: Int) = asFlow(sourceId, page)
.catch { log.warn(it) { "Failed to get latest manga from $sourceId on page $page" } }
.singleOrNull()
fun asFlow(source: Source, page: Int) = sourceRepository.getLatestManga(source.id, page)
fun asFlow(sourceId: Long, page: Int) = sourceRepository.getLatestManga(sourceId, page)
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,33 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.interactor
import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.domain.source.service.SourceRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class GetPopularManga @Inject constructor(private val sourceRepository: SourceRepository) {
suspend fun await(source: Source, page: Int) = asFlow(source.id, page)
.catch { log.warn(it) { "Failed to get popular manga from ${source.displayName} on page $page" } }
.singleOrNull()
suspend fun await(sourceId: Long, page: Int) = asFlow(sourceId, page)
.catch { log.warn(it) { "Failed to get popular manga from $sourceId on page $page" } }
.singleOrNull()
fun asFlow(source: Source, page: Int) = sourceRepository.getPopularManga(source.id, page)
fun asFlow(sourceId: Long, page: Int) = sourceRepository.getPopularManga(sourceId, page)
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,41 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.interactor
import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.domain.source.service.SourceRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class GetSearchManga @Inject constructor(private val sourceRepository: SourceRepository) {
suspend fun await(source: Source, searchTerm: String?, page: Int) = asFlow(source.id, searchTerm, page)
.catch { log.warn(it) { "Failed to get search results from ${source.displayName} on page $page with query '$searchTerm'" } }
.singleOrNull()
suspend fun await(sourceId: Long, searchTerm: String?, page: Int) = asFlow(sourceId, searchTerm, page)
.catch { log.warn(it) { "Failed to get search results from $sourceId on page $page with query '$searchTerm'" } }
.singleOrNull()
fun asFlow(source: Source, searchTerm: String?, page: Int) = sourceRepository.getSearchResults(
source.id,
searchTerm?.ifBlank { null },
page
)
fun asFlow(sourceId: Long, searchTerm: String?, page: Int) = sourceRepository.getSearchResults(
sourceId,
searchTerm?.ifBlank { null },
page
)
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,26 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.interactor
import ca.gosyer.jui.domain.source.service.SourceRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class GetSourceList @Inject constructor(private val sourceRepository: SourceRepository) {
suspend fun await() = asFlow()
.catch { log.warn(it) { "Failed to get source list" } }
.singleOrNull()
fun asFlow() = sourceRepository.getSourceList()
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,33 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.interactor
import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.domain.source.service.SourceRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class GetSourceSettings @Inject constructor(private val sourceRepository: SourceRepository) {
suspend fun await(source: Source) = asFlow(source.id)
.catch { log.warn(it) { "Failed to get source settings for ${source.displayName}" } }
.singleOrNull()
suspend fun await(sourceId: Long) = asFlow(sourceId)
.catch { log.warn(it) { "Failed to get source settings for $sourceId" } }
.singleOrNull()
fun asFlow(source: Source) = sourceRepository.getSourceSettings(source.id)
fun asFlow(sourceId: Long) = sourceRepository.getSourceSettings(sourceId)
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,60 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.interactor
import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterChange
import ca.gosyer.jui.domain.source.service.SourceRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class SetSourceFilter @Inject constructor(private val sourceRepository: SourceRepository) {
suspend fun await(source: Source, filterIndex: Int, filter: Any) = asFlow(source, filterIndex, filter)
.catch { log.warn(it) { "Failed to set filter for ${source.displayName} with index = $filterIndex and value = $filter" } }
.collect()
suspend fun await(sourceId: Long, filterIndex: Int, filter: Any) = asFlow(sourceId, filterIndex, filter)
.catch { log.warn(it) { "Failed to set filter for $sourceId with index = $filterIndex and value = $filter" } }
.collect()
suspend fun await(source: Source, filterIndex: Int, childFilterIndex: Int, filter: Any) = asFlow(source, filterIndex, childFilterIndex, filter)
.catch { log.warn(it) { "Failed to set filter for ${source.displayName} with index = $filterIndex and childIndex = $childFilterIndex and value = $filter" } }
.collect()
suspend fun await(sourceId: Long, filterIndex: Int, childFilterIndex: Int, filter: Any) = asFlow(sourceId, filterIndex, childFilterIndex, filter)
.catch { log.warn(it) { "Failed to set filter for $sourceId with index = $filterIndex and childIndex = $childFilterIndex and value = $filter" } }
.collect()
fun asFlow(source: Source, filterIndex: Int, filter: Any) = sourceRepository.setFilter(
source.id,
SourceFilterChange(filterIndex, filter)
)
fun asFlow(sourceId: Long, filterIndex: Int, filter: Any) = sourceRepository.setFilter(
sourceId,
SourceFilterChange(filterIndex, filter)
)
fun asFlow(source: Source, filterIndex: Int, childFilterIndex: Int, filter: Any) = sourceRepository.setFilter(
source.id,
SourceFilterChange(filterIndex, Json.encodeToString(SourceFilterChange(childFilterIndex, filter)))
)
fun asFlow(sourceId: Long, filterIndex: Int, childFilterIndex: Int,filter: Any) = sourceRepository.setFilter(
sourceId,
SourceFilterChange(filterIndex, Json.encodeToString(SourceFilterChange(childFilterIndex, filter)))
)
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,40 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package ca.gosyer.jui.domain.source.interactor
import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreferenceChange
import ca.gosyer.jui.domain.source.service.SourceRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class SetSourceSetting @Inject constructor(private val sourceRepository: SourceRepository) {
suspend fun await(source: Source, settingIndex: Int, setting: Any) = asFlow(source, settingIndex, setting)
.catch { log.warn(it) { "Failed to set setting for ${source.displayName} with index = $settingIndex and value = $setting" } }
.collect()
suspend fun await(sourceId: Long, settingIndex: Int, setting: Any) = asFlow(sourceId, settingIndex, setting)
.catch { log.warn(it) { "Failed to set setting for $sourceId with index = $settingIndex and value = $setting" } }
.collect()
fun asFlow(source: Source, settingIndex: Int, setting: Any) = sourceRepository.setSourceSetting(
source.id,
SourcePreferenceChange(settingIndex, setting)
)
fun asFlow(sourceId: Long, settingIndex: Int, setting: Any) = sourceRepository.setSourceSetting(
sourceId,
SourcePreferenceChange(settingIndex, setting)
)
companion object {
private val log = logging()
}
}

View File

@@ -9,10 +9,12 @@ package ca.gosyer.jui.ui.sources.browse
import ca.gosyer.jui.domain.library.model.DisplayMode
import ca.gosyer.jui.domain.library.service.LibraryPreferences
import ca.gosyer.jui.domain.manga.model.Manga
import ca.gosyer.jui.domain.source.interactor.GetLatestManga
import ca.gosyer.jui.domain.source.interactor.GetPopularManga
import ca.gosyer.jui.domain.source.interactor.GetSearchManga
import ca.gosyer.jui.domain.source.model.MangaPage
import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.domain.source.service.CatalogPreferences
import ca.gosyer.jui.domain.source.service.SourceRepository
import ca.gosyer.jui.ui.base.state.SavedStateHandle
import ca.gosyer.jui.ui.base.state.getStateFlow
import ca.gosyer.jui.uicore.vm.ContextWrapper
@@ -24,8 +26,6 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import me.tatarka.inject.annotations.Inject
@@ -33,7 +33,9 @@ import org.lighthousegames.logging.logging
class SourceScreenViewModel(
private val source: Source,
private val sourceHandler: SourceRepository,
private val getLatestManga: GetLatestManga,
private val getPopularManga: GetPopularManga,
private val getSearchManga: GetSearchManga,
private val catalogPreferences: CatalogPreferences,
private val libraryPreferences: LibraryPreferences,
contextWrapper: ContextWrapper,
@@ -42,7 +44,9 @@ class SourceScreenViewModel(
) : ViewModel(contextWrapper) {
@Inject constructor(
sourceHandler: SourceRepository,
getLatestManga: GetLatestManga,
getPopularManga: GetPopularManga,
getSearchManga: GetSearchManga,
catalogPreferences: CatalogPreferences,
libraryPreferences: LibraryPreferences,
contextWrapper: ContextWrapper,
@@ -50,7 +54,9 @@ class SourceScreenViewModel(
params: Params
) : this(
params.source,
sourceHandler,
getLatestManga,
getPopularManga,
getSearchManga,
catalogPreferences,
libraryPreferences,
contextWrapper,
@@ -128,18 +134,14 @@ class SourceScreenViewModel(
private suspend fun getPage(): MangaPage? {
return when {
isLatest.value -> sourceHandler.getLatestManga(source.id, pageNum.value)
_query.value != null || _usingFilters.value -> sourceHandler.getSearchResults(
isLatest.value -> getLatestManga.await(source, pageNum.value)
_query.value != null || _usingFilters.value -> getSearchManga.await(
source.id,
_query.value?.ifBlank { null },
_query.value,
pageNum.value
)
else -> sourceHandler.getPopularManga(source.id, pageNum.value)
else -> getPopularManga.await(source.id, pageNum.value)
}
.catch {
log.warn(it) { "Error getting source page" }
}
.singleOrNull()
}
fun startSearch(query: String?) {

View File

@@ -6,9 +6,9 @@
package ca.gosyer.jui.ui.sources.browse.filter
import ca.gosyer.jui.domain.source.interactor.GetFilterList
import ca.gosyer.jui.domain.source.interactor.SetSourceFilter
import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilter
import ca.gosyer.jui.domain.source.model.sourcefilters.SourceFilterChange
import ca.gosyer.jui.domain.source.service.SourceRepository
import ca.gosyer.jui.ui.base.state.SavedStateHandle
import ca.gosyer.jui.ui.base.state.getStateFlow
import ca.gosyer.jui.ui.sources.browse.filter.model.SourceFiltersView
@@ -20,32 +20,32 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.supervisorScope
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class SourceFiltersViewModel(
private val sourceId: Long,
private val sourceHandler: SourceRepository,
private val getFilterList: GetFilterList,
private val setSourceFilter: SetSourceFilter,
contextWrapper: ContextWrapper,
private val savedStateHandle: SavedStateHandle
) : ViewModel(contextWrapper) {
@Inject constructor(
sourceHandler: SourceRepository,
getFilterList: GetFilterList,
setSourceFilter: SetSourceFilter,
contextWrapper: ContextWrapper,
savedStateHandle: SavedStateHandle,
params: Params
) : this(
params.sourceId,
sourceHandler,
getFilterList,
setSourceFilter,
contextWrapper,
savedStateHandle
)
@@ -75,13 +75,12 @@ class SourceFiltersViewModel(
childFilter.state.drop(1)
.filterNotNull()
.onEach {
sourceHandler.setFilter(
setSourceFilter.await(
sourceId,
SourceFilterChange(
filter.index,
Json.encodeToString(SourceFilterChange(childFilter.index, it))
)
).collect()
filter.index,
childFilter.index,
it
)
getFilters()
}
.launchIn(this)
@@ -89,8 +88,11 @@ class SourceFiltersViewModel(
} else {
filter.state.drop(1).filterNotNull()
.onEach {
sourceHandler.setFilter(sourceId, SourceFilterChange(filter.index, it))
.collect()
setSourceFilter.await(
sourceId,
filter.index,
it
)
getFilters()
}
.launchIn(this)
@@ -109,7 +111,7 @@ class SourceFiltersViewModel(
}
private fun getFilters(initialLoad: Boolean = false) {
sourceHandler.getFilterList(sourceId, reset = initialLoad)
getFilterList.asFlow(sourceId, reset = initialLoad)
.onEach {
_filters.value = it.toView()
_loading.value = false

View File

@@ -8,9 +8,10 @@ package ca.gosyer.jui.ui.sources.globalsearch
import androidx.compose.runtime.snapshots.SnapshotStateMap
import ca.gosyer.jui.domain.manga.model.Manga
import ca.gosyer.jui.domain.source.interactor.GetSearchManga
import ca.gosyer.jui.domain.source.interactor.GetSourceList
import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.domain.source.service.CatalogPreferences
import ca.gosyer.jui.domain.source.service.SourceRepository
import ca.gosyer.jui.i18n.MR
import ca.gosyer.jui.ui.base.state.SavedStateHandle
import ca.gosyer.jui.ui.base.state.getStateFlow
@@ -41,7 +42,8 @@ import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class GlobalSearchViewModel @Inject constructor(
private val sourceHandler: SourceRepository,
private val getSourceList: GetSourceList,
private val getSearchManga: GetSearchManga,
catalogPreferences: CatalogPreferences,
contextWrapper: ContextWrapper,
private val savedStateHandle: SavedStateHandle,
@@ -74,7 +76,7 @@ class GlobalSearchViewModel @Inject constructor(
}
private fun getSources() {
sourceHandler.getSourceList()
getSourceList.asFlow()
.onEach { sources ->
installedSources.value = sources.sortedWith(
compareBy<Source, String>(String.CASE_INSENSITIVE_ORDER) { it.lang }
@@ -104,8 +106,7 @@ class GlobalSearchViewModel @Inject constructor(
sources.map { source ->
async {
semaphore.withPermit {
sourceHandler
.getSearchResults(source.id, query, 1)
getSearchManga.asFlow(source, query, 1)
.map {
if (it.mangaList.isEmpty()) {
Search.Failure(MR.strings.no_results_found.toPlatformString())

View File

@@ -9,9 +9,9 @@ package ca.gosyer.jui.ui.sources.home
import androidx.compose.runtime.Stable
import androidx.compose.ui.text.intl.Locale
import ca.gosyer.jui.core.lang.displayName
import ca.gosyer.jui.domain.source.interactor.GetSourceList
import ca.gosyer.jui.domain.source.model.Source
import ca.gosyer.jui.domain.source.service.CatalogPreferences
import ca.gosyer.jui.domain.source.service.SourceRepository
import ca.gosyer.jui.i18n.MR
import ca.gosyer.jui.ui.base.state.SavedStateHandle
import ca.gosyer.jui.ui.base.state.getStateFlow
@@ -34,7 +34,7 @@ import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class SourceHomeScreenViewModel @Inject constructor(
private val sourceHandler: SourceRepository,
private val getSourceList: GetSourceList,
catalogPreferences: CatalogPreferences,
contextWrapper: ContextWrapper,
private val savedStateHandle: SavedStateHandle
@@ -98,7 +98,7 @@ class SourceHomeScreenViewModel @Inject constructor(
}
private fun getSources() {
sourceHandler.getSourceList()
getSourceList.asFlow()
.onEach {
installedSources.value = it
_isLoading.value = false

View File

@@ -6,9 +6,9 @@
package ca.gosyer.jui.ui.sources.settings
import ca.gosyer.jui.domain.source.interactor.GetSourceSettings
import ca.gosyer.jui.domain.source.interactor.SetSourceSetting
import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreference
import ca.gosyer.jui.domain.source.model.sourcepreference.SourcePreferenceChange
import ca.gosyer.jui.domain.source.service.SourceRepository
import ca.gosyer.jui.ui.sources.settings.model.SourceSettingsView
import ca.gosyer.jui.uicore.vm.ContextWrapper
import ca.gosyer.jui.uicore.vm.ViewModel
@@ -18,7 +18,6 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
@@ -29,7 +28,8 @@ import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class SourceSettingsScreenViewModel @Inject constructor(
private val sourceHandler: SourceRepository,
private val getSourceSettings: GetSourceSettings,
private val setSourceSetting: SetSourceSetting,
contextWrapper: ContextWrapper,
private val params: Params
) : ViewModel(contextWrapper) {
@@ -47,11 +47,7 @@ class SourceSettingsScreenViewModel @Inject constructor(
setting.state.drop(1)
.filterNotNull()
.onEach {
sourceHandler.setSourceSetting(params.sourceId, SourcePreferenceChange(setting.index, it))
.catch {
log.warn(it) { "Error setting source setting" }
}
.collect()
setSourceSetting.await(params.sourceId, setting.index, it)
getSourceSettings()
}
.launchIn(this)
@@ -61,7 +57,7 @@ class SourceSettingsScreenViewModel @Inject constructor(
}
private fun getSourceSettings() {
sourceHandler.getSourceSettings(params.sourceId)
getSourceSettings.asFlow(params.sourceId)
.onEach {
_sourceSettings.value = it.toView()
_loading.value = false