Migrate to AndroidX ViewModel from Voyager ScreenModel (#3594)
This commit is contained in:
@@ -209,6 +209,7 @@ dependencies {
|
|||||||
implementation(projects.i18n)
|
implementation(projects.i18n)
|
||||||
implementation(projects.core.archive)
|
implementation(projects.core.archive)
|
||||||
implementation(projects.core.common)
|
implementation(projects.core.common)
|
||||||
|
implementation(projects.core.viewmodel)
|
||||||
implementation(projects.coreMetadata)
|
implementation(projects.coreMetadata)
|
||||||
implementation(projects.sourceApi)
|
implementation(projects.sourceApi)
|
||||||
implementation(projects.sourceLocal)
|
implementation(projects.sourceLocal)
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
|
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsViewModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -66,7 +66,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
|||||||
@Composable
|
@Composable
|
||||||
fun ExtensionDetailsScreen(
|
fun ExtensionDetailsScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: ExtensionDetailsScreenModel.State,
|
state: ExtensionDetailsViewModel.State,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickEnableAll: () -> Unit,
|
onClickEnableAll: () -> Unit,
|
||||||
onClickDisableAll: () -> Unit,
|
onClickDisableAll: () -> Unit,
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
|||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsViewModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
|
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -71,7 +71,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionScreen(
|
fun ExtensionScreen(
|
||||||
state: ExtensionsScreenModel.State,
|
state: ExtensionsViewModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
@@ -133,7 +133,7 @@ fun ExtensionScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtensionContent(
|
private fun ExtensionContent(
|
||||||
state: ExtensionsScreenModel.State,
|
state: ExtensionsViewModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onLongClickItem: (Extension) -> Unit,
|
onLongClickItem: (Extension) -> Unit,
|
||||||
onClickItemCancel: (Extension) -> Unit,
|
onClickItemCancel: (Extension) -> Unit,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
|||||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchViewModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
@@ -20,7 +20,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchScreen(
|
fun GlobalSearchScreen(
|
||||||
state: SearchScreenModel.State,
|
state: SearchViewModel.State,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchViewModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateSearchScreen(
|
fun MigrateSearchScreen(
|
||||||
state: SearchScreenModel.State,
|
state: SearchViewModel.State,
|
||||||
fromSourceId: Long?,
|
fromSourceId: Long?,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.browse.components.SourceIcon
|
import eu.kanade.presentation.browse.components.SourceIcon
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceViewModel
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -43,7 +43,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateSourceScreen(
|
fun MigrateSourceScreen(
|
||||||
state: MigrateSourceScreenModel.State,
|
state: MigrateSourceViewModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onClickItem: (Source) -> Unit,
|
onClickItem: (Source) -> Unit,
|
||||||
onToggleSortingDirection: () -> Unit,
|
onToggleSortingDirection: () -> Unit,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import eu.kanade.presentation.browse.components.BaseSourceItem
|
|||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.presentation.util.animateItemFastScroll
|
import eu.kanade.presentation.util.animateItemFastScroll
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterViewModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -23,7 +23,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
|||||||
@Composable
|
@Composable
|
||||||
fun SourcesFilterScreen(
|
fun SourcesFilterScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: SourcesFilterScreenModel.State.Success,
|
state: SourcesFilterViewModel.State.Success,
|
||||||
onClickLanguage: (String) -> Unit,
|
onClickLanguage: (String) -> Unit,
|
||||||
onClickSource: (Source) -> Unit,
|
onClickSource: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -55,7 +55,7 @@ fun SourcesFilterScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun SourcesFilterContent(
|
private fun SourcesFilterContent(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
state: SourcesFilterScreenModel.State.Success,
|
state: SourcesFilterViewModel.State.Success,
|
||||||
onClickLanguage: (String) -> Unit,
|
onClickLanguage: (String) -> Unit,
|
||||||
onClickSource: (Source) -> Unit,
|
onClickSource: (Source) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesViewModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceViewModel.Listing
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.model.Pin
|
import tachiyomi.domain.source.model.Pin
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
@@ -40,7 +40,7 @@ import tachiyomi.source.local.isLocal
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourcesScreen(
|
fun SourcesScreen(
|
||||||
state: SourcesScreenModel.State,
|
state: SourcesViewModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onClickItem: (Source, Listing) -> Unit,
|
onClickItem: (Source, Listing) -> Unit,
|
||||||
onClickPin: (Source) -> Unit,
|
onClickPin: (Source) -> Unit,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import eu.kanade.presentation.components.relativeDateText
|
|||||||
import eu.kanade.presentation.history.components.HistoryItem
|
import eu.kanade.presentation.history.components.HistoryItem
|
||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.presentation.util.animateItemFastScroll
|
import eu.kanade.presentation.util.animateItemFastScroll
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
import eu.kanade.tachiyomi.ui.history.HistoryViewModel
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
@@ -32,13 +32,13 @@ import java.time.LocalDate
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryScreen(
|
fun HistoryScreen(
|
||||||
state: HistoryScreenModel.State,
|
state: HistoryViewModel.State,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
onSearchQueryChange: (String?) -> Unit,
|
onSearchQueryChange: (String?) -> Unit,
|
||||||
onClickCover: (mangaId: Long) -> Unit,
|
onClickCover: (mangaId: Long) -> Unit,
|
||||||
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
onClickResume: (mangaId: Long, chapterId: Long) -> Unit,
|
||||||
onClickFavorite: (mangaId: Long) -> Unit,
|
onClickFavorite: (mangaId: Long) -> Unit,
|
||||||
onDialogChange: (HistoryScreenModel.Dialog?) -> Unit,
|
onDialogChange: (HistoryViewModel.Dialog?) -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
@@ -53,7 +53,7 @@ fun HistoryScreen(
|
|||||||
title = stringResource(MR.strings.pref_clear_history),
|
title = stringResource(MR.strings.pref_clear_history),
|
||||||
icon = Icons.Outlined.DeleteSweep,
|
icon = Icons.Outlined.DeleteSweep,
|
||||||
onClick = {
|
onClick = {
|
||||||
onDialogChange(HistoryScreenModel.Dialog.DeleteAll)
|
onDialogChange(HistoryViewModel.Dialog.DeleteAll)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -83,7 +83,7 @@ fun HistoryScreen(
|
|||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onClickCover = { history -> onClickCover(history.mangaId) },
|
onClickCover = { history -> onClickCover(history.mangaId) },
|
||||||
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) },
|
||||||
onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) },
|
onClickDelete = { item -> onDialogChange(HistoryViewModel.Dialog.Delete(item)) },
|
||||||
onClickFavorite = { history -> onClickFavorite(history.mangaId) },
|
onClickFavorite = { history -> onClickFavorite(history.mangaId) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -144,8 +144,8 @@ sealed interface HistoryUiModel {
|
|||||||
@PreviewLightDark
|
@PreviewLightDark
|
||||||
@Composable
|
@Composable
|
||||||
internal fun HistoryScreenPreviews(
|
internal fun HistoryScreenPreviews(
|
||||||
@PreviewParameter(HistoryScreenModelStateProvider::class)
|
@PreviewParameter(HistoryviewModelStateProvider::class)
|
||||||
historyState: HistoryScreenModel.State,
|
historyState: HistoryViewModel.State,
|
||||||
) {
|
) {
|
||||||
TachiyomiPreviewTheme {
|
TachiyomiPreviewTheme {
|
||||||
HistoryScreen(
|
HistoryScreen(
|
||||||
|
|||||||
+9
-9
@@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.presentation.history
|
package eu.kanade.presentation.history
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
import eu.kanade.tachiyomi.ui.history.HistoryViewModel
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -10,9 +10,9 @@ import java.time.temporal.ChronoUnit
|
|||||||
import java.util.Date
|
import java.util.Date
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenModel.State> {
|
class HistoryviewModelStateProvider : PreviewParameterProvider<HistoryViewModel.State> {
|
||||||
|
|
||||||
private val multiPage = HistoryScreenModel.State(
|
private val multiPage = HistoryViewModel.State(
|
||||||
searchQuery = null,
|
searchQuery = null,
|
||||||
list =
|
list =
|
||||||
listOf(HistoryUiModelExamples.headerToday)
|
listOf(HistoryUiModelExamples.headerToday)
|
||||||
@@ -26,7 +26,7 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
|||||||
dialog = null,
|
dialog = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val shortRecent = HistoryScreenModel.State(
|
private val shortRecent = HistoryViewModel.State(
|
||||||
searchQuery = null,
|
searchQuery = null,
|
||||||
list = listOf(
|
list = listOf(
|
||||||
HistoryUiModelExamples.headerToday,
|
HistoryUiModelExamples.headerToday,
|
||||||
@@ -35,7 +35,7 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
|||||||
dialog = null,
|
dialog = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val shortFuture = HistoryScreenModel.State(
|
private val shortFuture = HistoryViewModel.State(
|
||||||
searchQuery = null,
|
searchQuery = null,
|
||||||
list = listOf(
|
list = listOf(
|
||||||
HistoryUiModelExamples.headerTomorrow,
|
HistoryUiModelExamples.headerTomorrow,
|
||||||
@@ -44,23 +44,23 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
|||||||
dialog = null,
|
dialog = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val empty = HistoryScreenModel.State(
|
private val empty = HistoryViewModel.State(
|
||||||
searchQuery = null,
|
searchQuery = null,
|
||||||
list = listOf(),
|
list = listOf(),
|
||||||
dialog = null,
|
dialog = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val loadingWithSearchQuery = HistoryScreenModel.State(
|
private val loadingWithSearchQuery = HistoryViewModel.State(
|
||||||
searchQuery = "Example Search Query",
|
searchQuery = "Example Search Query",
|
||||||
)
|
)
|
||||||
|
|
||||||
private val loading = HistoryScreenModel.State(
|
private val loading = HistoryViewModel.State(
|
||||||
searchQuery = null,
|
searchQuery = null,
|
||||||
list = null,
|
list = null,
|
||||||
dialog = null,
|
dialog = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
override val values: Sequence<HistoryScreenModel.State> = sequenceOf(
|
override val values: Sequence<HistoryViewModel.State> = sequenceOf(
|
||||||
multiPage,
|
multiPage,
|
||||||
shortRecent,
|
shortRecent,
|
||||||
shortFuture,
|
shortFuture,
|
||||||
@@ -19,7 +19,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import eu.kanade.presentation.components.TabbedDialog
|
import eu.kanade.presentation.components.TabbedDialog
|
||||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel
|
import eu.kanade.tachiyomi.ui.library.LibrarySettingsViewModel
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
import tachiyomi.core.common.preference.TriState
|
import tachiyomi.core.common.preference.TriState
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
@@ -41,7 +41,7 @@ import tachiyomi.presentation.core.util.collectAsState
|
|||||||
@Composable
|
@Composable
|
||||||
fun LibrarySettingsDialog(
|
fun LibrarySettingsDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
screenModel: LibrarySettingsScreenModel,
|
viewModel: LibrarySettingsViewModel,
|
||||||
category: Category?,
|
category: Category?,
|
||||||
) {
|
) {
|
||||||
TabbedDialog(
|
TabbedDialog(
|
||||||
@@ -59,14 +59,14 @@ fun LibrarySettingsDialog(
|
|||||||
) {
|
) {
|
||||||
when (page) {
|
when (page) {
|
||||||
0 -> FilterPage(
|
0 -> FilterPage(
|
||||||
screenModel = screenModel,
|
viewModel = viewModel,
|
||||||
)
|
)
|
||||||
1 -> SortPage(
|
1 -> SortPage(
|
||||||
category = category,
|
category = category,
|
||||||
screenModel = screenModel,
|
viewModel = viewModel,
|
||||||
)
|
)
|
||||||
2 -> DisplayPage(
|
2 -> DisplayPage(
|
||||||
screenModel = screenModel,
|
viewModel = viewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,11 +75,11 @@ fun LibrarySettingsDialog(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.FilterPage(
|
private fun ColumnScope.FilterPage(
|
||||||
screenModel: LibrarySettingsScreenModel,
|
viewModel: LibrarySettingsViewModel,
|
||||||
) {
|
) {
|
||||||
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded.collectAsState()
|
val filterDownloaded by viewModel.libraryPreferences.filterDownloaded.collectAsState()
|
||||||
val downloadedOnly by screenModel.preferences.downloadedOnly.collectAsState()
|
val downloadedOnly by viewModel.preferences.downloadedOnly.collectAsState()
|
||||||
val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions.collectAsState()
|
val autoUpdateMangaRestrictions by viewModel.libraryPreferences.autoUpdateMangaRestrictions.collectAsState()
|
||||||
|
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(MR.strings.label_downloaded),
|
label = stringResource(MR.strings.label_downloaded),
|
||||||
@@ -89,64 +89,64 @@ private fun ColumnScope.FilterPage(
|
|||||||
filterDownloaded
|
filterDownloaded
|
||||||
},
|
},
|
||||||
enabled = !downloadedOnly,
|
enabled = !downloadedOnly,
|
||||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterDownloaded) },
|
onClick = { viewModel.toggleFilter(LibraryPreferences::filterDownloaded) },
|
||||||
)
|
)
|
||||||
val filterUnread by screenModel.libraryPreferences.filterUnread.collectAsState()
|
val filterUnread by viewModel.libraryPreferences.filterUnread.collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(MR.strings.action_filter_unread),
|
label = stringResource(MR.strings.action_filter_unread),
|
||||||
state = filterUnread,
|
state = filterUnread,
|
||||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterUnread) },
|
onClick = { viewModel.toggleFilter(LibraryPreferences::filterUnread) },
|
||||||
)
|
)
|
||||||
val filterStarted by screenModel.libraryPreferences.filterStarted.collectAsState()
|
val filterStarted by viewModel.libraryPreferences.filterStarted.collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(MR.strings.label_started),
|
label = stringResource(MR.strings.label_started),
|
||||||
state = filterStarted,
|
state = filterStarted,
|
||||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterStarted) },
|
onClick = { viewModel.toggleFilter(LibraryPreferences::filterStarted) },
|
||||||
)
|
)
|
||||||
val filterBookmarked by screenModel.libraryPreferences.filterBookmarked.collectAsState()
|
val filterBookmarked by viewModel.libraryPreferences.filterBookmarked.collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(MR.strings.action_filter_bookmarked),
|
label = stringResource(MR.strings.action_filter_bookmarked),
|
||||||
state = filterBookmarked,
|
state = filterBookmarked,
|
||||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterBookmarked) },
|
onClick = { viewModel.toggleFilter(LibraryPreferences::filterBookmarked) },
|
||||||
)
|
)
|
||||||
val filterCompleted by screenModel.libraryPreferences.filterCompleted.collectAsState()
|
val filterCompleted by viewModel.libraryPreferences.filterCompleted.collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(MR.strings.completed),
|
label = stringResource(MR.strings.completed),
|
||||||
state = filterCompleted,
|
state = filterCompleted,
|
||||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterCompleted) },
|
onClick = { viewModel.toggleFilter(LibraryPreferences::filterCompleted) },
|
||||||
)
|
)
|
||||||
// TODO: re-enable when custom intervals are ready for stable
|
// TODO: re-enable when custom intervals are ready for stable
|
||||||
if ((!isReleaseBuildType) && LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in autoUpdateMangaRestrictions) {
|
if ((!isReleaseBuildType) && LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in autoUpdateMangaRestrictions) {
|
||||||
val filterIntervalCustom by screenModel.libraryPreferences.filterIntervalCustom.collectAsState()
|
val filterIntervalCustom by viewModel.libraryPreferences.filterIntervalCustom.collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(MR.strings.action_filter_interval_custom),
|
label = stringResource(MR.strings.action_filter_interval_custom),
|
||||||
state = filterIntervalCustom,
|
state = filterIntervalCustom,
|
||||||
onClick = { screenModel.toggleFilter(LibraryPreferences::filterIntervalCustom) },
|
onClick = { viewModel.toggleFilter(LibraryPreferences::filterIntervalCustom) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val trackers by screenModel.trackersFlow.collectAsState()
|
val trackers by viewModel.trackersFlow.collectAsState()
|
||||||
when (trackers.size) {
|
when (trackers.size) {
|
||||||
0 -> {
|
0 -> {
|
||||||
// No trackers
|
// No trackers
|
||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
val service = trackers[0]
|
val service = trackers[0]
|
||||||
val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
val filterTracker by viewModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(MR.strings.action_filter_tracked),
|
label = stringResource(MR.strings.action_filter_tracked),
|
||||||
state = filterTracker,
|
state = filterTracker,
|
||||||
onClick = { screenModel.toggleTracker(service.id.toInt()) },
|
onClick = { viewModel.toggleTracker(service.id.toInt()) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
HeadingItem(MR.strings.action_filter_tracked)
|
HeadingItem(MR.strings.action_filter_tracked)
|
||||||
trackers.map { service ->
|
trackers.map { service ->
|
||||||
val filterTracker by screenModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
val filterTracker by viewModel.libraryPreferences.filterTracking(service.id.toInt()).collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = service.name,
|
label = service.name,
|
||||||
state = filterTracker,
|
state = filterTracker,
|
||||||
onClick = { screenModel.toggleTracker(service.id.toInt()) },
|
onClick = { viewModel.toggleTracker(service.id.toInt()) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,9 +156,9 @@ private fun ColumnScope.FilterPage(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.SortPage(
|
private fun ColumnScope.SortPage(
|
||||||
category: Category?,
|
category: Category?,
|
||||||
screenModel: LibrarySettingsScreenModel,
|
viewModel: LibrarySettingsViewModel,
|
||||||
) {
|
) {
|
||||||
val trackers by screenModel.trackersFlow.collectAsState()
|
val trackers by viewModel.trackersFlow.collectAsState()
|
||||||
val sortingMode = category.sort.type
|
val sortingMode = category.sort.type
|
||||||
val sortDescending = !category.sort.isAscending
|
val sortDescending = !category.sort.isAscending
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ private fun ColumnScope.SortPage(
|
|||||||
icon = Icons.Default.Refresh
|
icon = Icons.Default.Refresh
|
||||||
.takeIf { sortingMode == LibrarySort.Type.Random },
|
.takeIf { sortingMode == LibrarySort.Type.Random },
|
||||||
onClick = {
|
onClick = {
|
||||||
screenModel.setSort(category, mode, LibrarySort.Direction.Ascending)
|
viewModel.setSort(category, mode, LibrarySort.Direction.Ascending)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return@map
|
return@map
|
||||||
@@ -211,7 +211,7 @@ private fun ColumnScope.SortPage(
|
|||||||
LibrarySort.Direction.Ascending
|
LibrarySort.Direction.Ascending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screenModel.setSort(category, mode, direction)
|
viewModel.setSort(category, mode, direction)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -226,14 +226,14 @@ private val displayModes = listOf(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.DisplayPage(
|
private fun ColumnScope.DisplayPage(
|
||||||
screenModel: LibrarySettingsScreenModel,
|
viewModel: LibrarySettingsViewModel,
|
||||||
) {
|
) {
|
||||||
val displayMode by screenModel.libraryPreferences.displayMode.collectAsState()
|
val displayMode by viewModel.libraryPreferences.displayMode.collectAsState()
|
||||||
SettingsChipRow(MR.strings.action_display_mode) {
|
SettingsChipRow(MR.strings.action_display_mode) {
|
||||||
displayModes.map { (titleRes, mode) ->
|
displayModes.map { (titleRes, mode) ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = displayMode == mode,
|
selected = displayMode == mode,
|
||||||
onClick = { screenModel.setDisplayMode(mode) },
|
onClick = { viewModel.setDisplayMode(mode) },
|
||||||
label = { Text(stringResource(titleRes)) },
|
label = { Text(stringResource(titleRes)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -243,9 +243,9 @@ private fun ColumnScope.DisplayPage(
|
|||||||
val configuration = LocalConfiguration.current
|
val configuration = LocalConfiguration.current
|
||||||
val columnPreference = remember {
|
val columnPreference = remember {
|
||||||
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
screenModel.libraryPreferences.landscapeColumns
|
viewModel.libraryPreferences.landscapeColumns
|
||||||
} else {
|
} else {
|
||||||
screenModel.libraryPreferences.portraitColumns
|
viewModel.libraryPreferences.portraitColumns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,32 +267,32 @@ private fun ColumnScope.DisplayPage(
|
|||||||
HeadingItem(MR.strings.overlay_header)
|
HeadingItem(MR.strings.overlay_header)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.action_display_download_badge),
|
label = stringResource(MR.strings.action_display_download_badge),
|
||||||
pref = screenModel.libraryPreferences.downloadBadge,
|
pref = viewModel.libraryPreferences.downloadBadge,
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.action_display_unread_badge),
|
label = stringResource(MR.strings.action_display_unread_badge),
|
||||||
pref = screenModel.libraryPreferences.unreadBadge,
|
pref = viewModel.libraryPreferences.unreadBadge,
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.action_display_local_badge),
|
label = stringResource(MR.strings.action_display_local_badge),
|
||||||
pref = screenModel.libraryPreferences.localBadge,
|
pref = viewModel.libraryPreferences.localBadge,
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.action_display_language_badge),
|
label = stringResource(MR.strings.action_display_language_badge),
|
||||||
pref = screenModel.libraryPreferences.languageBadge,
|
pref = viewModel.libraryPreferences.languageBadge,
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.action_display_show_continue_reading_button),
|
label = stringResource(MR.strings.action_display_show_continue_reading_button),
|
||||||
pref = screenModel.libraryPreferences.showContinueReadingButton,
|
pref = viewModel.libraryPreferences.showContinueReadingButton,
|
||||||
)
|
)
|
||||||
|
|
||||||
HeadingItem(MR.strings.tabs_header)
|
HeadingItem(MR.strings.tabs_header)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.action_display_show_tabs),
|
label = stringResource(MR.strings.action_display_show_tabs),
|
||||||
pref = screenModel.libraryPreferences.categoryTabs,
|
pref = viewModel.libraryPreferences.categoryTabs,
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.action_display_show_number_of_items),
|
label = stringResource(MR.strings.action_display_show_number_of_items),
|
||||||
pref = screenModel.libraryPreferences.categoryNumberOfItems,
|
pref = viewModel.libraryPreferences.categoryNumberOfItems,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ import eu.kanade.presentation.util.formatChapterNumber
|
|||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
||||||
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
import eu.kanade.tachiyomi.ui.manga.ChapterList
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
|
import eu.kanade.tachiyomi.ui.manga.MangaViewModel
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
import tachiyomi.domain.chapter.model.Chapter
|
||||||
import tachiyomi.domain.chapter.service.missingChaptersCount
|
import tachiyomi.domain.chapter.service.missingChaptersCount
|
||||||
@@ -79,7 +79,7 @@ import java.time.Instant
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaScreen(
|
fun MangaScreen(
|
||||||
state: MangaScreenModel.State.Success,
|
state: MangaViewModel.State.Success,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
nextUpdate: Instant?,
|
nextUpdate: Instant?,
|
||||||
isTabletUi: Boolean,
|
isTabletUi: Boolean,
|
||||||
@@ -210,7 +210,7 @@ fun MangaScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MangaScreenSmallImpl(
|
private fun MangaScreenSmallImpl(
|
||||||
state: MangaScreenModel.State.Success,
|
state: MangaViewModel.State.Success,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
nextUpdate: Instant?,
|
nextUpdate: Instant?,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
@@ -452,7 +452,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaScreenLargeImpl(
|
fun MangaScreenLargeImpl(
|
||||||
state: MangaScreenModel.State.Success,
|
state: MangaViewModel.State.Success,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
nextUpdate: Instant?,
|
nextUpdate: Instant?,
|
||||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||||
|
|||||||
+18
-18
@@ -30,9 +30,8 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastMap
|
import androidx.compose.ui.util.fastMap
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.components.SourceIcon
|
import eu.kanade.presentation.browse.components.SourceIcon
|
||||||
@@ -42,6 +41,7 @@ import eu.kanade.presentation.util.Screen
|
|||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.launchUI
|
import tachiyomi.core.common.util.lang.launchUI
|
||||||
import tachiyomi.core.common.util.lang.toLong
|
import tachiyomi.core.common.util.lang.toLong
|
||||||
@@ -67,13 +67,13 @@ class ClearDatabaseScreen : Screen() {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val model = rememberScreenModel { ClearDatabaseScreenModel() }
|
val viewModel = viewModel<ClearDatabaseViewModel>()
|
||||||
val state by model.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
when (val s = state) {
|
when (val s = state) {
|
||||||
is ClearDatabaseScreenModel.State.Loading -> LoadingScreen()
|
is ClearDatabaseViewModel.State.Loading -> LoadingScreen()
|
||||||
is ClearDatabaseScreenModel.State.Ready -> {
|
is ClearDatabaseViewModel.State.Ready -> {
|
||||||
if (s.showConfirmation) {
|
if (s.showConfirmation) {
|
||||||
var keepReadManga by remember { mutableStateOf(true) }
|
var keepReadManga by remember { mutableStateOf(true) }
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
@@ -108,14 +108,14 @@ class ClearDatabaseScreen : Screen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDismissRequest = model::hideConfirmation,
|
onDismissRequest = viewModel::hideConfirmation,
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launchUI {
|
scope.launchUI {
|
||||||
model.removeMangaBySourceId(keepReadManga)
|
viewModel.removeMangaBySourceId(keepReadManga)
|
||||||
model.clearSelection()
|
viewModel.clearSelection()
|
||||||
model.hideConfirmation()
|
viewModel.hideConfirmation()
|
||||||
context.toast(MR.strings.clear_database_completed)
|
context.toast(MR.strings.clear_database_completed)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -124,7 +124,7 @@ class ClearDatabaseScreen : Screen() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = model::hideConfirmation) {
|
TextButton(onClick = viewModel::hideConfirmation) {
|
||||||
Text(text = stringResource(MR.strings.action_cancel))
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -143,12 +143,12 @@ class ClearDatabaseScreen : Screen() {
|
|||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(MR.strings.action_select_all),
|
title = stringResource(MR.strings.action_select_all),
|
||||||
icon = Icons.Outlined.SelectAll,
|
icon = Icons.Outlined.SelectAll,
|
||||||
onClick = model::selectAll,
|
onClick = viewModel::selectAll,
|
||||||
),
|
),
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(MR.strings.action_select_inverse),
|
title = stringResource(MR.strings.action_select_inverse),
|
||||||
icon = Icons.Outlined.FlipToBack,
|
icon = Icons.Outlined.FlipToBack,
|
||||||
onClick = model::invertSelection,
|
onClick = viewModel::invertSelection,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -168,14 +168,14 @@ class ClearDatabaseScreen : Screen() {
|
|||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
actionLabel = stringResource(MR.strings.action_delete),
|
actionLabel = stringResource(MR.strings.action_delete),
|
||||||
actionEnabled = s.selection.isNotEmpty(),
|
actionEnabled = s.selection.isNotEmpty(),
|
||||||
onClickAction = model::showConfirmation,
|
onClickAction = viewModel::showConfirmation,
|
||||||
) {
|
) {
|
||||||
items(s.items) { sourceWithCount ->
|
items(s.items) { sourceWithCount ->
|
||||||
ClearDatabaseItem(
|
ClearDatabaseItem(
|
||||||
source = sourceWithCount.source,
|
source = sourceWithCount.source,
|
||||||
count = sourceWithCount.count,
|
count = sourceWithCount.count,
|
||||||
isSelected = s.selection.contains(sourceWithCount.id),
|
isSelected = s.selection.contains(sourceWithCount.id),
|
||||||
onClickSelect = { model.toggleSelection(sourceWithCount.source) },
|
onClickSelect = { viewModel.toggleSelection(sourceWithCount.source) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,12 +220,12 @@ class ClearDatabaseScreen : Screen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ClearDatabaseScreenModel : StateScreenModel<ClearDatabaseScreenModel.State>(State.Loading) {
|
private class ClearDatabaseViewModel : StateViewModel<ClearDatabaseViewModel.State>(State.Loading) {
|
||||||
private val getSourcesWithNonLibraryManga: GetSourcesWithNonLibraryManga = Injekt.get()
|
private val getSourcesWithNonLibraryManga: GetSourcesWithNonLibraryManga = Injekt.get()
|
||||||
private val database: Database = Injekt.get()
|
private val database: Database = Injekt.get()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
getSourcesWithNonLibraryManga.subscribe()
|
getSourcesWithNonLibraryManga.subscribe()
|
||||||
.collectLatest { list ->
|
.collectLatest { list ->
|
||||||
mutableState.update { old ->
|
mutableState.update { old ->
|
||||||
|
|||||||
+13
-13
@@ -5,7 +5,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoreConfirmDialog
|
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoreConfirmDialog
|
||||||
@@ -26,11 +26,11 @@ class ExtensionStoresScreen(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { ExtensionStoresScreenModel() }
|
val viewModel = viewModel<ExtensionStoresViewModel>()
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
LaunchedEffect(url) {
|
LaunchedEffect(url) {
|
||||||
url?.let { screenModel.addFromDeeplink(url) }
|
url?.let { viewModel.addFromDeeplink(url) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is ExtensionStoreScreenState.Loading) {
|
if (state is ExtensionStoreScreenState.Loading) {
|
||||||
@@ -42,12 +42,12 @@ class ExtensionStoresScreen(
|
|||||||
|
|
||||||
ExtensionStoresScreen(
|
ExtensionStoresScreen(
|
||||||
state = successState,
|
state = successState,
|
||||||
onClickCreate = { screenModel.showDialog(ExtensionStoreDialog.Create()) },
|
onClickCreate = { viewModel.showDialog(ExtensionStoreDialog.Create()) },
|
||||||
onCopy = { context.copyToClipboard(it.indexUrl, it.indexUrl) },
|
onCopy = { context.copyToClipboard(it.indexUrl, it.indexUrl) },
|
||||||
onOpenWebsite = { it.contact.website.let(context::openInBrowser) },
|
onOpenWebsite = { it.contact.website.let(context::openInBrowser) },
|
||||||
onOpenDiscord = { it.contact.discord?.let(context::openInBrowser) },
|
onOpenDiscord = { it.contact.discord?.let(context::openInBrowser) },
|
||||||
onClickDelete = { screenModel.showDialog(ExtensionStoreDialog.Delete(it)) },
|
onClickDelete = { viewModel.showDialog(ExtensionStoreDialog.Delete(it)) },
|
||||||
onClickRefresh = { screenModel.refreshRepos() },
|
onClickRefresh = { viewModel.refreshRepos() },
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@ class ExtensionStoresScreen(
|
|||||||
null -> {}
|
null -> {}
|
||||||
is ExtensionStoreDialog.Create -> {
|
is ExtensionStoreDialog.Create -> {
|
||||||
ExtensionStoreCreateDialog(
|
ExtensionStoreCreateDialog(
|
||||||
onDismissRequest = screenModel::dismissDialog,
|
onDismissRequest = viewModel::dismissDialog,
|
||||||
onCreate = { screenModel.createRepo(it) },
|
onCreate = { viewModel.createRepo(it) },
|
||||||
storeIndexUrls = successState.stores.map { it.indexUrl }.toSet(),
|
storeIndexUrls = successState.stores.map { it.indexUrl }.toSet(),
|
||||||
processing = dialog.processing,
|
processing = dialog.processing,
|
||||||
errorMessage = dialog.errorMessage,
|
errorMessage = dialog.errorMessage,
|
||||||
@@ -64,16 +64,16 @@ class ExtensionStoresScreen(
|
|||||||
}
|
}
|
||||||
is ExtensionStoreDialog.Delete -> {
|
is ExtensionStoreDialog.Delete -> {
|
||||||
ExtensionStoreDeleteDialog(
|
ExtensionStoreDeleteDialog(
|
||||||
onDismissRequest = screenModel::dismissDialog,
|
onDismissRequest = viewModel::dismissDialog,
|
||||||
onDelete = { screenModel.deleteRepo(dialog.store.indexUrl) },
|
onDelete = { viewModel.deleteRepo(dialog.store.indexUrl) },
|
||||||
storeName = dialog.store.name,
|
storeName = dialog.store.name,
|
||||||
storeIndexUrl = dialog.store.indexUrl,
|
storeIndexUrl = dialog.store.indexUrl,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ExtensionStoreDialog.Confirm -> {
|
is ExtensionStoreDialog.Confirm -> {
|
||||||
ExtensionStoreConfirmDialog(
|
ExtensionStoreConfirmDialog(
|
||||||
onDismissRequest = screenModel::dismissDialog,
|
onDismissRequest = viewModel::dismissDialog,
|
||||||
onCreate = { screenModel.createRepo(dialog.url) },
|
onCreate = { viewModel.createRepo(dialog.url) },
|
||||||
storeIndexUrl = dialog.url,
|
storeIndexUrl = dialog.url,
|
||||||
storeAlreadyExists = dialog.alreadyExists,
|
storeAlreadyExists = dialog.alreadyExists,
|
||||||
processing = dialog.processing,
|
processing = dialog.processing,
|
||||||
|
|||||||
+8
-8
@@ -1,12 +1,12 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen.browse
|
package eu.kanade.presentation.more.settings.screen.browse
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import mihon.domain.extension.interactor.AddExtensionStore
|
import mihon.domain.extension.interactor.AddExtensionStore
|
||||||
import mihon.domain.extension.interactor.GetExtensionStores
|
import mihon.domain.extension.interactor.GetExtensionStores
|
||||||
import mihon.domain.extension.interactor.RemoveExtensionStore
|
import mihon.domain.extension.interactor.RemoveExtensionStore
|
||||||
@@ -16,13 +16,13 @@ import tachiyomi.core.common.util.lang.launchIO
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ExtensionStoresScreenModel(
|
class ExtensionStoresViewModel(
|
||||||
private val getExtensionStores: GetExtensionStores = Injekt.get(),
|
private val getExtensionStores: GetExtensionStores = Injekt.get(),
|
||||||
private val addExtensionStore: AddExtensionStore = Injekt.get(),
|
private val addExtensionStore: AddExtensionStore = Injekt.get(),
|
||||||
private val removeExtensionStore: RemoveExtensionStore = Injekt.get(),
|
private val removeExtensionStore: RemoveExtensionStore = Injekt.get(),
|
||||||
private val updateExtensionStores: UpdateExtensionStores = Injekt.get(),
|
private val updateExtensionStores: UpdateExtensionStores = Injekt.get(),
|
||||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||||
) : StateScreenModel<ExtensionStoreScreenState>(ExtensionStoreScreenState.Loading) {
|
) : StateViewModel<ExtensionStoreScreenState>(ExtensionStoreScreenState.Loading) {
|
||||||
|
|
||||||
private inline fun updateSuccessState(
|
private inline fun updateSuccessState(
|
||||||
func: (ExtensionStoreScreenState.Success) -> ExtensionStoreScreenState.Success,
|
func: (ExtensionStoreScreenState.Success) -> ExtensionStoreScreenState.Success,
|
||||||
@@ -36,7 +36,7 @@ class ExtensionStoresScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
getExtensionStores.subscribe()
|
getExtensionStores.subscribe()
|
||||||
.collectLatest { stores ->
|
.collectLatest { stores ->
|
||||||
mutableState.update {
|
mutableState.update {
|
||||||
@@ -55,7 +55,7 @@ class ExtensionStoresScreenModel(
|
|||||||
* @param baseUrl The baseUrl of the repo to create.
|
* @param baseUrl The baseUrl of the repo to create.
|
||||||
*/
|
*/
|
||||||
fun createRepo(baseUrl: String) {
|
fun createRepo(baseUrl: String) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
updateSuccessState {
|
updateSuccessState {
|
||||||
it.copy(
|
it.copy(
|
||||||
dialog = when (it.dialog) {
|
dialog = when (it.dialog) {
|
||||||
@@ -97,7 +97,7 @@ class ExtensionStoresScreenModel(
|
|||||||
val status = state.value
|
val status = state.value
|
||||||
|
|
||||||
if (status is ExtensionStoreScreenState.Success) {
|
if (status is ExtensionStoreScreenState.Success) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
updateExtensionStores()
|
updateExtensionStores()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ class ExtensionStoresScreenModel(
|
|||||||
* Deletes the given repo from the database
|
* Deletes the given repo from the database
|
||||||
*/
|
*/
|
||||||
fun deleteRepo(baseUrl: String) {
|
fun deleteRepo(baseUrl: String) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
removeExtensionStore(baseUrl)
|
removeExtensionStore(baseUrl)
|
||||||
extensionManager.findAvailableExtensions()
|
extensionManager.findAvailableExtensions()
|
||||||
}
|
}
|
||||||
+10
-10
@@ -11,8 +11,7 @@ import androidx.compose.runtime.Immutable
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
@@ -24,6 +23,7 @@ import eu.kanade.tachiyomi.data.backup.create.BackupOptions
|
|||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.components.LazyColumnWithAction
|
import tachiyomi.presentation.core.components.LazyColumnWithAction
|
||||||
@@ -37,8 +37,8 @@ class CreateBackupScreen : Screen() {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val model = rememberScreenModel { CreateBackupScreenModel() }
|
val viewModel = viewModel<CreateBackupViewModel>()
|
||||||
val state by model.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
val chooseBackupDir = rememberLauncherForActivityResult(
|
val chooseBackupDir = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.CreateDocument("application/*"),
|
contract = ActivityResultContracts.CreateDocument("application/*"),
|
||||||
@@ -49,7 +49,7 @@ class CreateBackupScreen : Screen() {
|
|||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
|
||||||
)
|
)
|
||||||
model.createBackup(context, it)
|
viewModel.createBackup(context, it)
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,13 +87,13 @@ class CreateBackupScreen : Screen() {
|
|||||||
|
|
||||||
item {
|
item {
|
||||||
SectionCard(MR.strings.label_library) {
|
SectionCard(MR.strings.label_library) {
|
||||||
Options(BackupOptions.libraryOptions, state, model)
|
Options(BackupOptions.libraryOptions, state, viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
SectionCard(MR.strings.label_settings) {
|
SectionCard(MR.strings.label_settings) {
|
||||||
Options(BackupOptions.settingsOptions, state, model)
|
Options(BackupOptions.settingsOptions, state, viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,8 +103,8 @@ class CreateBackupScreen : Screen() {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun Options(
|
private fun Options(
|
||||||
options: List<BackupOptions.Entry>,
|
options: List<BackupOptions.Entry>,
|
||||||
state: CreateBackupScreenModel.State,
|
state: CreateBackupViewModel.State,
|
||||||
model: CreateBackupScreenModel,
|
model: CreateBackupViewModel,
|
||||||
) {
|
) {
|
||||||
options.forEach { option ->
|
options.forEach { option ->
|
||||||
LabeledCheckbox(
|
LabeledCheckbox(
|
||||||
@@ -119,7 +119,7 @@ class CreateBackupScreen : Screen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CreateBackupScreenModel : StateScreenModel<CreateBackupScreenModel.State>(State()) {
|
private class CreateBackupViewModel : StateViewModel<CreateBackupViewModel.State>(State()) {
|
||||||
|
|
||||||
fun toggle(setter: (BackupOptions, Boolean) -> BackupOptions, enabled: Boolean) {
|
fun toggle(setter: (BackupOptions, Boolean) -> BackupOptions, enabled: Boolean) {
|
||||||
mutableState.update {
|
mutableState.update {
|
||||||
|
|||||||
+32
-10
@@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen.data
|
package eu.kanade.presentation.more.settings.screen.data
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -14,14 +15,15 @@ import androidx.compose.runtime.Immutable
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
@@ -32,6 +34,7 @@ import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
|
|||||||
import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions
|
import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||||
import tachiyomi.presentation.core.components.LazyColumnWithAction
|
import tachiyomi.presentation.core.components.LazyColumnWithAction
|
||||||
@@ -39,6 +42,8 @@ import tachiyomi.presentation.core.components.SectionCard
|
|||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class RestoreBackupScreen(
|
class RestoreBackupScreen(
|
||||||
private val uri: String,
|
private val uri: String,
|
||||||
@@ -46,10 +51,14 @@ class RestoreBackupScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val model = rememberScreenModel { RestoreBackupScreenModel(context, uri) }
|
val viewModel = viewModel<RestoreBackupViewModel>(
|
||||||
val state by model.state.collectAsState()
|
factory = RestoreBackupViewModel.Factory,
|
||||||
|
extras = CreationExtras {
|
||||||
|
set(RestoreBackupViewModel.URI_KEY, uri)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -65,7 +74,7 @@ class RestoreBackupScreen(
|
|||||||
actionLabel = stringResource(MR.strings.action_restore),
|
actionLabel = stringResource(MR.strings.action_restore),
|
||||||
actionEnabled = state.canRestore && state.options.canRestore(),
|
actionEnabled = state.canRestore && state.options.canRestore(),
|
||||||
onClickAction = {
|
onClickAction = {
|
||||||
model.startRestore()
|
viewModel.startRestore()
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
@@ -83,7 +92,7 @@ class RestoreBackupScreen(
|
|||||||
label = stringResource(option.label),
|
label = stringResource(option.label),
|
||||||
checked = option.getter(state.options),
|
checked = option.getter(state.options),
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
model.toggle(option.setter, it)
|
viewModel.toggle(option.setter, it)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -164,10 +173,23 @@ class RestoreBackupScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RestoreBackupScreenModel(
|
private class RestoreBackupViewModel(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val uri: String,
|
private val uri: String,
|
||||||
) : StateScreenModel<RestoreBackupScreenModel.State>(State()) {
|
) : StateViewModel<RestoreBackupViewModel.State>(State()) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val URI_KEY = CreationExtras.Key<String>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
RestoreBackupViewModel(
|
||||||
|
context = Injekt.get<Application>(),
|
||||||
|
uri = get(URI_KEY)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
validate(uri.toUri())
|
validate(uri.toUri())
|
||||||
|
|||||||
+27
-11
@@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen.debug
|
package eu.kanade.presentation.more.settings.screen.debug
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
@@ -18,17 +19,19 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import androidx.work.WorkInfo
|
import androidx.work.WorkInfo
|
||||||
import androidx.work.WorkQuery
|
import androidx.work.WorkQuery
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.presentation.util.ioCoroutineScope
|
|
||||||
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
import eu.kanade.tachiyomi.util.lang.toDateTimestampString
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import eu.kanade.tachiyomi.util.system.workManager
|
import eu.kanade.tachiyomi.util.system.workManager
|
||||||
@@ -56,10 +59,10 @@ class WorkerInfoScreen : Screen() {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { Model(context) }
|
val viewModel = viewModel<WorkerInfoViewModel>(factory = WorkerInfoViewModel.Factory)
|
||||||
val enqueued by screenModel.enqueued.collectAsState()
|
val enqueued by viewModel.enqueued.collectAsState()
|
||||||
val finished by screenModel.finished.collectAsState()
|
val finished by viewModel.finished.collectAsState()
|
||||||
val running by screenModel.running.collectAsState()
|
val running by viewModel.running.collectAsState()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@@ -117,7 +120,20 @@ class WorkerInfoScreen : Screen() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Model(context: Context) : ScreenModel {
|
private class WorkerInfoViewModel(
|
||||||
|
context: Context,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
WorkerInfoViewModel(
|
||||||
|
context = Injekt.get<Application>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val workManager = context.workManager
|
private val workManager = context.workManager
|
||||||
|
|
||||||
val finished = workManager
|
val finished = workManager
|
||||||
@@ -125,17 +141,17 @@ class WorkerInfoScreen : Screen() {
|
|||||||
WorkQuery.fromStates(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED),
|
WorkQuery.fromStates(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED),
|
||||||
)
|
)
|
||||||
.map(::constructString)
|
.map(::constructString)
|
||||||
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
||||||
|
|
||||||
val running = workManager
|
val running = workManager
|
||||||
.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.RUNNING))
|
.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.RUNNING))
|
||||||
.map(::constructString)
|
.map(::constructString)
|
||||||
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
||||||
|
|
||||||
val enqueued = workManager
|
val enqueued = workManager
|
||||||
.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.ENQUEUED))
|
.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.ENQUEUED))
|
||||||
.map(::constructString)
|
.map(::constructString)
|
||||||
.stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "")
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), "")
|
||||||
|
|
||||||
private fun constructString(list: List<WorkInfo>) = buildString {
|
private fun constructString(list: List<WorkInfo>) = buildString {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import eu.kanade.presentation.components.AdaptiveSheet
|
|||||||
import eu.kanade.presentation.reader.components.ModeSelectionDialog
|
import eu.kanade.presentation.reader.components.ModeSelectionDialog
|
||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsViewModel
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.SettingsIconGrid
|
import tachiyomi.presentation.core.components.SettingsIconGrid
|
||||||
import tachiyomi.presentation.core.components.material.IconToggleButton
|
import tachiyomi.presentation.core.components.material.IconToggleButton
|
||||||
@@ -29,17 +29,17 @@ private val ReaderOrientationsWithoutDefault = ReaderOrientation.entries - Reade
|
|||||||
@Composable
|
@Composable
|
||||||
fun OrientationSelectDialog(
|
fun OrientationSelectDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
screenModel: ReaderSettingsScreenModel,
|
viewModel: ReaderSettingsViewModel,
|
||||||
onChange: (StringResource) -> Unit,
|
onChange: (StringResource) -> Unit,
|
||||||
) {
|
) {
|
||||||
val manga by screenModel.mangaFlow.collectAsState()
|
val manga by viewModel.mangaFlow.collectAsState()
|
||||||
val orientation = remember(manga) { ReaderOrientation.fromPreference(manga?.readerOrientation?.toInt()) }
|
val orientation = remember(manga) { ReaderOrientation.fromPreference(manga?.readerOrientation?.toInt()) }
|
||||||
|
|
||||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||||
DialogContent(
|
DialogContent(
|
||||||
orientation = orientation,
|
orientation = orientation,
|
||||||
onChangeOrientation = {
|
onChangeOrientation = {
|
||||||
screenModel.onChangeOrientation(it)
|
viewModel.onChangeOrientation(it)
|
||||||
onChange(it.stringRes)
|
onChange(it.stringRes)
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import eu.kanade.domain.manga.model.readingMode
|
|||||||
import eu.kanade.presentation.components.AdaptiveSheet
|
import eu.kanade.presentation.components.AdaptiveSheet
|
||||||
import eu.kanade.presentation.reader.components.ModeSelectionDialog
|
import eu.kanade.presentation.reader.components.ModeSelectionDialog
|
||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsViewModel
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.SettingsIconGrid
|
import tachiyomi.presentation.core.components.SettingsIconGrid
|
||||||
@@ -31,17 +31,17 @@ private val ReadingModesWithoutDefault = ReadingMode.entries - ReadingMode.DEFAU
|
|||||||
@Composable
|
@Composable
|
||||||
fun ReadingModeSelectDialog(
|
fun ReadingModeSelectDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
screenModel: ReaderSettingsScreenModel,
|
viewModel: ReaderSettingsViewModel,
|
||||||
onChange: (StringResource) -> Unit,
|
onChange: (StringResource) -> Unit,
|
||||||
) {
|
) {
|
||||||
val manga by screenModel.mangaFlow.collectAsState()
|
val manga by viewModel.mangaFlow.collectAsState()
|
||||||
val readingMode = remember(manga) { ReadingMode.fromPreference(manga?.readingMode?.toInt()) }
|
val readingMode = remember(manga) { ReadingMode.fromPreference(manga?.readingMode?.toInt()) }
|
||||||
|
|
||||||
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
AdaptiveSheet(onDismissRequest = onDismissRequest) {
|
||||||
DialogContent(
|
DialogContent(
|
||||||
readingMode = readingMode,
|
readingMode = readingMode,
|
||||||
onChangeReadingMode = {
|
onChangeReadingMode = {
|
||||||
screenModel.onChangeReadingMode(it)
|
viewModel.onChangeReadingMode(it)
|
||||||
onChange(it.stringRes)
|
onChange(it.stringRes)
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import androidx.core.graphics.blue
|
|||||||
import androidx.core.graphics.green
|
import androidx.core.graphics.green
|
||||||
import androidx.core.graphics.red
|
import androidx.core.graphics.red
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences.Companion.ColorFilterMode
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences.Companion.ColorFilterMode
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsViewModel
|
||||||
import tachiyomi.core.common.preference.getAndSet
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
@@ -21,11 +21,11 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) {
|
internal fun ColumnScope.ColorFilterPage(viewModel: ReaderSettingsViewModel) {
|
||||||
val customBrightness by screenModel.preferences.customBrightness.collectAsState()
|
val customBrightness by viewModel.preferences.customBrightness.collectAsState()
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_custom_brightness),
|
label = stringResource(MR.strings.pref_custom_brightness),
|
||||||
pref = screenModel.preferences.customBrightness,
|
pref = viewModel.preferences.customBrightness,
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -35,31 +35,31 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
* 0 sets system brightness and hides the overlay.
|
* 0 sets system brightness and hides the overlay.
|
||||||
*/
|
*/
|
||||||
if (customBrightness) {
|
if (customBrightness) {
|
||||||
val customBrightnessValue by screenModel.preferences.customBrightnessValue.collectAsState()
|
val customBrightnessValue by viewModel.preferences.customBrightnessValue.collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = customBrightnessValue,
|
value = customBrightnessValue,
|
||||||
valueRange = -75..100,
|
valueRange = -75..100,
|
||||||
steps = 0,
|
steps = 0,
|
||||||
label = stringResource(MR.strings.pref_custom_brightness),
|
label = stringResource(MR.strings.pref_custom_brightness),
|
||||||
onChange = { screenModel.preferences.customBrightnessValue.set(it) },
|
onChange = { viewModel.preferences.customBrightnessValue.set(it) },
|
||||||
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val colorFilter by screenModel.preferences.colorFilter.collectAsState()
|
val colorFilter by viewModel.preferences.colorFilter.collectAsState()
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_custom_color_filter),
|
label = stringResource(MR.strings.pref_custom_color_filter),
|
||||||
pref = screenModel.preferences.colorFilter,
|
pref = viewModel.preferences.colorFilter,
|
||||||
)
|
)
|
||||||
if (colorFilter) {
|
if (colorFilter) {
|
||||||
val colorFilterValue by screenModel.preferences.colorFilterValue.collectAsState()
|
val colorFilterValue by viewModel.preferences.colorFilterValue.collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = colorFilterValue.red,
|
value = colorFilterValue.red,
|
||||||
valueRange = 0..255,
|
valueRange = 0..255,
|
||||||
steps = 0,
|
steps = 0,
|
||||||
label = stringResource(MR.strings.color_filter_r_value),
|
label = stringResource(MR.strings.color_filter_r_value),
|
||||||
onChange = { newRValue ->
|
onChange = { newRValue ->
|
||||||
screenModel.preferences.colorFilterValue.getAndSet {
|
viewModel.preferences.colorFilterValue.getAndSet {
|
||||||
getColorValue(it, newRValue, RED_MASK, 16)
|
getColorValue(it, newRValue, RED_MASK, 16)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -71,7 +71,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
steps = 0,
|
steps = 0,
|
||||||
label = stringResource(MR.strings.color_filter_g_value),
|
label = stringResource(MR.strings.color_filter_g_value),
|
||||||
onChange = { newGValue ->
|
onChange = { newGValue ->
|
||||||
screenModel.preferences.colorFilterValue.getAndSet {
|
viewModel.preferences.colorFilterValue.getAndSet {
|
||||||
getColorValue(it, newGValue, GREEN_MASK, 8)
|
getColorValue(it, newGValue, GREEN_MASK, 8)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -83,7 +83,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
steps = 0,
|
steps = 0,
|
||||||
label = stringResource(MR.strings.color_filter_b_value),
|
label = stringResource(MR.strings.color_filter_b_value),
|
||||||
onChange = { newBValue ->
|
onChange = { newBValue ->
|
||||||
screenModel.preferences.colorFilterValue.getAndSet {
|
viewModel.preferences.colorFilterValue.getAndSet {
|
||||||
getColorValue(it, newBValue, BLUE_MASK, 0)
|
getColorValue(it, newBValue, BLUE_MASK, 0)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -95,19 +95,19 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
steps = 0,
|
steps = 0,
|
||||||
label = stringResource(MR.strings.color_filter_a_value),
|
label = stringResource(MR.strings.color_filter_a_value),
|
||||||
onChange = { newAValue ->
|
onChange = { newAValue ->
|
||||||
screenModel.preferences.colorFilterValue.getAndSet {
|
viewModel.preferences.colorFilterValue.getAndSet {
|
||||||
getColorValue(it, newAValue, ALPHA_MASK, 24)
|
getColorValue(it, newAValue, ALPHA_MASK, 24)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
|
|
||||||
val colorFilterMode by screenModel.preferences.colorFilterMode.collectAsState()
|
val colorFilterMode by viewModel.preferences.colorFilterMode.collectAsState()
|
||||||
SettingsChipRow(MR.strings.pref_color_filter_mode) {
|
SettingsChipRow(MR.strings.pref_color_filter_mode) {
|
||||||
ColorFilterMode.mapIndexed { index, it ->
|
ColorFilterMode.mapIndexed { index, it ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = colorFilterMode == index,
|
selected = colorFilterMode == index,
|
||||||
onClick = { screenModel.preferences.colorFilterMode.set(index) },
|
onClick = { viewModel.preferences.colorFilterMode.set(index) },
|
||||||
label = { Text(stringResource(it.first)) },
|
label = { Text(stringResource(it.first)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -116,11 +116,11 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
|
|||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_grayscale),
|
label = stringResource(MR.strings.pref_grayscale),
|
||||||
pref = screenModel.preferences.grayscale,
|
pref = viewModel.preferences.grayscale,
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_inverted_colors),
|
label = stringResource(MR.strings.pref_inverted_colors),
|
||||||
pref = screenModel.preferences.invertedColors,
|
pref = viewModel.preferences.invertedColors,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsViewModel
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -33,25 +33,25 @@ private val flashColors = listOf(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
internal fun ColumnScope.GeneralPage(viewModel: ReaderSettingsViewModel) {
|
||||||
val readerTheme by screenModel.preferences.readerTheme.collectAsState()
|
val readerTheme by viewModel.preferences.readerTheme.collectAsState()
|
||||||
|
|
||||||
val flashPageState by screenModel.preferences.flashOnPageChange.collectAsState()
|
val flashPageState by viewModel.preferences.flashOnPageChange.collectAsState()
|
||||||
|
|
||||||
val flashMillisPref = screenModel.preferences.flashDurationMillis
|
val flashMillisPref = viewModel.preferences.flashDurationMillis
|
||||||
val flashMillis by flashMillisPref.collectAsState()
|
val flashMillis by flashMillisPref.collectAsState()
|
||||||
|
|
||||||
val flashIntervalPref = screenModel.preferences.flashPageInterval
|
val flashIntervalPref = viewModel.preferences.flashPageInterval
|
||||||
val flashInterval by flashIntervalPref.collectAsState()
|
val flashInterval by flashIntervalPref.collectAsState()
|
||||||
|
|
||||||
val flashColorPref = screenModel.preferences.flashColor
|
val flashColorPref = viewModel.preferences.flashColor
|
||||||
val flashColor by flashColorPref.collectAsState()
|
val flashColor by flashColorPref.collectAsState()
|
||||||
|
|
||||||
SettingsChipRow(MR.strings.pref_reader_theme) {
|
SettingsChipRow(MR.strings.pref_reader_theme) {
|
||||||
themes.map { (labelRes, value) ->
|
themes.map { (labelRes, value) ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = readerTheme == value,
|
selected = readerTheme == value,
|
||||||
onClick = { screenModel.preferences.readerTheme.set(value) },
|
onClick = { viewModel.preferences.readerTheme.set(value) },
|
||||||
label = { Text(stringResource(labelRes)) },
|
label = { Text(stringResource(labelRes)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -59,10 +59,10 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_show_page_number),
|
label = stringResource(MR.strings.pref_show_page_number),
|
||||||
pref = screenModel.preferences.showPageNumber,
|
pref = viewModel.preferences.showPageNumber,
|
||||||
)
|
)
|
||||||
|
|
||||||
val verticalNavigatorModes by screenModel.preferences.verticalNavigator.collectAsState()
|
val verticalNavigatorModes by viewModel.preferences.verticalNavigator.collectAsState()
|
||||||
|
|
||||||
SettingsChipRow(MR.strings.pref_vertical_navigator) {
|
SettingsChipRow(MR.strings.pref_vertical_navigator) {
|
||||||
ReadingMode.entries.filter { it != ReadingMode.DEFAULT }.forEach { mode ->
|
ReadingMode.entries.filter { it != ReadingMode.DEFAULT }.forEach { mode ->
|
||||||
@@ -74,7 +74,7 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||||||
} else {
|
} else {
|
||||||
verticalNavigatorModes + mode
|
verticalNavigatorModes + mode
|
||||||
}
|
}
|
||||||
screenModel.preferences.verticalNavigator.set(newModes)
|
viewModel.preferences.verticalNavigator.set(newModes)
|
||||||
},
|
},
|
||||||
label = { Text(stringResource(mode.stringRes)) },
|
label = { Text(stringResource(mode.stringRes)) },
|
||||||
)
|
)
|
||||||
@@ -82,12 +82,12 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (verticalNavigatorModes.isNotEmpty()) {
|
if (verticalNavigatorModes.isNotEmpty()) {
|
||||||
val verticalNavigatorHeightPref = screenModel.preferences.verticalNavigatorHeight
|
val verticalNavigatorHeightPref = viewModel.preferences.verticalNavigatorHeight
|
||||||
val verticalNavigatorHeight by verticalNavigatorHeightPref.collectAsState()
|
val verticalNavigatorHeight by verticalNavigatorHeightPref.collectAsState()
|
||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_webtoon_vertical_navigator_on_left),
|
label = stringResource(MR.strings.pref_webtoon_vertical_navigator_on_left),
|
||||||
pref = screenModel.preferences.verticalNavigatorOnLeft,
|
pref = viewModel.preferences.verticalNavigatorOnLeft,
|
||||||
)
|
)
|
||||||
|
|
||||||
SliderItem(
|
SliderItem(
|
||||||
@@ -101,40 +101,40 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
|
|||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_fullscreen),
|
label = stringResource(MR.strings.pref_fullscreen),
|
||||||
pref = screenModel.preferences.fullscreen,
|
pref = viewModel.preferences.fullscreen,
|
||||||
)
|
)
|
||||||
|
|
||||||
val isFullscreen by screenModel.preferences.fullscreen.collectAsState()
|
val isFullscreen by viewModel.preferences.fullscreen.collectAsState()
|
||||||
if (LocalActivity.current?.hasDisplayCutout() == true && isFullscreen) {
|
if (LocalActivity.current?.hasDisplayCutout() == true && isFullscreen) {
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_cutout_short),
|
label = stringResource(MR.strings.pref_cutout_short),
|
||||||
pref = screenModel.preferences.drawUnderCutout,
|
pref = viewModel.preferences.drawUnderCutout,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_keep_screen_on),
|
label = stringResource(MR.strings.pref_keep_screen_on),
|
||||||
pref = screenModel.preferences.keepScreenOn,
|
pref = viewModel.preferences.keepScreenOn,
|
||||||
)
|
)
|
||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_read_with_long_tap),
|
label = stringResource(MR.strings.pref_read_with_long_tap),
|
||||||
pref = screenModel.preferences.readWithLongTap,
|
pref = viewModel.preferences.readWithLongTap,
|
||||||
)
|
)
|
||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_always_show_chapter_transition),
|
label = stringResource(MR.strings.pref_always_show_chapter_transition),
|
||||||
pref = screenModel.preferences.alwaysShowChapterTransition,
|
pref = viewModel.preferences.alwaysShowChapterTransition,
|
||||||
)
|
)
|
||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_page_transitions),
|
label = stringResource(MR.strings.pref_page_transitions),
|
||||||
pref = screenModel.preferences.pageTransitions,
|
pref = viewModel.preferences.pageTransitions,
|
||||||
)
|
)
|
||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_flash_page),
|
label = stringResource(MR.strings.pref_flash_page),
|
||||||
pref = screenModel.preferences.flashOnPageChange,
|
pref = viewModel.preferences.flashOnPageChange,
|
||||||
)
|
)
|
||||||
if (flashPageState) {
|
if (flashPageState) {
|
||||||
SliderItem(
|
SliderItem(
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import androidx.compose.ui.platform.LocalView
|
|||||||
import androidx.compose.ui.window.DialogWindowProvider
|
import androidx.compose.ui.window.DialogWindowProvider
|
||||||
import eu.kanade.presentation.components.TabbedDialog
|
import eu.kanade.presentation.components.TabbedDialog
|
||||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsViewModel
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ fun ReaderSettingsDialog(
|
|||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onShowMenus: () -> Unit,
|
onShowMenus: () -> Unit,
|
||||||
onHideMenus: () -> Unit,
|
onHideMenus: () -> Unit,
|
||||||
screenModel: ReaderSettingsScreenModel,
|
viewModel: ReaderSettingsViewModel,
|
||||||
) {
|
) {
|
||||||
val tabTitles = listOf(
|
val tabTitles = listOf(
|
||||||
stringResource(MR.strings.pref_category_reading_mode),
|
stringResource(MR.strings.pref_category_reading_mode),
|
||||||
@@ -60,9 +60,9 @@ fun ReaderSettingsDialog(
|
|||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
when (page) {
|
when (page) {
|
||||||
0 -> ReadingModePage(screenModel)
|
0 -> ReadingModePage(viewModel)
|
||||||
1 -> GeneralPage(screenModel)
|
1 -> GeneralPage(viewModel)
|
||||||
2 -> ColorFilterPage(screenModel)
|
2 -> ColorFilterPage(viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import eu.kanade.domain.manga.model.readerOrientation
|
|||||||
import eu.kanade.domain.manga.model.readingMode
|
import eu.kanade.domain.manga.model.readingMode
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsViewModel
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -25,16 +25,16 @@ import tachiyomi.presentation.core.util.collectAsState
|
|||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) {
|
internal fun ColumnScope.ReadingModePage(viewModel: ReaderSettingsViewModel) {
|
||||||
HeadingItem(MR.strings.pref_category_for_this_series)
|
HeadingItem(MR.strings.pref_category_for_this_series)
|
||||||
val manga by screenModel.mangaFlow.collectAsState()
|
val manga by viewModel.mangaFlow.collectAsState()
|
||||||
|
|
||||||
val readingMode = remember(manga) { ReadingMode.fromPreference(manga?.readingMode?.toInt()) }
|
val readingMode = remember(manga) { ReadingMode.fromPreference(manga?.readingMode?.toInt()) }
|
||||||
SettingsChipRow(MR.strings.pref_category_reading_mode) {
|
SettingsChipRow(MR.strings.pref_category_reading_mode) {
|
||||||
ReadingMode.entries.map {
|
ReadingMode.entries.map {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = it == readingMode,
|
selected = it == readingMode,
|
||||||
onClick = { screenModel.onChangeReadingMode(it) },
|
onClick = { viewModel.onChangeReadingMode(it) },
|
||||||
label = { Text(stringResource(it.stringRes)) },
|
label = { Text(stringResource(it.stringRes)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -45,50 +45,50 @@ internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel)
|
|||||||
ReaderOrientation.entries.map {
|
ReaderOrientation.entries.map {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = it == orientation,
|
selected = it == orientation,
|
||||||
onClick = { screenModel.onChangeOrientation(it) },
|
onClick = { viewModel.onChangeOrientation(it) },
|
||||||
label = { Text(stringResource(it.stringRes)) },
|
label = { Text(stringResource(it.stringRes)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewer by screenModel.viewerFlow.collectAsState()
|
val viewer by viewModel.viewerFlow.collectAsState()
|
||||||
if (viewer is WebtoonViewer) {
|
if (viewer is WebtoonViewer) {
|
||||||
WebtoonViewerSettings(screenModel)
|
WebtoonViewerSettings(viewModel)
|
||||||
} else {
|
} else {
|
||||||
PagerViewerSettings(screenModel)
|
PagerViewerSettings(viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenModel) {
|
private fun ColumnScope.PagerViewerSettings(viewModel: ReaderSettingsViewModel) {
|
||||||
HeadingItem(MR.strings.pager_viewer)
|
HeadingItem(MR.strings.pager_viewer)
|
||||||
|
|
||||||
val navigationModePager by screenModel.preferences.navigationModePager.collectAsState()
|
val navigationModePager by viewModel.preferences.navigationModePager.collectAsState()
|
||||||
val pagerNavInverted by screenModel.preferences.pagerNavInverted.collectAsState()
|
val pagerNavInverted by viewModel.preferences.pagerNavInverted.collectAsState()
|
||||||
TapZonesItems(
|
TapZonesItems(
|
||||||
selected = navigationModePager,
|
selected = navigationModePager,
|
||||||
onSelect = screenModel.preferences.navigationModePager::set,
|
onSelect = viewModel.preferences.navigationModePager::set,
|
||||||
invertMode = pagerNavInverted,
|
invertMode = pagerNavInverted,
|
||||||
onSelectInvertMode = screenModel.preferences.pagerNavInverted::set,
|
onSelectInvertMode = viewModel.preferences.pagerNavInverted::set,
|
||||||
)
|
)
|
||||||
|
|
||||||
val imageScaleType by screenModel.preferences.imageScaleType.collectAsState()
|
val imageScaleType by viewModel.preferences.imageScaleType.collectAsState()
|
||||||
SettingsChipRow(MR.strings.pref_image_scale_type) {
|
SettingsChipRow(MR.strings.pref_image_scale_type) {
|
||||||
ReaderPreferences.ImageScaleType.mapIndexed { index, it ->
|
ReaderPreferences.ImageScaleType.mapIndexed { index, it ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = imageScaleType == index + 1,
|
selected = imageScaleType == index + 1,
|
||||||
onClick = { screenModel.preferences.imageScaleType.set(index + 1) },
|
onClick = { viewModel.preferences.imageScaleType.set(index + 1) },
|
||||||
label = { Text(stringResource(it)) },
|
label = { Text(stringResource(it)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val zoomStart by screenModel.preferences.zoomStart.collectAsState()
|
val zoomStart by viewModel.preferences.zoomStart.collectAsState()
|
||||||
SettingsChipRow(MR.strings.pref_zoom_start) {
|
SettingsChipRow(MR.strings.pref_zoom_start) {
|
||||||
ReaderPreferences.ZoomStart.mapIndexed { index, it ->
|
ReaderPreferences.ZoomStart.mapIndexed { index, it ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = zoomStart == index + 1,
|
selected = zoomStart == index + 1,
|
||||||
onClick = { screenModel.preferences.zoomStart.set(index + 1) },
|
onClick = { viewModel.preferences.zoomStart.set(index + 1) },
|
||||||
label = { Text(stringResource(it)) },
|
label = { Text(stringResource(it)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -96,111 +96,111 @@ private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenMod
|
|||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_crop_borders),
|
label = stringResource(MR.strings.pref_crop_borders),
|
||||||
pref = screenModel.preferences.cropBorders,
|
pref = viewModel.preferences.cropBorders,
|
||||||
)
|
)
|
||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_landscape_zoom),
|
label = stringResource(MR.strings.pref_landscape_zoom),
|
||||||
pref = screenModel.preferences.landscapeZoom,
|
pref = viewModel.preferences.landscapeZoom,
|
||||||
)
|
)
|
||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_navigate_pan),
|
label = stringResource(MR.strings.pref_navigate_pan),
|
||||||
pref = screenModel.preferences.navigateToPan,
|
pref = viewModel.preferences.navigateToPan,
|
||||||
)
|
)
|
||||||
|
|
||||||
val dualPageSplitPaged by screenModel.preferences.dualPageSplitPaged.collectAsState()
|
val dualPageSplitPaged by viewModel.preferences.dualPageSplitPaged.collectAsState()
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_dual_page_split),
|
label = stringResource(MR.strings.pref_dual_page_split),
|
||||||
pref = screenModel.preferences.dualPageSplitPaged,
|
pref = viewModel.preferences.dualPageSplitPaged,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (dualPageSplitPaged) {
|
if (dualPageSplitPaged) {
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_dual_page_invert),
|
label = stringResource(MR.strings.pref_dual_page_invert),
|
||||||
pref = screenModel.preferences.dualPageInvertPaged,
|
pref = viewModel.preferences.dualPageInvertPaged,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val dualPageRotateToFit by screenModel.preferences.dualPageRotateToFit.collectAsState()
|
val dualPageRotateToFit by viewModel.preferences.dualPageRotateToFit.collectAsState()
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_page_rotate),
|
label = stringResource(MR.strings.pref_page_rotate),
|
||||||
pref = screenModel.preferences.dualPageRotateToFit,
|
pref = viewModel.preferences.dualPageRotateToFit,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (dualPageRotateToFit) {
|
if (dualPageRotateToFit) {
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_page_rotate_invert),
|
label = stringResource(MR.strings.pref_page_rotate_invert),
|
||||||
pref = screenModel.preferences.dualPageRotateToFitInvert,
|
pref = viewModel.preferences.dualPageRotateToFitInvert,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenModel) {
|
private fun ColumnScope.WebtoonViewerSettings(viewModel: ReaderSettingsViewModel) {
|
||||||
val numberFormat = remember { NumberFormat.getPercentInstance() }
|
val numberFormat = remember { NumberFormat.getPercentInstance() }
|
||||||
|
|
||||||
HeadingItem(MR.strings.webtoon_viewer)
|
HeadingItem(MR.strings.webtoon_viewer)
|
||||||
|
|
||||||
val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon.collectAsState()
|
val navigationModeWebtoon by viewModel.preferences.navigationModeWebtoon.collectAsState()
|
||||||
val webtoonNavInverted by screenModel.preferences.webtoonNavInverted.collectAsState()
|
val webtoonNavInverted by viewModel.preferences.webtoonNavInverted.collectAsState()
|
||||||
TapZonesItems(
|
TapZonesItems(
|
||||||
selected = navigationModeWebtoon,
|
selected = navigationModeWebtoon,
|
||||||
onSelect = screenModel.preferences.navigationModeWebtoon::set,
|
onSelect = viewModel.preferences.navigationModeWebtoon::set,
|
||||||
invertMode = webtoonNavInverted,
|
invertMode = webtoonNavInverted,
|
||||||
onSelectInvertMode = screenModel.preferences.webtoonNavInverted::set,
|
onSelectInvertMode = viewModel.preferences.webtoonNavInverted::set,
|
||||||
)
|
)
|
||||||
|
|
||||||
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding.collectAsState()
|
val webtoonSidePadding by viewModel.preferences.webtoonSidePadding.collectAsState()
|
||||||
SliderItem(
|
SliderItem(
|
||||||
value = webtoonSidePadding,
|
value = webtoonSidePadding,
|
||||||
valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX },
|
valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX },
|
||||||
label = stringResource(MR.strings.pref_webtoon_side_padding),
|
label = stringResource(MR.strings.pref_webtoon_side_padding),
|
||||||
valueString = numberFormat.format(webtoonSidePadding / 100f),
|
valueString = numberFormat.format(webtoonSidePadding / 100f),
|
||||||
onChange = {
|
onChange = {
|
||||||
screenModel.preferences.webtoonSidePadding.set(it)
|
viewModel.preferences.webtoonSidePadding.set(it)
|
||||||
},
|
},
|
||||||
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
)
|
)
|
||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_crop_borders),
|
label = stringResource(MR.strings.pref_crop_borders),
|
||||||
pref = screenModel.preferences.cropBordersWebtoon,
|
pref = viewModel.preferences.cropBordersWebtoon,
|
||||||
)
|
)
|
||||||
|
|
||||||
val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon.collectAsState()
|
val dualPageSplitWebtoon by viewModel.preferences.dualPageSplitWebtoon.collectAsState()
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_dual_page_split),
|
label = stringResource(MR.strings.pref_dual_page_split),
|
||||||
pref = screenModel.preferences.dualPageSplitWebtoon,
|
pref = viewModel.preferences.dualPageSplitWebtoon,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (dualPageSplitWebtoon) {
|
if (dualPageSplitWebtoon) {
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_dual_page_invert),
|
label = stringResource(MR.strings.pref_dual_page_invert),
|
||||||
pref = screenModel.preferences.dualPageInvertWebtoon,
|
pref = viewModel.preferences.dualPageInvertWebtoon,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val dualPageRotateToFitWebtoon by screenModel.preferences.dualPageRotateToFitWebtoon.collectAsState()
|
val dualPageRotateToFitWebtoon by viewModel.preferences.dualPageRotateToFitWebtoon.collectAsState()
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_page_rotate),
|
label = stringResource(MR.strings.pref_page_rotate),
|
||||||
pref = screenModel.preferences.dualPageRotateToFitWebtoon,
|
pref = viewModel.preferences.dualPageRotateToFitWebtoon,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (dualPageRotateToFitWebtoon) {
|
if (dualPageRotateToFitWebtoon) {
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_page_rotate_invert),
|
label = stringResource(MR.strings.pref_page_rotate_invert),
|
||||||
pref = screenModel.preferences.dualPageRotateToFitInvertWebtoon,
|
pref = viewModel.preferences.dualPageRotateToFitInvertWebtoon,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_double_tap_zoom),
|
label = stringResource(MR.strings.pref_double_tap_zoom),
|
||||||
pref = screenModel.preferences.webtoonDoubleTapZoomEnabled,
|
pref = viewModel.preferences.webtoonDoubleTapZoomEnabled,
|
||||||
)
|
)
|
||||||
CheckboxItem(
|
CheckboxItem(
|
||||||
label = stringResource(MR.strings.pref_webtoon_disable_zoom_out),
|
label = stringResource(MR.strings.pref_webtoon_disable_zoom_out),
|
||||||
pref = screenModel.preferences.webtoonDisableZoomOut,
|
pref = viewModel.preferences.webtoonDisableZoomOut,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import eu.kanade.presentation.components.TabbedDialog
|
import eu.kanade.presentation.components.TabbedDialog
|
||||||
import eu.kanade.presentation.components.TabbedDialogPaddings
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.updates.UpdatesSettingsViewModel
|
||||||
import tachiyomi.core.common.preference.getAndSet
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
import tachiyomi.domain.updates.service.UpdatesPreferences
|
import tachiyomi.domain.updates.service.UpdatesPreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -32,7 +32,7 @@ import tachiyomi.presentation.core.util.collectAsState
|
|||||||
@Composable
|
@Composable
|
||||||
fun UpdatesFilterDialog(
|
fun UpdatesFilterDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
screenModel: UpdatesSettingsScreenModel,
|
viewModel: UpdatesSettingsViewModel,
|
||||||
) {
|
) {
|
||||||
TabbedDialog(
|
TabbedDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
@@ -45,48 +45,48 @@ fun UpdatesFilterDialog(
|
|||||||
.padding(vertical = TabbedDialogPaddings.Vertical)
|
.padding(vertical = TabbedDialogPaddings.Vertical)
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
FilterSheet(screenModel = screenModel)
|
FilterSheet(viewModel = viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.FilterSheet(
|
private fun ColumnScope.FilterSheet(
|
||||||
screenModel: UpdatesSettingsScreenModel,
|
viewModel: UpdatesSettingsViewModel,
|
||||||
) {
|
) {
|
||||||
val filterDownloaded by screenModel.updatesPreferences.filterDownloaded.collectAsState()
|
val filterDownloaded by viewModel.updatesPreferences.filterDownloaded.collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(MR.strings.label_downloaded),
|
label = stringResource(MR.strings.label_downloaded),
|
||||||
state = filterDownloaded,
|
state = filterDownloaded,
|
||||||
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterDownloaded) },
|
onClick = { viewModel.toggleFilter(UpdatesPreferences::filterDownloaded) },
|
||||||
)
|
)
|
||||||
|
|
||||||
val filterUnread by screenModel.updatesPreferences.filterUnread.collectAsState()
|
val filterUnread by viewModel.updatesPreferences.filterUnread.collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(MR.strings.action_filter_unread),
|
label = stringResource(MR.strings.action_filter_unread),
|
||||||
state = filterUnread,
|
state = filterUnread,
|
||||||
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterUnread) },
|
onClick = { viewModel.toggleFilter(UpdatesPreferences::filterUnread) },
|
||||||
)
|
)
|
||||||
|
|
||||||
val filterStarted by screenModel.updatesPreferences.filterStarted.collectAsState()
|
val filterStarted by viewModel.updatesPreferences.filterStarted.collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(MR.strings.label_started),
|
label = stringResource(MR.strings.label_started),
|
||||||
state = filterStarted,
|
state = filterStarted,
|
||||||
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterStarted) },
|
onClick = { viewModel.toggleFilter(UpdatesPreferences::filterStarted) },
|
||||||
)
|
)
|
||||||
|
|
||||||
val filterBookmarked by screenModel.updatesPreferences.filterBookmarked.collectAsState()
|
val filterBookmarked by viewModel.updatesPreferences.filterBookmarked.collectAsState()
|
||||||
TriStateItem(
|
TriStateItem(
|
||||||
label = stringResource(MR.strings.action_filter_bookmarked),
|
label = stringResource(MR.strings.action_filter_bookmarked),
|
||||||
state = filterBookmarked,
|
state = filterBookmarked,
|
||||||
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterBookmarked) },
|
onClick = { viewModel.toggleFilter(UpdatesPreferences::filterBookmarked) },
|
||||||
)
|
)
|
||||||
|
|
||||||
HorizontalDivider(modifier = Modifier.padding(MaterialTheme.padding.small))
|
HorizontalDivider(modifier = Modifier.padding(MaterialTheme.padding.small))
|
||||||
|
|
||||||
val filterExcludedScanlators by screenModel.updatesPreferences.filterExcludedScanlators.collectAsState()
|
val filterExcludedScanlators by viewModel.updatesPreferences.filterExcludedScanlators.collectAsState()
|
||||||
|
|
||||||
fun toggleScanlatorFilter() = screenModel.updatesPreferences.filterExcludedScanlators.getAndSet { !it }
|
fun toggleScanlatorFilter() = viewModel.updatesPreferences.filterExcludedScanlators.getAndSet { !it }
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import eu.kanade.presentation.manga.components.ChapterDownloadAction
|
|||||||
import eu.kanade.presentation.manga.components.MangaBottomActionMenu
|
import eu.kanade.presentation.manga.components.MangaBottomActionMenu
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
import eu.kanade.tachiyomi.ui.updates.UpdatesItem
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel
|
import eu.kanade.tachiyomi.ui.updates.UpdatesViewModel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
@@ -45,7 +45,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpdateScreen(
|
fun UpdateScreen(
|
||||||
state: UpdatesScreenModel.State,
|
state: UpdatesViewModel.State,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
lastUpdated: Long,
|
lastUpdated: Long,
|
||||||
onClickCover: (UpdatesItem) -> Unit,
|
onClickCover: (UpdatesItem) -> Unit,
|
||||||
|
|||||||
@@ -8,20 +8,12 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
|
||||||
import cafe.adriel.voyager.core.model.ScreenModelStore
|
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.core.screen.ScreenKey
|
import cafe.adriel.voyager.core.screen.ScreenKey
|
||||||
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
import cafe.adriel.voyager.core.screen.uniqueScreenKey
|
||||||
import cafe.adriel.voyager.core.stack.StackEvent
|
import cafe.adriel.voyager.core.stack.StackEvent
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.transitions.ScreenTransitionContent
|
import cafe.adriel.voyager.transitions.ScreenTransitionContent
|
||||||
import kotlinx.coroutines.CoroutineName
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.plus
|
|
||||||
import soup.compose.material.motion.animation.materialSharedAxisX
|
import soup.compose.material.motion.animation.materialSharedAxisX
|
||||||
import soup.compose.material.motion.animation.rememberSlideDistance
|
import soup.compose.material.motion.animation.rememberSlideDistance
|
||||||
|
|
||||||
@@ -39,18 +31,6 @@ abstract class Screen : Screen {
|
|||||||
override val key: ScreenKey = uniqueScreenKey
|
override val key: ScreenKey = uniqueScreenKey
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A variant of ScreenModel.coroutineScope except with the IO dispatcher instead of the
|
|
||||||
* main dispatcher.
|
|
||||||
*/
|
|
||||||
val ScreenModel.ioCoroutineScope: CoroutineScope
|
|
||||||
get() = ScreenModelStore.getOrPutDependency(
|
|
||||||
screenModel = this,
|
|
||||||
name = "ScreenModelIoCoroutineScope",
|
|
||||||
factory = { key -> CoroutineScope(Dispatchers.IO + SupervisorJob()) + CoroutineName(key) },
|
|
||||||
onDispose = { scope -> scope.cancel() },
|
|
||||||
)
|
|
||||||
|
|
||||||
interface AssistContentScreen {
|
interface AssistContentScreen {
|
||||||
fun onProvideAssistUrl(): String?
|
fun onProvideAssistUrl(): String?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
import eu.kanade.presentation.components.TabbedScreen
|
import eu.kanade.presentation.components.TabbedScreen
|
||||||
import eu.kanade.presentation.util.Tab
|
import eu.kanade.presentation.util.Tab
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsViewModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.extensionsTab
|
import eu.kanade.tachiyomi.ui.browse.extension.extensionsTab
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.migrateSourceTab
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.migrateSourceTab
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||||
@@ -58,12 +58,12 @@ data object BrowseTab : Tab {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
// Hoisted for extensions tab's search bar
|
// Hoisted for extensions tab's search bar
|
||||||
val extensionsScreenModel = rememberScreenModel { ExtensionsScreenModel() }
|
val extensionsViewModel = viewModel<ExtensionsViewModel>()
|
||||||
val extensionsState by extensionsScreenModel.state.collectAsState()
|
val extensionsState by extensionsViewModel.state.collectAsState()
|
||||||
|
|
||||||
val tabs = listOf(
|
val tabs = listOf(
|
||||||
sourcesTab(),
|
sourcesTab(),
|
||||||
extensionsTab(extensionsScreenModel),
|
extensionsTab(extensionsViewModel),
|
||||||
migrateSourceTab(),
|
migrateSourceTab(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ data object BrowseTab : Tab {
|
|||||||
tabs = tabs,
|
tabs = tabs,
|
||||||
state = state,
|
state = state,
|
||||||
searchQuery = extensionsState.searchQuery,
|
searchQuery = extensionsState.searchQuery,
|
||||||
onChangeSearchQuery = extensionsScreenModel::search,
|
onChangeSearchQuery = extensionsViewModel::search,
|
||||||
)
|
)
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
switchToExtensionTabChannel.receiveAsFlow()
|
switchToExtensionTabChannel.receiveAsFlow()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.ExtensionFilterScreen
|
import eu.kanade.presentation.browse.ExtensionFilterScreen
|
||||||
@@ -21,8 +21,8 @@ class ExtensionFilterScreen : Screen() {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { ExtensionFilterScreenModel() }
|
val viewModel = viewModel<ExtensionFilterViewModel>()
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
if (state is ExtensionFilterState.Loading) {
|
if (state is ExtensionFilterState.Loading) {
|
||||||
LoadingScreen()
|
LoadingScreen()
|
||||||
@@ -34,11 +34,11 @@ class ExtensionFilterScreen : Screen() {
|
|||||||
ExtensionFilterScreen(
|
ExtensionFilterScreen(
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
state = successState,
|
state = successState,
|
||||||
onClickToggle = screenModel::toggle,
|
onClickToggle = viewModel::toggle,
|
||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
screenModel.events.collectLatest {
|
viewModel.events.collectLatest {
|
||||||
when (it) {
|
when (it) {
|
||||||
ExtensionFilterEvent.FailedFetchingLanguages -> {
|
ExtensionFilterEvent.FailedFetchingLanguages -> {
|
||||||
context.stringResource(MR.strings.internal_error)
|
context.stringResource(MR.strings.internal_error)
|
||||||
|
|||||||
+5
-5
@@ -1,8 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.extension
|
package eu.kanade.tachiyomi.ui.browse.extension
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
@@ -15,21 +14,22 @@ import kotlinx.coroutines.flow.receiveAsFlow
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ExtensionFilterScreenModel(
|
class ExtensionFilterViewModel(
|
||||||
private val preferences: SourcePreferences = Injekt.get(),
|
private val preferences: SourcePreferences = Injekt.get(),
|
||||||
private val getExtensionLanguages: GetExtensionLanguages = Injekt.get(),
|
private val getExtensionLanguages: GetExtensionLanguages = Injekt.get(),
|
||||||
private val toggleLanguage: ToggleLanguage = Injekt.get(),
|
private val toggleLanguage: ToggleLanguage = Injekt.get(),
|
||||||
) : StateScreenModel<ExtensionFilterState>(ExtensionFilterState.Loading) {
|
) : StateViewModel<ExtensionFilterState>(ExtensionFilterState.Loading) {
|
||||||
|
|
||||||
private val _events: Channel<ExtensionFilterEvent> = Channel()
|
private val _events: Channel<ExtensionFilterEvent> = Channel()
|
||||||
val events: Flow<ExtensionFilterEvent> = _events.receiveAsFlow()
|
val events: Flow<ExtensionFilterEvent> = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
combine(
|
combine(
|
||||||
getExtensionLanguages.subscribe(),
|
getExtensionLanguages.subscribe(),
|
||||||
preferences.enabledLanguages.changes(),
|
preferences.enabledLanguages.changes(),
|
||||||
@@ -26,12 +26,12 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun extensionsTab(
|
fun extensionsTab(
|
||||||
extensionsScreenModel: ExtensionsScreenModel,
|
extensionsViewModel: ExtensionsViewModel,
|
||||||
): TabContent {
|
): TabContent {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val state by extensionsScreenModel.state.collectAsState()
|
val state by extensionsViewModel.state.collectAsState()
|
||||||
var privateExtensionToUninstall by remember { mutableStateOf<Extension?>(null) }
|
var privateExtensionToUninstall by remember { mutableStateOf<Extension?>(null) }
|
||||||
|
|
||||||
return TabContent(
|
return TabContent(
|
||||||
@@ -50,7 +50,7 @@ fun extensionsTab(
|
|||||||
),
|
),
|
||||||
content = { contentPadding, _ ->
|
content = { contentPadding, _ ->
|
||||||
BackHandler(enabled = state.searchQuery != null) {
|
BackHandler(enabled = state.searchQuery != null) {
|
||||||
extensionsScreenModel.search(null)
|
extensionsViewModel.search(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtensionScreen(
|
ExtensionScreen(
|
||||||
@@ -59,18 +59,18 @@ fun extensionsTab(
|
|||||||
searchQuery = state.searchQuery,
|
searchQuery = state.searchQuery,
|
||||||
onLongClickItem = { extension ->
|
onLongClickItem = { extension ->
|
||||||
when (extension) {
|
when (extension) {
|
||||||
is Extension.Available -> extensionsScreenModel.installExtension(extension)
|
is Extension.Available -> extensionsViewModel.installExtension(extension)
|
||||||
else -> {
|
else -> {
|
||||||
if (context.isPackageInstalled(extension.pkgName)) {
|
if (context.isPackageInstalled(extension.pkgName)) {
|
||||||
extensionsScreenModel.uninstallExtension(extension)
|
extensionsViewModel.uninstallExtension(extension)
|
||||||
} else {
|
} else {
|
||||||
privateExtensionToUninstall = extension
|
privateExtensionToUninstall = extension
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onClickItemCancel = extensionsScreenModel::cancelInstallUpdateExtension,
|
onClickItemCancel = extensionsViewModel::cancelInstallUpdateExtension,
|
||||||
onClickUpdateAll = extensionsScreenModel::updateAllExtensions,
|
onClickUpdateAll = extensionsViewModel::updateAllExtensions,
|
||||||
onOpenWebView = { extension ->
|
onOpenWebView = { extension ->
|
||||||
extension.sources.getOrNull(0)?.let {
|
extension.sources.getOrNull(0)?.let {
|
||||||
navigator.push(
|
navigator.push(
|
||||||
@@ -82,19 +82,19 @@ fun extensionsTab(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onInstallExtension = extensionsScreenModel::installExtension,
|
onInstallExtension = extensionsViewModel::installExtension,
|
||||||
onOpenExtension = { navigator.push(ExtensionDetailsScreen(it.pkgName)) },
|
onOpenExtension = { navigator.push(ExtensionDetailsScreen(it.pkgName)) },
|
||||||
onTrustExtension = { extensionsScreenModel.trustExtension(it) },
|
onTrustExtension = { extensionsViewModel.trustExtension(it) },
|
||||||
onUninstallExtension = { extensionsScreenModel.uninstallExtension(it) },
|
onUninstallExtension = { extensionsViewModel.uninstallExtension(it) },
|
||||||
onUpdateExtension = extensionsScreenModel::updateExtension,
|
onUpdateExtension = extensionsViewModel::updateExtension,
|
||||||
onRefresh = extensionsScreenModel::findAvailableExtensions,
|
onRefresh = extensionsViewModel::findAvailableExtensions,
|
||||||
)
|
)
|
||||||
|
|
||||||
privateExtensionToUninstall?.let { extension ->
|
privateExtensionToUninstall?.let { extension ->
|
||||||
ExtensionUninstallConfirmation(
|
ExtensionUninstallConfirmation(
|
||||||
extensionName = extension.name,
|
extensionName = extension.name,
|
||||||
onClickConfirm = {
|
onClickConfirm = {
|
||||||
extensionsScreenModel.uninstallExtension(extension)
|
extensionsViewModel.uninstallExtension(extension)
|
||||||
},
|
},
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
privateExtensionToUninstall = null
|
privateExtensionToUninstall = null
|
||||||
|
|||||||
+13
-13
@@ -2,8 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.extension
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||||
@@ -28,18 +27,19 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.flow.takeWhile
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class ExtensionsScreenModel(
|
class ExtensionsViewModel(
|
||||||
preferences: SourcePreferences = Injekt.get(),
|
preferences: SourcePreferences = Injekt.get(),
|
||||||
basePreferences: BasePreferences = Injekt.get(),
|
basePreferences: BasePreferences = Injekt.get(),
|
||||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||||
private val getExtensions: GetExtensionsByType = Injekt.get(),
|
private val getExtensions: GetExtensionsByType = Injekt.get(),
|
||||||
) : StateScreenModel<ExtensionsScreenModel.State>(State()) {
|
) : StateViewModel<ExtensionsViewModel.State>(State()) {
|
||||||
|
|
||||||
private val currentDownloads = MutableStateFlow<Map<String, InstallStep>>(hashMapOf())
|
private val currentDownloads = MutableStateFlow<Map<String, InstallStep>>(hashMapOf())
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ class ExtensionsScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
combine(
|
combine(
|
||||||
state.map { it.searchQuery }
|
state.map { it.searchQuery }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
@@ -95,15 +95,15 @@ class ExtensionsScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launchIO { findAvailableExtensions() }
|
viewModelScope.launchIO { findAvailableExtensions() }
|
||||||
|
|
||||||
preferences.extensionUpdatesCount.changes()
|
preferences.extensionUpdatesCount.changes()
|
||||||
.onEach { mutableState.update { state -> state.copy(updates = it) } }
|
.onEach { mutableState.update { state -> state.copy(updates = it) } }
|
||||||
.launchIn(screenModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
basePreferences.extensionInstaller.changes()
|
basePreferences.extensionInstaller.changes()
|
||||||
.onEach { mutableState.update { state -> state.copy(installer = it) } }
|
.onEach { mutableState.update { state -> state.copy(installer = it) } }
|
||||||
.launchIn(screenModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchQueryPredicate(query: String): (Extension) -> Boolean {
|
fun searchQueryPredicate(query: String): (Extension) -> Boolean {
|
||||||
@@ -143,7 +143,7 @@ class ExtensionsScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateAllExtensions() {
|
fun updateAllExtensions() {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
state.value.items.values.flatten()
|
state.value.items.values.flatten()
|
||||||
.map { it.extension }
|
.map { it.extension }
|
||||||
.filterIsInstance<Extension.Installed>()
|
.filterIsInstance<Extension.Installed>()
|
||||||
@@ -153,13 +153,13 @@ class ExtensionsScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun installExtension(extension: Extension.Available) {
|
fun installExtension(extension: Extension.Available) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
extensionManager.installExtension(extension).collectToInstallUpdate(extension)
|
extensionManager.installExtension(extension).collectToInstallUpdate(extension)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateExtension(extension: Extension.Installed) {
|
fun updateExtension(extension: Extension.Installed) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
extensionManager.updateExtension(extension).collectToInstallUpdate(extension)
|
extensionManager.updateExtension(extension).collectToInstallUpdate(extension)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,7 +189,7 @@ class ExtensionsScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun findAvailableExtensions() {
|
fun findAvailableExtensions() {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
mutableState.update { it.copy(isRefreshing = true) }
|
mutableState.update { it.copy(isRefreshing = true) }
|
||||||
|
|
||||||
extensionManager.findAvailableExtensions()
|
extensionManager.findAvailableExtensions()
|
||||||
@@ -202,7 +202,7 @@ class ExtensionsScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun trustExtension(extension: Extension.Untrusted) {
|
fun trustExtension(extension: Extension.Untrusted) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
extensionManager.trust(extension)
|
extensionManager.trust(extension)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+16
-12
@@ -4,8 +4,8 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.ExtensionDetailsScreen
|
import eu.kanade.presentation.browse.ExtensionDetailsScreen
|
||||||
@@ -19,9 +19,13 @@ data class ExtensionDetailsScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val viewModel = viewModel<ExtensionDetailsViewModel>(
|
||||||
val screenModel = rememberScreenModel { ExtensionDetailsScreenModel(pkgName = pkgName, context = context) }
|
factory = ExtensionDetailsViewModel.Factory,
|
||||||
val state by screenModel.state.collectAsState()
|
extras = CreationExtras {
|
||||||
|
set(ExtensionDetailsViewModel.PKG_NAME_KEY, pkgName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
LoadingScreen()
|
LoadingScreen()
|
||||||
@@ -34,16 +38,16 @@ data class ExtensionDetailsScreen(
|
|||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
state = state,
|
state = state,
|
||||||
onClickSourcePreferences = { navigator.push(SourcePreferencesScreen(it)) },
|
onClickSourcePreferences = { navigator.push(SourcePreferencesScreen(it)) },
|
||||||
onClickEnableAll = { screenModel.toggleSources(true) },
|
onClickEnableAll = { viewModel.toggleSources(true) },
|
||||||
onClickDisableAll = { screenModel.toggleSources(false) },
|
onClickDisableAll = { viewModel.toggleSources(false) },
|
||||||
onClickClearCookies = screenModel::clearCookies,
|
onClickClearCookies = viewModel::clearCookies,
|
||||||
onClickUninstall = screenModel::uninstallExtension,
|
onClickUninstall = viewModel::uninstallExtension,
|
||||||
onClickSource = screenModel::toggleSource,
|
onClickSource = viewModel::toggleSource,
|
||||||
onClickIncognito = screenModel::toggleIncognito,
|
onClickIncognito = viewModel::toggleIncognito,
|
||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
screenModel.events.collectLatest { event ->
|
viewModel.events.collectLatest { event ->
|
||||||
if (event is ExtensionDetailsEvent.Uninstalled) {
|
if (event is ExtensionDetailsEvent.Uninstalled) {
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-5
@@ -1,9 +1,12 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.extension.details
|
package eu.kanade.tachiyomi.ui.browse.extension.details
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import eu.kanade.domain.extension.interactor.ExtensionSourceItem
|
import eu.kanade.domain.extension.interactor.ExtensionSourceItem
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||||
import eu.kanade.domain.source.interactor.ToggleIncognito
|
import eu.kanade.domain.source.interactor.ToggleIncognito
|
||||||
@@ -24,12 +27,13 @@ import kotlinx.coroutines.flow.receiveAsFlow
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ExtensionDetailsScreenModel(
|
class ExtensionDetailsViewModel(
|
||||||
pkgName: String,
|
pkgName: String,
|
||||||
context: Context,
|
context: Context,
|
||||||
private val network: NetworkHelper = Injekt.get(),
|
private val network: NetworkHelper = Injekt.get(),
|
||||||
@@ -38,13 +42,26 @@ class ExtensionDetailsScreenModel(
|
|||||||
private val toggleSource: ToggleSource = Injekt.get(),
|
private val toggleSource: ToggleSource = Injekt.get(),
|
||||||
private val toggleIncognito: ToggleIncognito = Injekt.get(),
|
private val toggleIncognito: ToggleIncognito = Injekt.get(),
|
||||||
private val preferences: SourcePreferences = Injekt.get(),
|
private val preferences: SourcePreferences = Injekt.get(),
|
||||||
) : StateScreenModel<ExtensionDetailsScreenModel.State>(State()) {
|
) : StateViewModel<ExtensionDetailsViewModel.State>(State()) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val PKG_NAME_KEY = CreationExtras.Key<String>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
ExtensionDetailsViewModel(
|
||||||
|
pkgName = get(PKG_NAME_KEY)!!,
|
||||||
|
context = Injekt.get<Application>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val _events: Channel<ExtensionDetailsEvent> = Channel()
|
private val _events: Channel<ExtensionDetailsEvent> = Channel()
|
||||||
val events: Flow<ExtensionDetailsEvent> = _events.receiveAsFlow()
|
val events: Flow<ExtensionDetailsEvent> = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
launch {
|
launch {
|
||||||
extensionManager.installedExtensionsFlow
|
extensionManager.installedExtensionsFlow
|
||||||
.map { it.firstOrNull { extension -> extension.pkgName == pkgName } }
|
.map { it.firstOrNull { extension -> extension.pkgName == pkgName } }
|
||||||
+15
-9
@@ -19,7 +19,8 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
@@ -47,9 +48,14 @@ data class MigrateMangaScreen(
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { MigrateMangaScreenModel(sourceId) }
|
val viewModel = viewModel<MigrateMangaViewModel>(
|
||||||
|
factory = MigrateMangaViewModel.Factory,
|
||||||
|
extras = CreationExtras {
|
||||||
|
set(MigrateMangaViewModel.SOURCE_ID_KEY, sourceId)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
LoadingScreen()
|
LoadingScreen()
|
||||||
@@ -57,7 +63,7 @@ data class MigrateMangaScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
BackHandler(enabled = state.selectionMode) {
|
BackHandler(enabled = state.selectionMode) {
|
||||||
screenModel.clearSelection()
|
viewModel.clearSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
@@ -68,7 +74,7 @@ data class MigrateMangaScreen(
|
|||||||
title = state.source!!.name,
|
title = state.source!!.name,
|
||||||
navigateUp = {
|
navigateUp = {
|
||||||
if (state.selectionMode) {
|
if (state.selectionMode) {
|
||||||
screenModel.clearSelection()
|
viewModel.clearSelection()
|
||||||
} else {
|
} else {
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
}
|
}
|
||||||
@@ -84,7 +90,7 @@ data class MigrateMangaScreen(
|
|||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
val selection = state.selection
|
val selection = state.selection
|
||||||
screenModel.clearSelection()
|
viewModel.clearSelection()
|
||||||
navigator.push(MigrationConfigScreen(selection))
|
navigator.push(MigrationConfigScreen(selection))
|
||||||
},
|
},
|
||||||
expanded = lazyListState.shouldExpandFAB(),
|
expanded = lazyListState.shouldExpandFAB(),
|
||||||
@@ -107,13 +113,13 @@ data class MigrateMangaScreen(
|
|||||||
lazyListState = lazyListState,
|
lazyListState = lazyListState,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
state = state,
|
state = state,
|
||||||
onClickItem = screenModel::toggleSelection,
|
onClickItem = viewModel::toggleSelection,
|
||||||
onClickCover = { navigator.push(MangaScreen(it.id)) },
|
onClickCover = { navigator.push(MangaScreen(it.id)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
screenModel.events.collectLatest { event ->
|
viewModel.events.collectLatest { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
MigrationMangaEvent.FailedFetchingFavorites -> {
|
MigrationMangaEvent.FailedFetchingFavorites -> {
|
||||||
context.toast(MR.strings.internal_error)
|
context.toast(MR.strings.internal_error)
|
||||||
@@ -127,7 +133,7 @@ data class MigrateMangaScreen(
|
|||||||
private fun MigrateMangaContent(
|
private fun MigrateMangaContent(
|
||||||
lazyListState: LazyListState,
|
lazyListState: LazyListState,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
state: MigrateMangaScreenModel.State,
|
state: MigrateMangaViewModel.State,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onClickCover: (Manga) -> Unit,
|
onClickCover: (Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|||||||
+20
-5
@@ -1,8 +1,10 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.manga
|
package eu.kanade.tachiyomi.ui.browse.migration.manga
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -14,6 +16,7 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import mihon.core.common.utils.mutate
|
import mihon.core.common.utils.mutate
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.manga.interactor.GetFavorites
|
import tachiyomi.domain.manga.interactor.GetFavorites
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
@@ -21,17 +24,29 @@ import tachiyomi.domain.source.service.SourceManager
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class MigrateMangaScreenModel(
|
class MigrateMangaViewModel(
|
||||||
private val sourceId: Long,
|
private val sourceId: Long,
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
private val getFavorites: GetFavorites = Injekt.get(),
|
private val getFavorites: GetFavorites = Injekt.get(),
|
||||||
) : StateScreenModel<MigrateMangaScreenModel.State>(State()) {
|
) : StateViewModel<MigrateMangaViewModel.State>(State()) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val SOURCE_ID_KEY = CreationExtras.Key<Long>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
MigrateMangaViewModel(
|
||||||
|
sourceId = get(SOURCE_ID_KEY)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val _events: Channel<MigrationMangaEvent> = Channel()
|
private val _events: Channel<MigrationMangaEvent> = Channel()
|
||||||
val events: Flow<MigrationMangaEvent> = _events.receiveAsFlow()
|
val events: Flow<MigrationMangaEvent> = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
mutableState.update { state ->
|
mutableState.update { state ->
|
||||||
state.copy(source = sourceManager.getOrStub(sourceId))
|
state.copy(source = sourceManager.getOrStub(sourceId))
|
||||||
}
|
}
|
||||||
+18
-12
@@ -3,12 +3,13 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.MigrateSearchScreen
|
import eu.kanade.presentation.browse.MigrateSearchScreen
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchViewModel
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import mihon.feature.migration.dialog.MigrateMangaDialog
|
import mihon.feature.migration.dialog.MigrateMangaDialog
|
||||||
import mihon.feature.migration.list.MigrationListScreen
|
import mihon.feature.migration.list.MigrationListScreen
|
||||||
@@ -19,18 +20,23 @@ class MigrateSearchScreen(private val mangaId: Long) : Screen() {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { MigrateSearchScreenModel(mangaId = mangaId) }
|
val viewModel = viewModel<MigrateSearchViewModel>(
|
||||||
val state by screenModel.state.collectAsState()
|
factory = MigrateSearchViewModel.Factory,
|
||||||
|
extras = CreationExtras {
|
||||||
|
set(MigrateSearchViewModel.MANGA_ID_KEY, mangaId)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
MigrateSearchScreen(
|
MigrateSearchScreen(
|
||||||
state = state,
|
state = state,
|
||||||
fromSourceId = state.from?.source,
|
fromSourceId = state.from?.source,
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
onChangeSearchQuery = viewModel::updateSearchQuery,
|
||||||
onSearch = { screenModel.search() },
|
onSearch = { viewModel.search() },
|
||||||
getManga = { screenModel.getManga(it) },
|
getManga = { viewModel.getManga(it) },
|
||||||
onChangeSearchFilter = screenModel::setSourceFilter,
|
onChangeSearchFilter = viewModel::setSourceFilter,
|
||||||
onToggleResults = screenModel::toggleFilterResults,
|
onToggleResults = viewModel::toggleFilterResults,
|
||||||
onClickSource = { navigator.push(MigrateSourceSearchScreen(state.from!!, it.id, state.searchQuery)) },
|
onClickSource = { navigator.push(MigrateSourceSearchScreen(state.from!!, it.id, state.searchQuery)) },
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
val migrateListScreen = navigator.items
|
val migrateListScreen = navigator.items
|
||||||
@@ -38,7 +44,7 @@ class MigrateSearchScreen(private val mangaId: Long) : Screen() {
|
|||||||
.lastOrNull()
|
.lastOrNull()
|
||||||
|
|
||||||
if (migrateListScreen == null) {
|
if (migrateListScreen == null) {
|
||||||
screenModel.setMigrateDialog(mangaId, it)
|
viewModel.setMigrateDialog(mangaId, it)
|
||||||
} else {
|
} else {
|
||||||
migrateListScreen.addMatchOverride(current = mangaId, target = it.id)
|
migrateListScreen.addMatchOverride(current = mangaId, target = it.id)
|
||||||
navigator.popUntil { screen -> screen is MigrationListScreen }
|
navigator.popUntil { screen -> screen is MigrationListScreen }
|
||||||
@@ -48,13 +54,13 @@ class MigrateSearchScreen(private val mangaId: Long) : Screen() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
when (val dialog = state.dialog) {
|
when (val dialog = state.dialog) {
|
||||||
is SearchScreenModel.Dialog.Migrate -> {
|
is SearchViewModel.Dialog.Migrate -> {
|
||||||
MigrateMangaDialog(
|
MigrateMangaDialog(
|
||||||
current = dialog.current,
|
current = dialog.current,
|
||||||
target = dialog.target,
|
target = dialog.target,
|
||||||
// Initiated from the context of [dialog.current] so we show [dialog.target].
|
// Initiated from the context of [dialog.current] so we show [dialog.target].
|
||||||
onClickTitle = { navigator.push(MangaScreen(dialog.target.id, true)) },
|
onClickTitle = { navigator.push(MangaScreen(dialog.target.id, true)) },
|
||||||
onDismissRequest = { screenModel.clearDialog() },
|
onDismissRequest = { viewModel.clearDialog() },
|
||||||
onComplete = {
|
onComplete = {
|
||||||
if (navigator.lastItem is MangaScreen) {
|
if (navigator.lastItem is MangaScreen) {
|
||||||
val lastItem = navigator.lastItem
|
val lastItem = navigator.lastItem
|
||||||
|
|||||||
+20
-5
@@ -1,10 +1,13 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.search
|
package eu.kanade.tachiyomi.ui.browse.migration.search
|
||||||
|
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchViewModel
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.domain.manga.interactor.GetManga
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
@@ -12,12 +15,24 @@ import tachiyomi.domain.source.service.SourceManager
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class MigrateSearchScreenModel(
|
class MigrateSearchViewModel(
|
||||||
val mangaId: Long,
|
val mangaId: Long,
|
||||||
getManga: GetManga = Injekt.get(),
|
getManga: GetManga = Injekt.get(),
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
private val sourcePreferences: SourcePreferences = Injekt.get(),
|
||||||
) : SearchScreenModel() {
|
) : SearchViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val MANGA_ID_KEY = CreationExtras.Key<Long>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
MigrateSearchViewModel(
|
||||||
|
mangaId = get(MANGA_ID_KEY)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val migrationSources by lazy { sourcePreferences.migrationSources.get() }
|
private val migrationSources by lazy { sourcePreferences.migrationSources.get() }
|
||||||
|
|
||||||
@@ -29,7 +44,7 @@ class MigrateSearchScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
val manga = getManga.await(mangaId)!!
|
val manga = getManga.await(mangaId)!!
|
||||||
mutableState.update {
|
mutableState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
+26
-19
@@ -17,7 +17,8 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.core.util.ifSourcesLoaded
|
import eu.kanade.core.util.ifSourcesLoaded
|
||||||
@@ -25,7 +26,7 @@ import eu.kanade.presentation.browse.BrowseSourceContent
|
|||||||
import eu.kanade.presentation.components.SearchToolbar
|
import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceViewModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterDialog
|
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterDialog
|
||||||
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
@@ -59,8 +60,14 @@ data class MigrateSourceSearchScreen(
|
|||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId, query) }
|
val viewModel = viewModel<BrowseSourceViewModel>(
|
||||||
val state by screenModel.state.collectAsState()
|
factory = BrowseSourceViewModel.Factory,
|
||||||
|
extras = CreationExtras {
|
||||||
|
set(BrowseSourceViewModel.SOURCE_ID_KEY, sourceId)
|
||||||
|
set(BrowseSourceViewModel.LISTING_QUERY_KEY, query)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
@@ -68,9 +75,9 @@ data class MigrateSourceSearchScreen(
|
|||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
SearchToolbar(
|
SearchToolbar(
|
||||||
searchQuery = state.toolbarQuery ?: "",
|
searchQuery = state.toolbarQuery ?: "",
|
||||||
onChangeSearchQuery = screenModel::setToolbarQuery,
|
onChangeSearchQuery = viewModel::setToolbarQuery,
|
||||||
onClickCloseSearch = navigator::pop,
|
onClickCloseSearch = navigator::pop,
|
||||||
onSearch = screenModel::search,
|
onSearch = viewModel::search,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -78,7 +85,7 @@ data class MigrateSourceSearchScreen(
|
|||||||
SmallExtendedFloatingActionButton(
|
SmallExtendedFloatingActionButton(
|
||||||
text = { Text(text = stringResource(MR.strings.action_filter)) },
|
text = { Text(text = stringResource(MR.strings.action_filter)) },
|
||||||
icon = { Icon(Icons.Outlined.FilterList, contentDescription = null) },
|
icon = { Icon(Icons.Outlined.FilterList, contentDescription = null) },
|
||||||
onClick = screenModel::openFilterSheet,
|
onClick = viewModel::openFilterSheet,
|
||||||
modifier = Modifier.animateFloatingActionButton(
|
modifier = Modifier.animateFloatingActionButton(
|
||||||
visible = state.filters.isNotEmpty(),
|
visible = state.filters.isNotEmpty(),
|
||||||
alignment = Alignment.BottomEnd,
|
alignment = Alignment.BottomEnd,
|
||||||
@@ -93,21 +100,21 @@ data class MigrateSourceSearchScreen(
|
|||||||
.lastOrNull()
|
.lastOrNull()
|
||||||
|
|
||||||
if (migrateListScreen == null) {
|
if (migrateListScreen == null) {
|
||||||
screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(target = it, current = currentManga))
|
viewModel.setDialog(BrowseSourceViewModel.Dialog.Migrate(target = it, current = currentManga))
|
||||||
} else {
|
} else {
|
||||||
migrateListScreen.addMatchOverride(current = currentManga.id, target = it.id)
|
migrateListScreen.addMatchOverride(current = currentManga.id, target = it.id)
|
||||||
navigator.popUntil { screen -> screen is MigrationListScreen }
|
navigator.popUntil { screen -> screen is MigrationListScreen }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BrowseSourceContent(
|
BrowseSourceContent(
|
||||||
source = screenModel.source,
|
source = viewModel.source,
|
||||||
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
|
mangaList = viewModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
|
||||||
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
columns = viewModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
||||||
displayMode = screenModel.displayMode,
|
displayMode = viewModel.displayMode,
|
||||||
snackbarHostState = snackbarHostState,
|
snackbarHostState = snackbarHostState,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
onWebViewClick = {
|
onWebViewClick = {
|
||||||
val source = screenModel.source as? HttpSource ?: return@BrowseSourceContent
|
val source = viewModel.source as? HttpSource ?: return@BrowseSourceContent
|
||||||
navigator.push(
|
navigator.push(
|
||||||
WebViewScreen(
|
WebViewScreen(
|
||||||
url = source.getHomeUrl(),
|
url = source.getHomeUrl(),
|
||||||
@@ -123,18 +130,18 @@ data class MigrateSourceSearchScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val onDismissRequest = { screenModel.setDialog(null) }
|
val onDismissRequest = { viewModel.setDialog(null) }
|
||||||
when (val dialog = state.dialog) {
|
when (val dialog = state.dialog) {
|
||||||
is BrowseSourceScreenModel.Dialog.Filter -> {
|
is BrowseSourceViewModel.Dialog.Filter -> {
|
||||||
SourceFilterDialog(
|
SourceFilterDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
filters = state.filters,
|
filters = state.filters,
|
||||||
onReset = screenModel::resetFilters,
|
onReset = viewModel::resetFilters,
|
||||||
onFilter = { screenModel.search(filters = state.filters) },
|
onFilter = { viewModel.search(filters = state.filters) },
|
||||||
onUpdate = screenModel::setFilters,
|
onUpdate = viewModel::setFilters,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is BrowseSourceScreenModel.Dialog.Migrate -> {
|
is BrowseSourceViewModel.Dialog.Migrate -> {
|
||||||
MigrateMangaDialog(
|
MigrateMangaDialog(
|
||||||
current = currentManga,
|
current = currentManga,
|
||||||
target = dialog.target,
|
target = dialog.target,
|
||||||
|
|||||||
+5
-5
@@ -6,7 +6,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
@@ -21,8 +21,8 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
fun Screen.migrateSourceTab(): TabContent {
|
fun Screen.migrateSourceTab(): TabContent {
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { MigrateSourceScreenModel() }
|
val viewModel = viewModel<MigrateSourceViewModel>()
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
return TabContent(
|
return TabContent(
|
||||||
titleRes = MR.strings.label_migration,
|
titleRes = MR.strings.label_migration,
|
||||||
@@ -42,8 +42,8 @@ fun Screen.migrateSourceTab(): TabContent {
|
|||||||
onClickItem = { source ->
|
onClickItem = { source ->
|
||||||
navigator.push(MigrateMangaScreen(source.id))
|
navigator.push(MigrateMangaScreen(source.id))
|
||||||
},
|
},
|
||||||
onToggleSortingDirection = screenModel::toggleSortingDirection,
|
onToggleSortingDirection = viewModel::toggleSortingDirection,
|
||||||
onToggleSortingMode = screenModel::toggleSortingMode,
|
onToggleSortingMode = viewModel::toggleSortingMode,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
+7
-7
@@ -1,8 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
package eu.kanade.tachiyomi.ui.browse.migration.sources
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
import eu.kanade.domain.source.interactor.GetSourcesWithFavoriteCount
|
||||||
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
import eu.kanade.domain.source.interactor.SetMigrateSorting
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
@@ -14,23 +13,24 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class MigrateSourceScreenModel(
|
class MigrateSourceViewModel(
|
||||||
preferences: SourcePreferences = Injekt.get(),
|
preferences: SourcePreferences = Injekt.get(),
|
||||||
private val getSourcesWithFavoriteCount: GetSourcesWithFavoriteCount = Injekt.get(),
|
private val getSourcesWithFavoriteCount: GetSourcesWithFavoriteCount = Injekt.get(),
|
||||||
private val setMigrateSorting: SetMigrateSorting = Injekt.get(),
|
private val setMigrateSorting: SetMigrateSorting = Injekt.get(),
|
||||||
) : StateScreenModel<MigrateSourceScreenModel.State>(State()) {
|
) : StateViewModel<MigrateSourceViewModel.State>(State()) {
|
||||||
|
|
||||||
private val _channel = Channel<Event>(Int.MAX_VALUE)
|
private val _channel = Channel<Event>(Int.MAX_VALUE)
|
||||||
val channel = _channel.receiveAsFlow()
|
val channel = _channel.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
getSourcesWithFavoriteCount.subscribe()
|
getSourcesWithFavoriteCount.subscribe()
|
||||||
.catch {
|
.catch {
|
||||||
logcat(LogPriority.ERROR, it)
|
logcat(LogPriority.ERROR, it)
|
||||||
@@ -48,11 +48,11 @@ class MigrateSourceScreenModel(
|
|||||||
|
|
||||||
preferences.migrationSortingDirection.changes()
|
preferences.migrationSortingDirection.changes()
|
||||||
.onEach { mutableState.update { state -> state.copy(sortingDirection = it) } }
|
.onEach { mutableState.update { state -> state.copy(sortingDirection = it) } }
|
||||||
.launchIn(screenModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
preferences.migrationSortingMode.changes()
|
preferences.migrationSortingMode.changes()
|
||||||
.onEach { mutableState.update { state -> state.copy(sortingMode = it) } }
|
.onEach { mutableState.update { state -> state.copy(sortingMode = it) } }
|
||||||
.launchIn(screenModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleSortingMode() {
|
fun toggleSortingMode() {
|
||||||
@@ -5,7 +5,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.browse.SourcesFilterScreen
|
import eu.kanade.presentation.browse.SourcesFilterScreen
|
||||||
@@ -19,15 +19,15 @@ class SourcesFilterScreen : Screen() {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { SourcesFilterScreenModel() }
|
val viewModel = viewModel<SourcesFilterViewModel>()
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
if (state is SourcesFilterScreenModel.State.Loading) {
|
if (state is SourcesFilterViewModel.State.Loading) {
|
||||||
LoadingScreen()
|
LoadingScreen()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is SourcesFilterScreenModel.State.Error) {
|
if (state is SourcesFilterViewModel.State.Error) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
context.toast(MR.strings.internal_error)
|
context.toast(MR.strings.internal_error)
|
||||||
@@ -36,13 +36,13 @@ class SourcesFilterScreen : Screen() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val successState = state as SourcesFilterScreenModel.State.Success
|
val successState = state as SourcesFilterViewModel.State.Success
|
||||||
|
|
||||||
SourcesFilterScreen(
|
SourcesFilterScreen(
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
state = successState,
|
state = successState,
|
||||||
onClickLanguage = screenModel::toggleLanguage,
|
onClickLanguage = viewModel::toggleLanguage,
|
||||||
onClickSource = screenModel::toggleSource,
|
onClickSource = viewModel::toggleSource,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -1,8 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source
|
package eu.kanade.tachiyomi.ui.browse.source
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
import eu.kanade.domain.source.interactor.GetLanguagesWithSources
|
||||||
import eu.kanade.domain.source.interactor.ToggleLanguage
|
import eu.kanade.domain.source.interactor.ToggleLanguage
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
@@ -12,20 +11,21 @@ import kotlinx.coroutines.flow.collectLatest
|
|||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.SortedMap
|
import java.util.SortedMap
|
||||||
|
|
||||||
class SourcesFilterScreenModel(
|
class SourcesFilterViewModel(
|
||||||
private val preferences: SourcePreferences = Injekt.get(),
|
private val preferences: SourcePreferences = Injekt.get(),
|
||||||
private val getLanguagesWithSources: GetLanguagesWithSources = Injekt.get(),
|
private val getLanguagesWithSources: GetLanguagesWithSources = Injekt.get(),
|
||||||
private val toggleSource: ToggleSource = Injekt.get(),
|
private val toggleSource: ToggleSource = Injekt.get(),
|
||||||
private val toggleLanguage: ToggleLanguage = Injekt.get(),
|
private val toggleLanguage: ToggleLanguage = Injekt.get(),
|
||||||
) : StateScreenModel<SourcesFilterScreenModel.State>(State.Loading) {
|
) : StateViewModel<SourcesFilterViewModel.State>(State.Loading) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
combine(
|
combine(
|
||||||
getLanguagesWithSources.subscribe(),
|
getLanguagesWithSources.subscribe(),
|
||||||
preferences.enabledLanguages.changes(),
|
preferences.enabledLanguages.changes(),
|
||||||
@@ -7,7 +7,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
@@ -25,8 +25,8 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
@Composable
|
@Composable
|
||||||
fun Screen.sourcesTab(): TabContent {
|
fun Screen.sourcesTab(): TabContent {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { SourcesScreenModel() }
|
val viewModel = viewModel<SourcesViewModel>()
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
return TabContent(
|
return TabContent(
|
||||||
titleRes = MR.strings.label_sources,
|
titleRes = MR.strings.label_sources,
|
||||||
@@ -49,8 +49,8 @@ fun Screen.sourcesTab(): TabContent {
|
|||||||
onClickItem = { source, listing ->
|
onClickItem = { source, listing ->
|
||||||
navigator.push(BrowseSourceScreen(source.id, listing.query))
|
navigator.push(BrowseSourceScreen(source.id, listing.query))
|
||||||
},
|
},
|
||||||
onClickPin = screenModel::togglePin,
|
onClickPin = viewModel::togglePin,
|
||||||
onLongClickItem = screenModel::showSourceDialog,
|
onLongClickItem = viewModel::showSourceDialog,
|
||||||
)
|
)
|
||||||
|
|
||||||
state.dialog?.let { dialog ->
|
state.dialog?.let { dialog ->
|
||||||
@@ -58,22 +58,22 @@ fun Screen.sourcesTab(): TabContent {
|
|||||||
SourceOptionsDialog(
|
SourceOptionsDialog(
|
||||||
source = source,
|
source = source,
|
||||||
onClickPin = {
|
onClickPin = {
|
||||||
screenModel.togglePin(source)
|
viewModel.togglePin(source)
|
||||||
screenModel.closeDialog()
|
viewModel.closeDialog()
|
||||||
},
|
},
|
||||||
onClickDisable = {
|
onClickDisable = {
|
||||||
screenModel.toggleSource(source)
|
viewModel.toggleSource(source)
|
||||||
screenModel.closeDialog()
|
viewModel.closeDialog()
|
||||||
},
|
},
|
||||||
onDismiss = screenModel::closeDialog,
|
onDismiss = viewModel::closeDialog,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val internalErrString = stringResource(MR.strings.internal_error)
|
val internalErrString = stringResource(MR.strings.internal_error)
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
screenModel.events.collectLatest { event ->
|
viewModel.events.collectLatest { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
SourcesScreenModel.Event.FailedFetchingSources -> {
|
SourcesViewModel.Event.FailedFetchingSources -> {
|
||||||
launch { snackbarHostState.showSnackbar(internalErrString) }
|
launch { snackbarHostState.showSnackbar(internalErrString) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -1,8 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source
|
package eu.kanade.tachiyomi.ui.browse.source
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import eu.kanade.domain.source.interactor.GetEnabledSources
|
import eu.kanade.domain.source.interactor.GetEnabledSources
|
||||||
import eu.kanade.domain.source.interactor.ToggleSource
|
import eu.kanade.domain.source.interactor.ToggleSource
|
||||||
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
import eu.kanade.domain.source.interactor.ToggleSourcePin
|
||||||
@@ -13,6 +12,7 @@ import kotlinx.coroutines.flow.collectLatest
|
|||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.source.model.Pin
|
import tachiyomi.domain.source.model.Pin
|
||||||
@@ -21,17 +21,17 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.TreeMap
|
import java.util.TreeMap
|
||||||
|
|
||||||
class SourcesScreenModel(
|
class SourcesViewModel(
|
||||||
private val getEnabledSources: GetEnabledSources = Injekt.get(),
|
private val getEnabledSources: GetEnabledSources = Injekt.get(),
|
||||||
private val toggleSource: ToggleSource = Injekt.get(),
|
private val toggleSource: ToggleSource = Injekt.get(),
|
||||||
private val toggleSourcePin: ToggleSourcePin = Injekt.get(),
|
private val toggleSourcePin: ToggleSourcePin = Injekt.get(),
|
||||||
) : StateScreenModel<SourcesScreenModel.State>(State()) {
|
) : StateViewModel<SourcesViewModel.State>(State()) {
|
||||||
|
|
||||||
private val _events = Channel<Event>(Int.MAX_VALUE)
|
private val _events = Channel<Event>(Int.MAX_VALUE)
|
||||||
val events = _events.receiveAsFlow()
|
val events = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
getEnabledSources.subscribe()
|
getEnabledSources.subscribe()
|
||||||
.catch {
|
.catch {
|
||||||
logcat(LogPriority.ERROR, it)
|
logcat(LogPriority.ERROR, it)
|
||||||
+53
-46
@@ -32,7 +32,8 @@ import androidx.compose.ui.input.pointer.pointerInput
|
|||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.core.util.ifSourcesLoaded
|
import eu.kanade.core.util.ifSourcesLoaded
|
||||||
@@ -46,7 +47,7 @@ import eu.kanade.presentation.util.AssistContentScreen
|
|||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen
|
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceViewModel.Listing
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
|
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
|
||||||
@@ -81,20 +82,26 @@ data class BrowseSourceScreen(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { BrowseSourceScreenModel(sourceId, listingQuery) }
|
val viewModel = viewModel<BrowseSourceViewModel>(
|
||||||
val state by screenModel.state.collectAsState()
|
factory = BrowseSourceViewModel.Factory,
|
||||||
|
extras = CreationExtras {
|
||||||
|
set(BrowseSourceViewModel.SOURCE_ID_KEY, sourceId)
|
||||||
|
set(BrowseSourceViewModel.LISTING_QUERY_KEY, listingQuery)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val navigateUp: () -> Unit = {
|
val navigateUp: () -> Unit = {
|
||||||
when {
|
when {
|
||||||
!state.isUserQuery && state.toolbarQuery != null -> screenModel.setToolbarQuery(null)
|
!state.isUserQuery && state.toolbarQuery != null -> viewModel.setToolbarQuery(null)
|
||||||
else -> navigator.pop()
|
else -> navigator.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screenModel.source is StubSource) {
|
if (viewModel.source is StubSource) {
|
||||||
MissingSourceScreen(
|
MissingSourceScreen(
|
||||||
source = screenModel.source,
|
source = viewModel.source,
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -107,7 +114,7 @@ data class BrowseSourceScreen(
|
|||||||
|
|
||||||
val onHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) }
|
val onHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) }
|
||||||
val onWebViewClick = f@{
|
val onWebViewClick = f@{
|
||||||
val source = screenModel.source as? HttpSource ?: return@f
|
val source = viewModel.source as? HttpSource ?: return@f
|
||||||
navigator.push(
|
navigator.push(
|
||||||
WebViewScreen(
|
WebViewScreen(
|
||||||
url = source.getHomeUrl(),
|
url = source.getHomeUrl(),
|
||||||
@@ -117,8 +124,8 @@ data class BrowseSourceScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(screenModel.source) {
|
LaunchedEffect(viewModel.source) {
|
||||||
assistUrl = (screenModel.source as? HttpSource)?.getHomeUrl()
|
assistUrl = (viewModel.source as? HttpSource)?.getHomeUrl()
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -130,15 +137,15 @@ data class BrowseSourceScreen(
|
|||||||
) {
|
) {
|
||||||
BrowseSourceToolbar(
|
BrowseSourceToolbar(
|
||||||
searchQuery = state.toolbarQuery,
|
searchQuery = state.toolbarQuery,
|
||||||
onSearchQueryChange = screenModel::setToolbarQuery,
|
onSearchQueryChange = viewModel::setToolbarQuery,
|
||||||
source = screenModel.source,
|
source = viewModel.source,
|
||||||
displayMode = screenModel.displayMode,
|
displayMode = viewModel.displayMode,
|
||||||
onDisplayModeChange = { screenModel.displayMode = it },
|
onDisplayModeChange = { viewModel.displayMode = it },
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
onWebViewClick = onWebViewClick,
|
onWebViewClick = onWebViewClick,
|
||||||
onHelpClick = onHelpClick,
|
onHelpClick = onHelpClick,
|
||||||
onSettingsClick = { navigator.push(SourcePreferencesScreen(sourceId)) },
|
onSettingsClick = { navigator.push(SourcePreferencesScreen(sourceId)) },
|
||||||
onSearch = screenModel::search,
|
onSearch = viewModel::search,
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -150,8 +157,8 @@ data class BrowseSourceScreen(
|
|||||||
FilterChip(
|
FilterChip(
|
||||||
selected = state.listing == Listing.Popular,
|
selected = state.listing == Listing.Popular,
|
||||||
onClick = {
|
onClick = {
|
||||||
screenModel.resetFilters()
|
viewModel.resetFilters()
|
||||||
screenModel.setListing(Listing.Popular)
|
viewModel.setListing(Listing.Popular)
|
||||||
},
|
},
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -165,12 +172,12 @@ data class BrowseSourceScreen(
|
|||||||
Text(text = stringResource(MR.strings.popular))
|
Text(text = stringResource(MR.strings.popular))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if (screenModel.source.supportsLatest) {
|
if (viewModel.source.supportsLatest) {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = state.listing == Listing.Latest,
|
selected = state.listing == Listing.Latest,
|
||||||
onClick = {
|
onClick = {
|
||||||
screenModel.resetFilters()
|
viewModel.resetFilters()
|
||||||
screenModel.setListing(Listing.Latest)
|
viewModel.setListing(Listing.Latest)
|
||||||
},
|
},
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -188,7 +195,7 @@ data class BrowseSourceScreen(
|
|||||||
if (state.filters.isNotEmpty()) {
|
if (state.filters.isNotEmpty()) {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = state.listing is Listing.Search,
|
selected = state.listing is Listing.Search,
|
||||||
onClick = screenModel::openFilterSheet,
|
onClick = viewModel::openFilterSheet,
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.FilterList,
|
imageVector = Icons.Outlined.FilterList,
|
||||||
@@ -210,10 +217,10 @@ data class BrowseSourceScreen(
|
|||||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
BrowseSourceContent(
|
BrowseSourceContent(
|
||||||
source = screenModel.source,
|
source = viewModel.source,
|
||||||
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
|
mangaList = viewModel.mangaPagerFlowFlow.collectAsLazyPagingItems(),
|
||||||
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
columns = viewModel.getColumnsPreference(LocalConfiguration.current.orientation),
|
||||||
displayMode = screenModel.displayMode,
|
displayMode = viewModel.displayMode,
|
||||||
snackbarHostState = snackbarHostState,
|
snackbarHostState = snackbarHostState,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
onWebViewClick = onWebViewClick,
|
onWebViewClick = onWebViewClick,
|
||||||
@@ -222,13 +229,13 @@ data class BrowseSourceScreen(
|
|||||||
onMangaClick = { navigator.push((MangaScreen(it.id, true))) },
|
onMangaClick = { navigator.push((MangaScreen(it.id, true))) },
|
||||||
onMangaLongClick = { manga ->
|
onMangaLongClick = { manga ->
|
||||||
scope.launchIO {
|
scope.launchIO {
|
||||||
val duplicates = screenModel.getDuplicateLibraryManga(manga)
|
val duplicates = viewModel.getDuplicateLibraryManga(manga)
|
||||||
when {
|
when {
|
||||||
manga.favorite -> screenModel.setDialog(BrowseSourceScreenModel.Dialog.RemoveManga(manga))
|
manga.favorite -> viewModel.setDialog(BrowseSourceViewModel.Dialog.RemoveManga(manga))
|
||||||
duplicates.isNotEmpty() -> screenModel.setDialog(
|
duplicates.isNotEmpty() -> viewModel.setDialog(
|
||||||
BrowseSourceScreenModel.Dialog.AddDuplicateManga(manga, duplicates),
|
BrowseSourceViewModel.Dialog.AddDuplicateManga(manga, duplicates),
|
||||||
)
|
)
|
||||||
else -> screenModel.addFavorite(manga)
|
else -> viewModel.addFavorite(manga)
|
||||||
}
|
}
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
}
|
}
|
||||||
@@ -236,28 +243,28 @@ data class BrowseSourceScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val onDismissRequest = { screenModel.setDialog(null) }
|
val onDismissRequest = { viewModel.setDialog(null) }
|
||||||
when (val dialog = state.dialog) {
|
when (val dialog = state.dialog) {
|
||||||
is BrowseSourceScreenModel.Dialog.Filter -> {
|
is BrowseSourceViewModel.Dialog.Filter -> {
|
||||||
SourceFilterDialog(
|
SourceFilterDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
filters = state.filters,
|
filters = state.filters,
|
||||||
onReset = screenModel::resetFilters,
|
onReset = viewModel::resetFilters,
|
||||||
onFilter = { screenModel.search(filters = state.filters) },
|
onFilter = { viewModel.search(filters = state.filters) },
|
||||||
onUpdate = screenModel::setFilters,
|
onUpdate = viewModel::setFilters,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is BrowseSourceScreenModel.Dialog.AddDuplicateManga -> {
|
is BrowseSourceViewModel.Dialog.AddDuplicateManga -> {
|
||||||
DuplicateMangaDialog(
|
DuplicateMangaDialog(
|
||||||
duplicates = dialog.duplicates,
|
duplicates = dialog.duplicates,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
onConfirm = { viewModel.addFavorite(dialog.manga) },
|
||||||
onOpenManga = { navigator.push(MangaScreen(it.id)) },
|
onOpenManga = { navigator.push(MangaScreen(it.id)) },
|
||||||
onMigrate = { screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(dialog.manga, it)) },
|
onMigrate = { viewModel.setDialog(BrowseSourceViewModel.Dialog.Migrate(dialog.manga, it)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is BrowseSourceScreenModel.Dialog.Migrate -> {
|
is BrowseSourceViewModel.Dialog.Migrate -> {
|
||||||
MigrateMangaDialog(
|
MigrateMangaDialog(
|
||||||
current = dialog.current,
|
current = dialog.current,
|
||||||
target = dialog.target,
|
target = dialog.target,
|
||||||
@@ -266,23 +273,23 @@ data class BrowseSourceScreen(
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is BrowseSourceScreenModel.Dialog.RemoveManga -> {
|
is BrowseSourceViewModel.Dialog.RemoveManga -> {
|
||||||
RemoveMangaDialog(
|
RemoveMangaDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
screenModel.changeMangaFavorite(dialog.manga)
|
viewModel.changeMangaFavorite(dialog.manga)
|
||||||
},
|
},
|
||||||
mangaToRemove = dialog.manga,
|
mangaToRemove = dialog.manga,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is BrowseSourceScreenModel.Dialog.ChangeMangaCategory -> {
|
is BrowseSourceViewModel.Dialog.ChangeMangaCategory -> {
|
||||||
ChangeCategoryDialog(
|
ChangeCategoryDialog(
|
||||||
initialSelection = dialog.initialSelection,
|
initialSelection = dialog.initialSelection,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onEditCategories = { navigator.push(CategoryScreen()) },
|
onEditCategories = { navigator.push(CategoryScreen()) },
|
||||||
onConfirm = { include, _ ->
|
onConfirm = { include, _ ->
|
||||||
screenModel.changeMangaFavorite(dialog.manga)
|
viewModel.changeMangaFavorite(dialog.manga)
|
||||||
screenModel.moveMangaToCategories(dialog.manga, include)
|
viewModel.moveMangaToCategories(dialog.manga, include)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -293,8 +300,8 @@ data class BrowseSourceScreen(
|
|||||||
queryEvent.receiveAsFlow()
|
queryEvent.receiveAsFlow()
|
||||||
.collectLatest {
|
.collectLatest {
|
||||||
when (it) {
|
when (it) {
|
||||||
is SearchType.Genre -> screenModel.searchGenre(it.txt)
|
is SearchType.Genre -> viewModel.searchGenre(it.txt)
|
||||||
is SearchType.Text -> screenModel.search(it.txt)
|
is SearchType.Text -> viewModel.search(it.txt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-12
@@ -6,19 +6,20 @@ import androidx.compose.runtime.Immutable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import androidx.paging.Pager
|
import androidx.paging.Pager
|
||||||
import androidx.paging.PagingConfig
|
import androidx.paging.PagingConfig
|
||||||
import androidx.paging.cachedIn
|
import androidx.paging.cachedIn
|
||||||
import androidx.paging.filter
|
import androidx.paging.filter
|
||||||
import androidx.paging.map
|
import androidx.paging.map
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.source.interactor.GetIncognitoState
|
import eu.kanade.domain.source.interactor.GetIncognitoState
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.domain.track.interactor.AddTracks
|
import eu.kanade.domain.track.interactor.AddTracks
|
||||||
import eu.kanade.presentation.util.ioCoroutineScope
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.util.removeCovers
|
import eu.kanade.tachiyomi.util.removeCovers
|
||||||
@@ -30,6 +31,7 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.preference.CheckboxState
|
import tachiyomi.core.common.preference.CheckboxState
|
||||||
import tachiyomi.core.common.preference.mapAsCheckboxState
|
import tachiyomi.core.common.preference.mapAsCheckboxState
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
@@ -50,7 +52,7 @@ import uy.kohesive.injekt.api.get
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter
|
import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter
|
||||||
|
|
||||||
class BrowseSourceScreenModel(
|
class BrowseSourceViewModel(
|
||||||
private val sourceId: Long,
|
private val sourceId: Long,
|
||||||
listingQuery: String?,
|
listingQuery: String?,
|
||||||
sourceManager: SourceManager = Injekt.get(),
|
sourceManager: SourceManager = Injekt.get(),
|
||||||
@@ -66,9 +68,23 @@ class BrowseSourceScreenModel(
|
|||||||
private val updateManga: UpdateManga = Injekt.get(),
|
private val updateManga: UpdateManga = Injekt.get(),
|
||||||
private val addTracks: AddTracks = Injekt.get(),
|
private val addTracks: AddTracks = Injekt.get(),
|
||||||
getIncognitoState: GetIncognitoState = Injekt.get(),
|
getIncognitoState: GetIncognitoState = Injekt.get(),
|
||||||
) : StateScreenModel<BrowseSourceScreenModel.State>(State(Listing.valueOf(listingQuery))) {
|
) : StateViewModel<BrowseSourceViewModel.State>(State(Listing.valueOf(listingQuery))) {
|
||||||
|
|
||||||
var displayMode by sourcePreferences.sourceDisplayMode.asState(screenModelScope)
|
companion object {
|
||||||
|
val SOURCE_ID_KEY = CreationExtras.Key<Long>()
|
||||||
|
val LISTING_QUERY_KEY = CreationExtras.Key<String?>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
BrowseSourceViewModel(
|
||||||
|
sourceId = get(SOURCE_ID_KEY)!!,
|
||||||
|
listingQuery = get(LISTING_QUERY_KEY),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayMode by sourcePreferences.sourceDisplayMode.asState(viewModelScope)
|
||||||
|
|
||||||
val source = sourceManager.getOrStub(sourceId)
|
val source = sourceManager.getOrStub(sourceId)
|
||||||
|
|
||||||
@@ -107,13 +123,13 @@ class BrowseSourceScreenModel(
|
|||||||
pagingData.map { manga ->
|
pagingData.map { manga ->
|
||||||
getManga.subscribe(manga.url, manga.source)
|
getManga.subscribe(manga.url, manga.source)
|
||||||
.map { it ?: manga }
|
.map { it ?: manga }
|
||||||
.stateIn(ioCoroutineScope)
|
.stateIn(viewModelScope)
|
||||||
}
|
}
|
||||||
.filter { !hideInLibraryItems || !it.value.favorite }
|
.filter { !hideInLibraryItems || !it.value.favorite }
|
||||||
}
|
}
|
||||||
.cachedIn(ioCoroutineScope)
|
.cachedIn(viewModelScope)
|
||||||
}
|
}
|
||||||
.stateIn(ioCoroutineScope, SharingStarted.Lazily, emptyFlow())
|
.stateIn(viewModelScope, SharingStarted.Lazily, emptyFlow())
|
||||||
|
|
||||||
fun getColumnsPreference(orientation: Int): GridCells {
|
fun getColumnsPreference(orientation: Int): GridCells {
|
||||||
val isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE
|
val isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
@@ -205,7 +221,7 @@ class BrowseSourceScreenModel(
|
|||||||
* @param manga the manga to update.
|
* @param manga the manga to update.
|
||||||
*/
|
*/
|
||||||
fun changeMangaFavorite(manga: Manga) {
|
fun changeMangaFavorite(manga: Manga) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
var new = manga.copy(
|
var new = manga.copy(
|
||||||
favorite = !manga.favorite,
|
favorite = !manga.favorite,
|
||||||
dateAdded = when (manga.favorite) {
|
dateAdded = when (manga.favorite) {
|
||||||
@@ -226,7 +242,7 @@ class BrowseSourceScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addFavorite(manga: Manga) {
|
fun addFavorite(manga: Manga) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
val categories = getCategories()
|
val categories = getCategories()
|
||||||
val defaultCategoryId = libraryPreferences.defaultCategory.get()
|
val defaultCategoryId = libraryPreferences.defaultCategory.get()
|
||||||
val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
|
val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
|
||||||
@@ -281,7 +297,7 @@ class BrowseSourceScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun moveMangaToCategories(manga: Manga, categoryIds: List<Long>) {
|
fun moveMangaToCategories(manga: Manga, categoryIds: List<Long>) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
setMangaCategories.await(
|
setMangaCategories.await(
|
||||||
mangaId = manga.id,
|
mangaId = manga.id,
|
||||||
categoryIds = categoryIds.toList(),
|
categoryIds = categoryIds.toList(),
|
||||||
+14
-12
@@ -7,7 +7,8 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.core.util.ifSourcesLoaded
|
import eu.kanade.core.util.ifSourcesLoaded
|
||||||
@@ -31,13 +32,14 @@ class GlobalSearchScreen(
|
|||||||
|
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel {
|
val viewModel = viewModel<GlobalSearchViewModel>(
|
||||||
GlobalSearchScreenModel(
|
factory = GlobalSearchViewModel.Factory,
|
||||||
initialQuery = searchQuery,
|
extras = CreationExtras {
|
||||||
initialExtensionFilter = extensionFilter,
|
set(GlobalSearchViewModel.INITIAL_QUERY_KEY, searchQuery)
|
||||||
|
set(GlobalSearchViewModel.INITIAL_EXTENSION_FILTER_KEY, extensionFilter)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
val state by viewModel.state.collectAsState()
|
||||||
val state by screenModel.state.collectAsState()
|
|
||||||
var showSingleLoadingScreen by remember {
|
var showSingleLoadingScreen by remember {
|
||||||
mutableStateOf(searchQuery.isNotEmpty() && !extensionFilter.isNullOrEmpty() && state.total == 1)
|
mutableStateOf(searchQuery.isNotEmpty() && !extensionFilter.isNullOrEmpty() && state.total == 1)
|
||||||
}
|
}
|
||||||
@@ -64,11 +66,11 @@ class GlobalSearchScreen(
|
|||||||
GlobalSearchScreen(
|
GlobalSearchScreen(
|
||||||
state = state,
|
state = state,
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
onChangeSearchQuery = screenModel::updateSearchQuery,
|
onChangeSearchQuery = viewModel::updateSearchQuery,
|
||||||
onSearch = { screenModel.search() },
|
onSearch = { viewModel.search() },
|
||||||
getManga = { screenModel.getManga(it) },
|
getManga = { viewModel.getManga(it) },
|
||||||
onChangeSearchFilter = screenModel::setSourceFilter,
|
onChangeSearchFilter = viewModel::setSourceFilter,
|
||||||
onToggleResults = screenModel::toggleFilterResults,
|
onToggleResults = viewModel::toggleFilterResults,
|
||||||
onClickSource = {
|
onClickSource = {
|
||||||
navigator.push(BrowseSourceScreen(it.id, state.searchQuery))
|
navigator.push(BrowseSourceScreen(it.id, state.searchQuery))
|
||||||
},
|
},
|
||||||
|
|||||||
-25
@@ -1,25 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.source.globalsearch
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
|
|
||||||
class GlobalSearchScreenModel(
|
|
||||||
initialQuery: String = "",
|
|
||||||
initialExtensionFilter: String? = null,
|
|
||||||
) : SearchScreenModel(State(searchQuery = initialQuery)) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
extensionFilter = initialExtensionFilter
|
|
||||||
if (initialQuery.isNotBlank() || !initialExtensionFilter.isNullOrBlank()) {
|
|
||||||
if (extensionFilter != null) {
|
|
||||||
// we're going to use custom extension filter instead
|
|
||||||
setSourceFilter(SourceFilter.All)
|
|
||||||
}
|
|
||||||
search()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getEnabledSources(): List<Source> {
|
|
||||||
return super.getEnabledSources()
|
|
||||||
.filter { state.value.sourceFilter != SourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse.source.globalsearch
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
|
||||||
|
class GlobalSearchViewModel(
|
||||||
|
initialQuery: String,
|
||||||
|
initialExtensionFilter: String?,
|
||||||
|
) : SearchViewModel(State(searchQuery = initialQuery)) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val INITIAL_QUERY_KEY = CreationExtras.Key<String>()
|
||||||
|
val INITIAL_EXTENSION_FILTER_KEY = CreationExtras.Key<String?>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
GlobalSearchViewModel(
|
||||||
|
initialQuery = get(INITIAL_QUERY_KEY)!!,
|
||||||
|
initialExtensionFilter = get(INITIAL_EXTENSION_FILTER_KEY),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
extensionFilter = initialExtensionFilter
|
||||||
|
if (initialQuery.isNotBlank() || !initialExtensionFilter.isNullOrBlank()) {
|
||||||
|
if (extensionFilter != null) {
|
||||||
|
// we're going to use custom extension filter instead
|
||||||
|
setSourceFilter(SourceFilter.All)
|
||||||
|
}
|
||||||
|
search()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEnabledSources(): List<Source> {
|
||||||
|
return super.getEnabledSources()
|
||||||
|
.filter { state.value.sourceFilter != SourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-8
@@ -3,10 +3,8 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.presentation.util.ioCoroutineScope
|
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -19,6 +17,7 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import mihon.domain.manga.model.toDomainManga
|
import mihon.domain.manga.model.toDomainManga
|
||||||
import tachiyomi.core.common.preference.toggle
|
import tachiyomi.core.common.preference.toggle
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
@@ -30,7 +29,7 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
abstract class SearchScreenModel(
|
abstract class SearchViewModel(
|
||||||
initialState: State = State(),
|
initialState: State = State(),
|
||||||
sourcePreferences: SourcePreferences = Injekt.get(),
|
sourcePreferences: SourcePreferences = Injekt.get(),
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
@@ -38,7 +37,7 @@ abstract class SearchScreenModel(
|
|||||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||||
private val getManga: GetManga = Injekt.get(),
|
private val getManga: GetManga = Injekt.get(),
|
||||||
private val preferences: SourcePreferences = Injekt.get(),
|
private val preferences: SourcePreferences = Injekt.get(),
|
||||||
) : StateScreenModel<SearchScreenModel.State>(initialState) {
|
) : StateViewModel<SearchViewModel.State>(initialState) {
|
||||||
|
|
||||||
private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
|
private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
|
||||||
private var searchJob: Job? = null
|
private var searchJob: Job? = null
|
||||||
@@ -61,7 +60,7 @@ abstract class SearchScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
preferences.globalSearchFilterState.changes().collectLatest { state ->
|
preferences.globalSearchFilterState.changes().collectLatest { state ->
|
||||||
mutableState.update { it.copy(onlyShowHasResults = state) }
|
mutableState.update { it.copy(onlyShowHasResults = state) }
|
||||||
}
|
}
|
||||||
@@ -147,7 +146,7 @@ abstract class SearchScreenModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
searchJob = ioCoroutineScope.launch {
|
searchJob = viewModelScope.launchIO {
|
||||||
sources.map { source ->
|
sources.map { source ->
|
||||||
async {
|
async {
|
||||||
if (state.value.items[source] !is SearchItemResult.Loading) {
|
if (state.value.items[source] !is SearchItemResult.Loading) {
|
||||||
@@ -192,7 +191,7 @@ abstract class SearchScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setMigrateDialog(currentId: Long, target: Manga) {
|
fun setMigrateDialog(currentId: Long, target: Manga) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val current = getManga.await(currentId) ?: return@launchIO
|
val current = getManga.await(currentId) ?: return@launchIO
|
||||||
mutableState.update { it.copy(dialog = Dialog.Migrate(target, current)) }
|
mutableState.update { it.copy(dialog = Dialog.Migrate(target, current)) }
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.util.fastMap
|
import androidx.compose.ui.util.fastMap
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.category.CategoryScreen
|
import eu.kanade.presentation.category.CategoryScreen
|
||||||
@@ -24,9 +24,9 @@ class CategoryScreen : Screen() {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { CategoryScreenModel() }
|
val viewModel = viewModel<CategoryViewModel>()
|
||||||
|
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
if (state is CategoryScreenState.Loading) {
|
if (state is CategoryScreenState.Loading) {
|
||||||
LoadingScreen()
|
LoadingScreen()
|
||||||
@@ -37,10 +37,10 @@ class CategoryScreen : Screen() {
|
|||||||
|
|
||||||
CategoryScreen(
|
CategoryScreen(
|
||||||
state = successState,
|
state = successState,
|
||||||
onClickCreate = { screenModel.showDialog(CategoryDialog.Create) },
|
onClickCreate = { viewModel.showDialog(CategoryDialog.Create) },
|
||||||
onClickRename = { screenModel.showDialog(CategoryDialog.Rename(it)) },
|
onClickRename = { viewModel.showDialog(CategoryDialog.Rename(it)) },
|
||||||
onClickDelete = { screenModel.showDialog(CategoryDialog.Delete(it)) },
|
onClickDelete = { viewModel.showDialog(CategoryDialog.Delete(it)) },
|
||||||
onChangeOrder = screenModel::changeOrder,
|
onChangeOrder = viewModel::changeOrder,
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,30 +48,30 @@ class CategoryScreen : Screen() {
|
|||||||
null -> {}
|
null -> {}
|
||||||
CategoryDialog.Create -> {
|
CategoryDialog.Create -> {
|
||||||
CategoryCreateDialog(
|
CategoryCreateDialog(
|
||||||
onDismissRequest = screenModel::dismissDialog,
|
onDismissRequest = viewModel::dismissDialog,
|
||||||
onCreate = screenModel::createCategory,
|
onCreate = viewModel::createCategory,
|
||||||
categories = successState.categories.fastMap { it.name },
|
categories = successState.categories.fastMap { it.name },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is CategoryDialog.Rename -> {
|
is CategoryDialog.Rename -> {
|
||||||
CategoryRenameDialog(
|
CategoryRenameDialog(
|
||||||
onDismissRequest = screenModel::dismissDialog,
|
onDismissRequest = viewModel::dismissDialog,
|
||||||
onRename = { screenModel.renameCategory(dialog.category, it) },
|
onRename = { viewModel.renameCategory(dialog.category, it) },
|
||||||
categories = successState.categories.fastMap { it.name },
|
categories = successState.categories.fastMap { it.name },
|
||||||
category = dialog.category.name,
|
category = dialog.category.name,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is CategoryDialog.Delete -> {
|
is CategoryDialog.Delete -> {
|
||||||
CategoryDeleteDialog(
|
CategoryDeleteDialog(
|
||||||
onDismissRequest = screenModel::dismissDialog,
|
onDismissRequest = viewModel::dismissDialog,
|
||||||
onDelete = { screenModel.deleteCategory(dialog.category.id) },
|
onDelete = { viewModel.deleteCategory(dialog.category.id) },
|
||||||
category = dialog.category.name,
|
category = dialog.category.name,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
screenModel.events.collectLatest { event ->
|
viewModel.events.collectLatest { event ->
|
||||||
if (event is CategoryEvent.LocalizedMessage) {
|
if (event is CategoryEvent.LocalizedMessage) {
|
||||||
context.toast(event.stringRes)
|
context.toast(event.stringRes)
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-9
@@ -1,14 +1,14 @@
|
|||||||
package eu.kanade.tachiyomi.ui.category
|
package eu.kanade.tachiyomi.ui.category
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.domain.category.interactor.CreateCategoryWithName
|
import tachiyomi.domain.category.interactor.CreateCategoryWithName
|
||||||
import tachiyomi.domain.category.interactor.DeleteCategory
|
import tachiyomi.domain.category.interactor.DeleteCategory
|
||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
@@ -19,19 +19,19 @@ import tachiyomi.i18n.MR
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class CategoryScreenModel(
|
class CategoryViewModel(
|
||||||
private val getCategories: GetCategories = Injekt.get(),
|
private val getCategories: GetCategories = Injekt.get(),
|
||||||
private val createCategoryWithName: CreateCategoryWithName = Injekt.get(),
|
private val createCategoryWithName: CreateCategoryWithName = Injekt.get(),
|
||||||
private val deleteCategory: DeleteCategory = Injekt.get(),
|
private val deleteCategory: DeleteCategory = Injekt.get(),
|
||||||
private val reorderCategory: ReorderCategory = Injekt.get(),
|
private val reorderCategory: ReorderCategory = Injekt.get(),
|
||||||
private val renameCategory: RenameCategory = Injekt.get(),
|
private val renameCategory: RenameCategory = Injekt.get(),
|
||||||
) : StateScreenModel<CategoryScreenState>(CategoryScreenState.Loading) {
|
) : StateViewModel<CategoryScreenState>(CategoryScreenState.Loading) {
|
||||||
|
|
||||||
private val _events: Channel<CategoryEvent> = Channel()
|
private val _events: Channel<CategoryEvent> = Channel()
|
||||||
val events = _events.receiveAsFlow()
|
val events = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
getCategories.subscribe()
|
getCategories.subscribe()
|
||||||
.collectLatest { categories ->
|
.collectLatest { categories ->
|
||||||
mutableState.update {
|
mutableState.update {
|
||||||
@@ -45,7 +45,7 @@ class CategoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createCategory(name: String) {
|
fun createCategory(name: String) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
when (createCategoryWithName.await(name)) {
|
when (createCategoryWithName.await(name)) {
|
||||||
is CreateCategoryWithName.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
is CreateCategoryWithName.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
||||||
else -> {}
|
else -> {}
|
||||||
@@ -54,7 +54,7 @@ class CategoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun deleteCategory(categoryId: Long) {
|
fun deleteCategory(categoryId: Long) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
when (deleteCategory.await(categoryId = categoryId)) {
|
when (deleteCategory.await(categoryId = categoryId)) {
|
||||||
is DeleteCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
is DeleteCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
||||||
else -> {}
|
else -> {}
|
||||||
@@ -63,7 +63,7 @@ class CategoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun changeOrder(category: Category, newIndex: Int) {
|
fun changeOrder(category: Category, newIndex: Int) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
when (reorderCategory.await(category, newIndex)) {
|
when (reorderCategory.await(category, newIndex)) {
|
||||||
is ReorderCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
is ReorderCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
||||||
else -> {}
|
else -> {}
|
||||||
@@ -72,7 +72,7 @@ class CategoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun renameCategory(category: Category, name: String) {
|
fun renameCategory(category: Category, name: String) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
when (renameCategory.await(category, name)) {
|
when (renameCategory.await(category, name)) {
|
||||||
is RenameCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
is RenameCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
||||||
else -> {}
|
else -> {}
|
||||||
@@ -6,7 +6,8 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
@@ -28,10 +29,13 @@ class DeepLinkScreen(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel {
|
val viewModel = viewModel<DeepLinkViewModel>(
|
||||||
DeepLinkScreenModel(query = query)
|
factory = DeepLinkViewModel.Factory,
|
||||||
}
|
extras = CreationExtras {
|
||||||
val state by screenModel.state.collectAsState()
|
set(DeepLinkViewModel.QUERY_KEY, query)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
AppBar(
|
AppBar(
|
||||||
@@ -42,14 +46,14 @@ class DeepLinkScreen(
|
|||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is DeepLinkScreenModel.State.Loading -> {
|
is DeepLinkViewModel.State.Loading -> {
|
||||||
LoadingScreen(Modifier.padding(contentPadding))
|
LoadingScreen(Modifier.padding(contentPadding))
|
||||||
}
|
}
|
||||||
is DeepLinkScreenModel.State.NoResults -> {
|
is DeepLinkViewModel.State.NoResults -> {
|
||||||
navigator.replace(GlobalSearchScreen(query))
|
navigator.replace(GlobalSearchScreen(query))
|
||||||
}
|
}
|
||||||
is DeepLinkScreenModel.State.Result -> {
|
is DeepLinkViewModel.State.Result -> {
|
||||||
val resultState = state as DeepLinkScreenModel.State.Result
|
val resultState = state as DeepLinkViewModel.State.Result
|
||||||
if (resultState.chapterId == null) {
|
if (resultState.chapterId == null) {
|
||||||
navigator.replace(
|
navigator.replace(
|
||||||
MangaScreen(
|
MangaScreen(
|
||||||
|
|||||||
+21
-6
@@ -1,13 +1,16 @@
|
|||||||
package eu.kanade.tachiyomi.ui.deeplink
|
package eu.kanade.tachiyomi.ui.deeplink
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.online.ResolvableSource
|
import eu.kanade.tachiyomi.source.online.ResolvableSource
|
||||||
import eu.kanade.tachiyomi.source.online.UriType
|
import eu.kanade.tachiyomi.source.online.UriType
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import mihon.domain.manga.model.toDomainManga
|
import mihon.domain.manga.model.toDomainManga
|
||||||
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
@@ -19,16 +22,28 @@ import tachiyomi.domain.source.service.SourceManager
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class DeepLinkScreenModel(
|
class DeepLinkViewModel(
|
||||||
query: String = "",
|
query: String,
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||||
private val getChapterByUrlAndMangaId: GetChapterByUrlAndMangaId = Injekt.get(),
|
private val getChapterByUrlAndMangaId: GetChapterByUrlAndMangaId = Injekt.get(),
|
||||||
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get(),
|
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get(),
|
||||||
) : StateScreenModel<DeepLinkScreenModel.State>(State.Loading) {
|
) : StateViewModel<DeepLinkViewModel.State>(State.Loading) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val QUERY_KEY = CreationExtras.Key<String>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
DeepLinkViewModel(
|
||||||
|
query = get(QUERY_KEY)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val source = sourceManager.getAll()
|
val source = sourceManager.getAll()
|
||||||
.filterIsInstance<ResolvableSource>()
|
.filterIsInstance<ResolvableSource>()
|
||||||
.firstOrNull { it.getUriType(query) != UriType.Unknown }
|
.firstOrNull { it.getUriType(query) != UriType.Unknown }
|
||||||
@@ -41,8 +41,8 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
@@ -65,8 +65,8 @@ object DownloadQueueScreen : Screen() {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val screenModel = rememberScreenModel { DownloadQueueScreenModel() }
|
val viewModel = viewModel<DownloadQueueViewModel>()
|
||||||
val downloadList by screenModel.state.collectAsState()
|
val downloadList by viewModel.state.collectAsState()
|
||||||
val downloadCount by remember {
|
val downloadCount by remember {
|
||||||
derivedStateOf { downloadList.sumOf { it.subItems.size } }
|
derivedStateOf { downloadList.sumOf { it.subItems.size } }
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ object DownloadQueueScreen : Screen() {
|
|||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(MR.strings.action_newest)) },
|
text = { Text(text = stringResource(MR.strings.action_newest)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
screenModel.reorderQueue(
|
viewModel.reorderQueue(
|
||||||
{ it.download.chapter.dateUpload },
|
{ it.download.chapter.dateUpload },
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
@@ -143,7 +143,7 @@ object DownloadQueueScreen : Screen() {
|
|||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(MR.strings.action_oldest)) },
|
text = { Text(text = stringResource(MR.strings.action_oldest)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
screenModel.reorderQueue(
|
viewModel.reorderQueue(
|
||||||
{ it.download.chapter.dateUpload },
|
{ it.download.chapter.dateUpload },
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
@@ -158,7 +158,7 @@ object DownloadQueueScreen : Screen() {
|
|||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(MR.strings.action_asc)) },
|
text = { Text(text = stringResource(MR.strings.action_asc)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
screenModel.reorderQueue(
|
viewModel.reorderQueue(
|
||||||
{ it.download.chapter.chapterNumber },
|
{ it.download.chapter.chapterNumber },
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
@@ -168,7 +168,7 @@ object DownloadQueueScreen : Screen() {
|
|||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(text = stringResource(MR.strings.action_desc)) },
|
text = { Text(text = stringResource(MR.strings.action_desc)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
screenModel.reorderQueue(
|
viewModel.reorderQueue(
|
||||||
{ it.download.chapter.chapterNumber },
|
{ it.download.chapter.chapterNumber },
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
@@ -188,7 +188,7 @@ object DownloadQueueScreen : Screen() {
|
|||||||
),
|
),
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(MR.strings.action_cancel_all),
|
title = stringResource(MR.strings.action_cancel_all),
|
||||||
onClick = { screenModel.clearQueue() },
|
onClick = { viewModel.clearQueue() },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -198,7 +198,7 @@ object DownloadQueueScreen : Screen() {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
val isRunning by screenModel.isDownloaderRunning.collectAsState()
|
val isRunning by viewModel.isDownloaderRunning.collectAsState()
|
||||||
SmallExtendedFloatingActionButton(
|
SmallExtendedFloatingActionButton(
|
||||||
text = {
|
text = {
|
||||||
val id = if (isRunning) {
|
val id = if (isRunning) {
|
||||||
@@ -218,9 +218,9 @@ object DownloadQueueScreen : Screen() {
|
|||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
screenModel.pauseDownloads()
|
viewModel.pauseDownloads()
|
||||||
} else {
|
} else {
|
||||||
screenModel.startDownloads()
|
viewModel.startDownloads()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
expanded = fabExpanded,
|
expanded = fabExpanded,
|
||||||
@@ -250,27 +250,27 @@ object DownloadQueueScreen : Screen() {
|
|||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
factory = { context ->
|
factory = { context ->
|
||||||
screenModel.controllerBinding = DownloadListBinding.inflate(LayoutInflater.from(context))
|
viewModel.controllerBinding = DownloadListBinding.inflate(LayoutInflater.from(context))
|
||||||
screenModel.adapter = DownloadAdapter(screenModel.listener)
|
viewModel.adapter = DownloadAdapter(viewModel.listener)
|
||||||
screenModel.controllerBinding.root.adapter = screenModel.adapter
|
viewModel.controllerBinding.root.adapter = viewModel.adapter
|
||||||
screenModel.adapter?.isHandleDragEnabled = true
|
viewModel.adapter?.isHandleDragEnabled = true
|
||||||
screenModel.controllerBinding.root.layoutManager = LinearLayoutManager(context)
|
viewModel.controllerBinding.root.layoutManager = LinearLayoutManager(context)
|
||||||
|
|
||||||
ViewCompat.setNestedScrollingEnabled(screenModel.controllerBinding.root, true)
|
ViewCompat.setNestedScrollingEnabled(viewModel.controllerBinding.root, true)
|
||||||
|
|
||||||
scope.launchUI {
|
scope.launchUI {
|
||||||
screenModel.getDownloadStatusFlow()
|
viewModel.getDownloadStatusFlow()
|
||||||
.collect(screenModel::onStatusChange)
|
.collect(viewModel::onStatusChange)
|
||||||
}
|
}
|
||||||
scope.launchUI {
|
scope.launchUI {
|
||||||
screenModel.getDownloadProgressFlow()
|
viewModel.getDownloadProgressFlow()
|
||||||
.collect(screenModel::onUpdateDownloadedPages)
|
.collect(viewModel::onUpdateDownloadedPages)
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModel.controllerBinding.root
|
viewModel.controllerBinding.root
|
||||||
},
|
},
|
||||||
update = {
|
update = {
|
||||||
screenModel.controllerBinding.root
|
viewModel.controllerBinding.root
|
||||||
.updatePadding(
|
.updatePadding(
|
||||||
left = left,
|
left = left,
|
||||||
top = top,
|
top = top,
|
||||||
@@ -278,7 +278,7 @@ object DownloadQueueScreen : Screen() {
|
|||||||
bottom = bottom,
|
bottom = bottom,
|
||||||
)
|
)
|
||||||
|
|
||||||
screenModel.adapter?.updateDataSet(downloadList)
|
viewModel.adapter?.updateDataSet(downloadList)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.ui.download
|
package eu.kanade.tachiyomi.ui.download
|
||||||
|
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import androidx.lifecycle.ViewModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
@@ -25,9 +25,9 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
class DownloadQueueScreenModel(
|
class DownloadQueueViewModel(
|
||||||
private val downloadManager: DownloadManager = Injekt.get(),
|
private val downloadManager: DownloadManager = Injekt.get(),
|
||||||
) : ScreenModel {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _state = MutableStateFlow(emptyList<DownloadHeaderItem>())
|
private val _state = MutableStateFlow(emptyList<DownloadHeaderItem>())
|
||||||
val state = _state.asStateFlow()
|
val state = _state.asStateFlow()
|
||||||
@@ -117,7 +117,7 @@ class DownloadQueueScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
downloadManager.queueState
|
downloadManager.queueState
|
||||||
.map { downloads ->
|
.map { downloads ->
|
||||||
downloads
|
downloads
|
||||||
@@ -132,7 +132,7 @@ class DownloadQueueScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDispose() {
|
override fun onCleared() {
|
||||||
for (job in progressJobs.values) {
|
for (job in progressJobs.values) {
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,7 @@ class DownloadQueueScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val isDownloaderRunning = downloadManager.isDownloaderRunning
|
val isDownloaderRunning = downloadManager.isDownloaderRunning
|
||||||
.stateIn(screenModelScope, SharingStarted.WhileSubscribed(5000), false)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
||||||
|
|
||||||
fun getDownloadStatusFlow() = downloadManager.statusFlow()
|
fun getDownloadStatusFlow() = downloadManager.statusFlow()
|
||||||
fun getDownloadProgressFlow() = downloadManager.progressFlow()
|
fun getDownloadProgressFlow() = downloadManager.progressFlow()
|
||||||
@@ -211,7 +211,7 @@ class DownloadQueueScreenModel(
|
|||||||
* @param download the download to observe its progress.
|
* @param download the download to observe its progress.
|
||||||
*/
|
*/
|
||||||
private fun launchProgressJob(download: Download) {
|
private fun launchProgressJob(download: Download) {
|
||||||
val job = screenModelScope.launch {
|
val job = viewModelScope.launch {
|
||||||
while (download.pages == null) {
|
while (download.pages == null) {
|
||||||
delay(50.milliseconds)
|
delay(50.milliseconds)
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
@@ -62,59 +62,59 @@ data object HistoryTab : Tab {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val screenModel = rememberScreenModel { HistoryScreenModel() }
|
val viewModel = viewModel<HistoryViewModel>()
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
HistoryScreen(
|
HistoryScreen(
|
||||||
state = state,
|
state = state,
|
||||||
snackbarHostState = snackbarHostState,
|
snackbarHostState = snackbarHostState,
|
||||||
onSearchQueryChange = screenModel::updateSearchQuery,
|
onSearchQueryChange = viewModel::updateSearchQuery,
|
||||||
onClickCover = { navigator.push(MangaScreen(it)) },
|
onClickCover = { navigator.push(MangaScreen(it)) },
|
||||||
onClickResume = screenModel::getNextChapterForManga,
|
onClickResume = viewModel::getNextChapterForManga,
|
||||||
onDialogChange = screenModel::setDialog,
|
onDialogChange = viewModel::setDialog,
|
||||||
onClickFavorite = screenModel::addFavorite,
|
onClickFavorite = viewModel::addFavorite,
|
||||||
)
|
)
|
||||||
|
|
||||||
val onDismissRequest = { screenModel.setDialog(null) }
|
val onDismissRequest = { viewModel.setDialog(null) }
|
||||||
when (val dialog = state.dialog) {
|
when (val dialog = state.dialog) {
|
||||||
is HistoryScreenModel.Dialog.Delete -> {
|
is HistoryViewModel.Dialog.Delete -> {
|
||||||
HistoryDeleteDialog(
|
HistoryDeleteDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onDelete = { all ->
|
onDelete = { all ->
|
||||||
if (all) {
|
if (all) {
|
||||||
screenModel.removeAllFromHistory(dialog.history.mangaId)
|
viewModel.removeAllFromHistory(dialog.history.mangaId)
|
||||||
} else {
|
} else {
|
||||||
screenModel.removeFromHistory(dialog.history)
|
viewModel.removeFromHistory(dialog.history)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is HistoryScreenModel.Dialog.DeleteAll -> {
|
is HistoryViewModel.Dialog.DeleteAll -> {
|
||||||
HistoryDeleteAllDialog(
|
HistoryDeleteAllDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onDelete = screenModel::removeAllHistory,
|
onDelete = viewModel::removeAllHistory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is HistoryScreenModel.Dialog.DuplicateManga -> {
|
is HistoryViewModel.Dialog.DuplicateManga -> {
|
||||||
DuplicateMangaDialog(
|
DuplicateMangaDialog(
|
||||||
duplicates = dialog.duplicates,
|
duplicates = dialog.duplicates,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onConfirm = { screenModel.addFavorite(dialog.manga) },
|
onConfirm = { viewModel.addFavorite(dialog.manga) },
|
||||||
onOpenManga = { navigator.push(MangaScreen(it.id)) },
|
onOpenManga = { navigator.push(MangaScreen(it.id)) },
|
||||||
onMigrate = { screenModel.showMigrateDialog(dialog.manga, it) },
|
onMigrate = { viewModel.showMigrateDialog(dialog.manga, it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is HistoryScreenModel.Dialog.ChangeCategory -> {
|
is HistoryViewModel.Dialog.ChangeCategory -> {
|
||||||
ChangeCategoryDialog(
|
ChangeCategoryDialog(
|
||||||
initialSelection = dialog.initialSelection,
|
initialSelection = dialog.initialSelection,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onEditCategories = { navigator.push(CategoryScreen()) },
|
onEditCategories = { navigator.push(CategoryScreen()) },
|
||||||
onConfirm = { include, _ ->
|
onConfirm = { include, _ ->
|
||||||
screenModel.moveMangaToCategoriesAndAddToLibrary(dialog.manga, include)
|
viewModel.moveMangaToCategoriesAndAddToLibrary(dialog.manga, include)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is HistoryScreenModel.Dialog.Migrate -> {
|
is HistoryViewModel.Dialog.Migrate -> {
|
||||||
MigrateMangaDialog(
|
MigrateMangaDialog(
|
||||||
current = dialog.current,
|
current = dialog.current,
|
||||||
target = dialog.target,
|
target = dialog.target,
|
||||||
@@ -133,20 +133,20 @@ data object HistoryTab : Tab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
screenModel.events.collectLatest { e ->
|
viewModel.events.collectLatest { e ->
|
||||||
when (e) {
|
when (e) {
|
||||||
HistoryScreenModel.Event.InternalError ->
|
HistoryViewModel.Event.InternalError ->
|
||||||
snackbarHostState.showSnackbar(context.stringResource(MR.strings.internal_error))
|
snackbarHostState.showSnackbar(context.stringResource(MR.strings.internal_error))
|
||||||
HistoryScreenModel.Event.HistoryCleared ->
|
HistoryViewModel.Event.HistoryCleared ->
|
||||||
snackbarHostState.showSnackbar(context.stringResource(MR.strings.clear_history_completed))
|
snackbarHostState.showSnackbar(context.stringResource(MR.strings.clear_history_completed))
|
||||||
is HistoryScreenModel.Event.OpenChapter -> openChapter(context, e.chapter)
|
is HistoryViewModel.Event.OpenChapter -> openChapter(context, e.chapter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
resumeLastChapterReadEvent.receiveAsFlow().collectLatest {
|
resumeLastChapterReadEvent.receiveAsFlow().collectLatest {
|
||||||
openChapter(context, screenModel.getNextChapter())
|
openChapter(context, viewModel.getNextChapter())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-14
@@ -2,8 +2,7 @@ package eu.kanade.tachiyomi.ui.history
|
|||||||
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import eu.kanade.core.util.insertSeparators
|
import eu.kanade.core.util.insertSeparators
|
||||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.track.interactor.AddTracks
|
import eu.kanade.domain.track.interactor.AddTracks
|
||||||
@@ -21,6 +20,7 @@ import kotlinx.coroutines.flow.receiveAsFlow
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.preference.CheckboxState
|
import tachiyomi.core.common.preference.CheckboxState
|
||||||
import tachiyomi.core.common.preference.mapAsCheckboxState
|
import tachiyomi.core.common.preference.mapAsCheckboxState
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
@@ -43,7 +43,7 @@ import tachiyomi.domain.source.service.SourceManager
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class HistoryScreenModel(
|
class HistoryViewModel(
|
||||||
private val addTracks: AddTracks = Injekt.get(),
|
private val addTracks: AddTracks = Injekt.get(),
|
||||||
private val getCategories: GetCategories = Injekt.get(),
|
private val getCategories: GetCategories = Injekt.get(),
|
||||||
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
|
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
|
||||||
@@ -56,13 +56,13 @@ class HistoryScreenModel(
|
|||||||
private val updateManga: UpdateManga = Injekt.get(),
|
private val updateManga: UpdateManga = Injekt.get(),
|
||||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
) : StateScreenModel<HistoryScreenModel.State>(State()) {
|
) : StateViewModel<HistoryViewModel.State>(State()) {
|
||||||
|
|
||||||
private val _events: Channel<Event> = Channel(Channel.UNLIMITED)
|
private val _events: Channel<Event> = Channel(Channel.UNLIMITED)
|
||||||
val events: Flow<Event> = _events.receiveAsFlow()
|
val events: Flow<Event> = _events.receiveAsFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
state.map { it.searchQuery }
|
state.map { it.searchQuery }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.flatMapLatest { query ->
|
.flatMapLatest { query ->
|
||||||
@@ -97,7 +97,7 @@ class HistoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
|
fun getNextChapterForManga(mangaId: Long, chapterId: Long) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false))
|
sendNextChapterEvent(getNextChapters.await(mangaId, chapterId, onlyUnread = false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,19 +108,19 @@ class HistoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun removeFromHistory(history: HistoryWithRelations) {
|
fun removeFromHistory(history: HistoryWithRelations) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
removeHistory.await(history)
|
removeHistory.await(history)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAllFromHistory(mangaId: Long) {
|
fun removeAllFromHistory(mangaId: Long) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
removeHistory.await(mangaId)
|
removeHistory.await(mangaId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAllHistory() {
|
fun removeAllHistory() {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val result = removeHistory.awaitAll()
|
val result = removeHistory.awaitAll()
|
||||||
if (!result) return@launchIO
|
if (!result) return@launchIO
|
||||||
_events.send(Event.HistoryCleared)
|
_events.send(Event.HistoryCleared)
|
||||||
@@ -150,7 +150,7 @@ class HistoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun moveMangaToCategory(mangaId: Long, categoryIds: List<Long>) {
|
private fun moveMangaToCategory(mangaId: Long, categoryIds: List<Long>) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
setMangaCategories.await(mangaId, categoryIds)
|
setMangaCategories.await(mangaId, categoryIds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,7 @@ class HistoryScreenModel(
|
|||||||
moveMangaToCategory(manga.id, categories)
|
moveMangaToCategory(manga.id, categories)
|
||||||
if (manga.favorite) return
|
if (manga.favorite) return
|
||||||
|
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
updateManga.awaitUpdateFavorite(manga.id, true)
|
updateManga.awaitUpdateFavorite(manga.id, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ class HistoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addFavorite(mangaId: Long) {
|
fun addFavorite(mangaId: Long) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val manga = getManga.await(mangaId) ?: return@launchIO
|
val manga = getManga.await(mangaId) ?: return@launchIO
|
||||||
|
|
||||||
val duplicates = getDuplicateLibraryManga(manga)
|
val duplicates = getDuplicateLibraryManga(manga)
|
||||||
@@ -184,7 +184,7 @@ class HistoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addFavorite(manga: Manga) {
|
fun addFavorite(manga: Manga) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
// Move to default category if applicable
|
// Move to default category if applicable
|
||||||
val categories = getCategories()
|
val categories = getCategories()
|
||||||
val defaultCategoryId = libraryPreferences.defaultCategory.get().toLong()
|
val defaultCategoryId = libraryPreferences.defaultCategory.get().toLong()
|
||||||
@@ -221,7 +221,7 @@ class HistoryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun showChangeCategoryDialog(manga: Manga) {
|
fun showChangeCategoryDialog(manga: Manga) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
val categories = getCategories()
|
val categories = getCategories()
|
||||||
val selection = getMangaCategoryIds(manga)
|
val selection = getMangaCategoryIds(manga)
|
||||||
mutableState.update { currentState ->
|
mutableState.update { currentState ->
|
||||||
+6
-6
@@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import androidx.lifecycle.ViewModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
@@ -20,17 +20,17 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class LibrarySettingsScreenModel(
|
class LibrarySettingsViewModel(
|
||||||
val preferences: BasePreferences = Injekt.get(),
|
val preferences: BasePreferences = Injekt.get(),
|
||||||
val libraryPreferences: LibraryPreferences = Injekt.get(),
|
val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||||
private val setDisplayMode: SetDisplayMode = Injekt.get(),
|
private val setDisplayMode: SetDisplayMode = Injekt.get(),
|
||||||
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
|
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
|
||||||
trackerManager: TrackerManager = Injekt.get(),
|
trackerManager: TrackerManager = Injekt.get(),
|
||||||
) : ScreenModel {
|
) : ViewModel() {
|
||||||
|
|
||||||
val trackersFlow = trackerManager.loggedInTrackersFlow()
|
val trackersFlow = trackerManager.loggedInTrackersFlow()
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope = screenModelScope,
|
scope = viewModelScope,
|
||||||
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds),
|
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds),
|
||||||
initialValue = trackerManager.loggedInTrackers(),
|
initialValue = trackerManager.loggedInTrackers(),
|
||||||
)
|
)
|
||||||
@@ -50,7 +50,7 @@ class LibrarySettingsScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setSort(category: Category?, mode: LibrarySort.Type, direction: LibrarySort.Direction) {
|
fun setSort(category: Category?, mode: LibrarySort.Type, direction: LibrarySort.Direction) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
setSortModeForCategory.await(category, mode, direction)
|
setSortModeForCategory.await(category, mode, direction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.util.fastAll
|
import androidx.compose.ui.util.fastAll
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
@@ -86,9 +86,9 @@ data object LibraryTab : Tab {
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { LibraryScreenModel() }
|
val viewModel = viewModel<LibraryViewModel>()
|
||||||
val settingsScreenModel = rememberScreenModel { LibrarySettingsScreenModel() }
|
val settingsViewModel = viewModel<LibrarySettingsViewModel>()
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
@@ -116,15 +116,15 @@ data object LibraryTab : Tab {
|
|||||||
hasActiveFilters = state.hasActiveFilters,
|
hasActiveFilters = state.hasActiveFilters,
|
||||||
selectedCount = state.selection.size,
|
selectedCount = state.selection.size,
|
||||||
title = title,
|
title = title,
|
||||||
onClickUnselectAll = screenModel::clearSelection,
|
onClickUnselectAll = viewModel::clearSelection,
|
||||||
onClickSelectAll = screenModel::selectAll,
|
onClickSelectAll = viewModel::selectAll,
|
||||||
onClickInvertSelection = screenModel::invertSelection,
|
onClickInvertSelection = viewModel::invertSelection,
|
||||||
onClickFilter = screenModel::showSettingsDialog,
|
onClickFilter = viewModel::showSettingsDialog,
|
||||||
onClickRefresh = { onClickRefresh(state.activeCategory) },
|
onClickRefresh = { onClickRefresh(state.activeCategory) },
|
||||||
onClickGlobalUpdate = { onClickRefresh(null) },
|
onClickGlobalUpdate = { onClickRefresh(null) },
|
||||||
onClickOpenRandomManga = {
|
onClickOpenRandomManga = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val randomItem = screenModel.getRandomLibraryItemForCurrentCategory()
|
val randomItem = viewModel.getRandomLibraryItemForCurrentCategory()
|
||||||
if (randomItem != null) {
|
if (randomItem != null) {
|
||||||
navigator.push(MangaScreen(randomItem.libraryManga.manga.id))
|
navigator.push(MangaScreen(randomItem.libraryManga.manga.id))
|
||||||
} else {
|
} else {
|
||||||
@@ -135,7 +135,7 @@ data object LibraryTab : Tab {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
searchQuery = state.searchQuery,
|
searchQuery = state.searchQuery,
|
||||||
onSearchQueryChange = screenModel::search,
|
onSearchQueryChange = viewModel::search,
|
||||||
// For scroll overlay when no tab
|
// For scroll overlay when no tab
|
||||||
scrollBehavior = scrollBehavior.takeIf { !state.showCategoryTabs },
|
scrollBehavior = scrollBehavior.takeIf { !state.showCategoryTabs },
|
||||||
)
|
)
|
||||||
@@ -143,15 +143,15 @@ data object LibraryTab : Tab {
|
|||||||
bottomBar = {
|
bottomBar = {
|
||||||
LibraryBottomActionMenu(
|
LibraryBottomActionMenu(
|
||||||
visible = state.selectionMode,
|
visible = state.selectionMode,
|
||||||
onChangeCategoryClicked = screenModel::openChangeCategoryDialog,
|
onChangeCategoryClicked = viewModel::openChangeCategoryDialog,
|
||||||
onMarkAsReadClicked = { screenModel.markReadSelection(true) },
|
onMarkAsReadClicked = { viewModel.markReadSelection(true) },
|
||||||
onMarkAsUnreadClicked = { screenModel.markReadSelection(false) },
|
onMarkAsUnreadClicked = { viewModel.markReadSelection(false) },
|
||||||
onDownloadClicked = screenModel::performDownloadAction
|
onDownloadClicked = viewModel::performDownloadAction
|
||||||
.takeIf { state.selectedManga.fastAll { !it.isLocal() } },
|
.takeIf { state.selectedManga.fastAll { !it.isLocal() } },
|
||||||
onDeleteClicked = screenModel::openDeleteMangaDialog,
|
onDeleteClicked = viewModel::openDeleteMangaDialog,
|
||||||
onMigrateClicked = {
|
onMigrateClicked = {
|
||||||
val selection = state.selection
|
val selection = state.selection
|
||||||
screenModel.clearSelection()
|
viewModel.clearSelection()
|
||||||
navigator.push(MigrationConfigScreen(selection))
|
navigator.push(MigrationConfigScreen(selection))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -185,11 +185,11 @@ data object LibraryTab : Tab {
|
|||||||
currentPage = state.coercedActiveCategoryIndex,
|
currentPage = state.coercedActiveCategoryIndex,
|
||||||
hasActiveFilters = state.hasActiveFilters,
|
hasActiveFilters = state.hasActiveFilters,
|
||||||
showPageTabs = state.showCategoryTabs || !state.searchQuery.isNullOrEmpty(),
|
showPageTabs = state.showCategoryTabs || !state.searchQuery.isNullOrEmpty(),
|
||||||
onChangeCurrentPage = screenModel::updateActiveCategoryIndex,
|
onChangeCurrentPage = viewModel::updateActiveCategoryIndex,
|
||||||
onClickManga = { navigator.push(MangaScreen(it)) },
|
onClickManga = { navigator.push(MangaScreen(it)) },
|
||||||
onContinueReadingClicked = { it: LibraryManga ->
|
onContinueReadingClicked = { it: LibraryManga ->
|
||||||
scope.launchIO {
|
scope.launchIO {
|
||||||
val chapter = screenModel.getNextUnreadChapter(it.manga)
|
val chapter = viewModel.getNextUnreadChapter(it.manga)
|
||||||
if (chapter != null) {
|
if (chapter != null) {
|
||||||
context.startActivity(
|
context.startActivity(
|
||||||
ReaderActivity.newIntent(context, chapter.mangaId, chapter.id),
|
ReaderActivity.newIntent(context, chapter.mangaId, chapter.id),
|
||||||
@@ -200,54 +200,54 @@ data object LibraryTab : Tab {
|
|||||||
}
|
}
|
||||||
Unit
|
Unit
|
||||||
}.takeIf { state.showMangaContinueButton },
|
}.takeIf { state.showMangaContinueButton },
|
||||||
onToggleSelection = screenModel::toggleSelection,
|
onToggleSelection = viewModel::toggleSelection,
|
||||||
onToggleRangeSelection = { category, manga ->
|
onToggleRangeSelection = { category, manga ->
|
||||||
screenModel.toggleRangeSelection(category, manga)
|
viewModel.toggleRangeSelection(category, manga)
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
},
|
},
|
||||||
onRefresh = { onClickRefresh(state.activeCategory) },
|
onRefresh = { onClickRefresh(state.activeCategory) },
|
||||||
onGlobalSearchClicked = {
|
onGlobalSearchClicked = {
|
||||||
navigator.push(GlobalSearchScreen(screenModel.state.value.searchQuery ?: ""))
|
navigator.push(GlobalSearchScreen(viewModel.state.value.searchQuery ?: ""))
|
||||||
},
|
},
|
||||||
getItemCountForCategory = { state.getItemCountForCategory(it) },
|
getItemCountForCategory = { state.getItemCountForCategory(it) },
|
||||||
getDisplayMode = { screenModel.getDisplayMode() },
|
getDisplayMode = { viewModel.getDisplayMode() },
|
||||||
getColumnsForOrientation = { screenModel.getColumnsForOrientation(it) },
|
getColumnsForOrientation = { viewModel.getColumnsForOrientation(it) },
|
||||||
getItemsForCategory = { state.getItemsForCategory(it) },
|
getItemsForCategory = { state.getItemsForCategory(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val onDismissRequest = screenModel::closeDialog
|
val onDismissRequest = viewModel::closeDialog
|
||||||
when (val dialog = state.dialog) {
|
when (val dialog = state.dialog) {
|
||||||
is LibraryScreenModel.Dialog.SettingsSheet -> run {
|
is LibraryViewModel.Dialog.SettingsSheet -> run {
|
||||||
LibrarySettingsDialog(
|
LibrarySettingsDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
screenModel = settingsScreenModel,
|
viewModel = settingsViewModel,
|
||||||
category = state.activeCategory,
|
category = state.activeCategory,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is LibraryScreenModel.Dialog.ChangeCategory -> {
|
is LibraryViewModel.Dialog.ChangeCategory -> {
|
||||||
ChangeCategoryDialog(
|
ChangeCategoryDialog(
|
||||||
initialSelection = dialog.initialSelection,
|
initialSelection = dialog.initialSelection,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onEditCategories = {
|
onEditCategories = {
|
||||||
screenModel.clearSelection()
|
viewModel.clearSelection()
|
||||||
navigator.push(CategoryScreen())
|
navigator.push(CategoryScreen())
|
||||||
},
|
},
|
||||||
onConfirm = { include, exclude ->
|
onConfirm = { include, exclude ->
|
||||||
screenModel.clearSelection()
|
viewModel.clearSelection()
|
||||||
screenModel.setMangaCategories(dialog.manga, include, exclude)
|
viewModel.setMangaCategories(dialog.manga, include, exclude)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is LibraryScreenModel.Dialog.DeleteManga -> {
|
is LibraryViewModel.Dialog.DeleteManga -> {
|
||||||
DeleteLibraryMangaDialog(
|
DeleteLibraryMangaDialog(
|
||||||
containsLocalManga = dialog.manga.any(Manga::isLocal),
|
containsLocalManga = dialog.manga.any(Manga::isLocal),
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onConfirm = { deleteManga, deleteChapter ->
|
onConfirm = { deleteManga, deleteChapter ->
|
||||||
screenModel.removeMangas(dialog.manga, deleteManga, deleteChapter)
|
viewModel.removeMangas(dialog.manga, deleteManga, deleteChapter)
|
||||||
screenModel.clearSelection()
|
viewModel.clearSelection()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -256,8 +256,8 @@ data object LibraryTab : Tab {
|
|||||||
|
|
||||||
BackHandler(enabled = state.selectionMode || state.searchQuery != null) {
|
BackHandler(enabled = state.selectionMode || state.searchQuery != null) {
|
||||||
when {
|
when {
|
||||||
state.selectionMode -> screenModel.clearSelection()
|
state.selectionMode -> viewModel.clearSelection()
|
||||||
state.searchQuery != null -> screenModel.search(null)
|
state.searchQuery != null -> viewModel.search(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,8 +272,8 @@ data object LibraryTab : Tab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
launch { queryEvent.receiveAsFlow().collect(screenModel::search) }
|
launch { queryEvent.receiveAsFlow().collect(viewModel::search) }
|
||||||
launch { requestSettingsSheetEvent.receiveAsFlow().collectLatest { screenModel.showSettingsDialog() } }
|
launch { requestSettingsSheetEvent.receiveAsFlow().collectLatest { viewModel.showSettingsDialog() } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-16
@@ -4,8 +4,7 @@ import androidx.compose.runtime.Immutable
|
|||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
import androidx.compose.ui.util.fastFilter
|
import androidx.compose.ui.util.fastFilter
|
||||||
import androidx.compose.ui.util.fastMap
|
import androidx.compose.ui.util.fastMap
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import eu.kanade.core.preference.PreferenceMutableState
|
import eu.kanade.core.preference.PreferenceMutableState
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.core.util.fastFilterNot
|
import eu.kanade.core.util.fastFilterNot
|
||||||
@@ -36,6 +35,7 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.flow.updateAndGet
|
import kotlinx.coroutines.flow.updateAndGet
|
||||||
import mihon.core.common.utils.mutate
|
import mihon.core.common.utils.mutate
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.preference.CheckboxState
|
import tachiyomi.core.common.preference.CheckboxState
|
||||||
import tachiyomi.core.common.preference.TriState
|
import tachiyomi.core.common.preference.TriState
|
||||||
import tachiyomi.core.common.util.lang.compareToWithCollator
|
import tachiyomi.core.common.util.lang.compareToWithCollator
|
||||||
@@ -66,7 +66,7 @@ import uy.kohesive.injekt.api.get
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class LibraryScreenModel(
|
class LibraryViewModel(
|
||||||
private val getLibraryManga: GetLibraryManga = Injekt.get(),
|
private val getLibraryManga: GetLibraryManga = Injekt.get(),
|
||||||
private val getCategories: GetCategories = Injekt.get(),
|
private val getCategories: GetCategories = Injekt.get(),
|
||||||
private val getTracksPerManga: GetTracksPerManga = Injekt.get(),
|
private val getTracksPerManga: GetTracksPerManga = Injekt.get(),
|
||||||
@@ -83,13 +83,13 @@ class LibraryScreenModel(
|
|||||||
private val downloadManager: DownloadManager = Injekt.get(),
|
private val downloadManager: DownloadManager = Injekt.get(),
|
||||||
private val downloadCache: DownloadCache = Injekt.get(),
|
private val downloadCache: DownloadCache = Injekt.get(),
|
||||||
private val trackerManager: TrackerManager = Injekt.get(),
|
private val trackerManager: TrackerManager = Injekt.get(),
|
||||||
) : StateScreenModel<LibraryScreenModel.State>(State()) {
|
) : StateViewModel<LibraryViewModel.State>(State()) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mutableState.update { state ->
|
mutableState.update { state ->
|
||||||
state.copy(activeCategoryIndex = libraryPreferences.lastUsedCategory.get())
|
state.copy(activeCategoryIndex = libraryPreferences.lastUsedCategory.get())
|
||||||
}
|
}
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
combine(
|
combine(
|
||||||
state.map { it.searchQuery }.distinctUntilChanged().debounce(0.25.seconds),
|
state.map { it.searchQuery }.distinctUntilChanged().debounce(0.25.seconds),
|
||||||
getCategories.subscribe(),
|
getCategories.subscribe(),
|
||||||
@@ -119,7 +119,7 @@ class LibraryScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
state
|
state
|
||||||
.dropWhile { !it.libraryData.isInitialized }
|
.dropWhile { !it.libraryData.isInitialized }
|
||||||
.map { it.libraryData }
|
.map { it.libraryData }
|
||||||
@@ -153,7 +153,7 @@ class LibraryScreenModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.launchIn(screenModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
combine(
|
combine(
|
||||||
getLibraryItemPreferencesFlow(),
|
getLibraryItemPreferencesFlow(),
|
||||||
@@ -176,7 +176,7 @@ class LibraryScreenModel(
|
|||||||
state.copy(hasActiveFilters = it)
|
state.copy(hasActiveFilters = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.launchIn(screenModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<LibraryItem>.applyFilters(
|
private fun List<LibraryItem>.applyFilters(
|
||||||
@@ -479,7 +479,7 @@ class LibraryScreenModel(
|
|||||||
|
|
||||||
private fun downloadNextChapters(amount: Int?) {
|
private fun downloadNextChapters(amount: Int?) {
|
||||||
val mangas = state.value.selectedManga
|
val mangas = state.value.selectedManga
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
mangas.forEach { manga ->
|
mangas.forEach { manga ->
|
||||||
val chapters = getNextChapters.await(manga.id)
|
val chapters = getNextChapters.await(manga.id)
|
||||||
.fastFilterNot { chapter ->
|
.fastFilterNot { chapter ->
|
||||||
@@ -501,7 +501,7 @@ class LibraryScreenModel(
|
|||||||
|
|
||||||
private fun downloadBookmarkedChapters() {
|
private fun downloadBookmarkedChapters() {
|
||||||
val mangas = state.value.selectedManga
|
val mangas = state.value.selectedManga
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
mangas.forEach { manga ->
|
mangas.forEach { manga ->
|
||||||
val chapters = getBookmarkedChaptersByMangaId.await(manga.id)
|
val chapters = getBookmarkedChaptersByMangaId.await(manga.id)
|
||||||
.fastFilterNot { chapter ->
|
.fastFilterNot { chapter ->
|
||||||
@@ -524,7 +524,7 @@ class LibraryScreenModel(
|
|||||||
*/
|
*/
|
||||||
fun markReadSelection(read: Boolean) {
|
fun markReadSelection(read: Boolean) {
|
||||||
val selection = state.value.selectedManga
|
val selection = state.value.selectedManga
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
selection.forEach { manga ->
|
selection.forEach { manga ->
|
||||||
setReadStatus.await(
|
setReadStatus.await(
|
||||||
manga = manga,
|
manga = manga,
|
||||||
@@ -543,7 +543,7 @@ class LibraryScreenModel(
|
|||||||
* @param deleteChapters whether to delete downloaded chapters.
|
* @param deleteChapters whether to delete downloaded chapters.
|
||||||
*/
|
*/
|
||||||
fun removeMangas(mangas: List<Manga>, deleteFromLibrary: Boolean, deleteChapters: Boolean) {
|
fun removeMangas(mangas: List<Manga>, deleteFromLibrary: Boolean, deleteChapters: Boolean) {
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
if (deleteFromLibrary) {
|
if (deleteFromLibrary) {
|
||||||
val toDelete = mangas.map {
|
val toDelete = mangas.map {
|
||||||
it.removeCovers(coverCache)
|
it.removeCovers(coverCache)
|
||||||
@@ -574,7 +574,7 @@ class LibraryScreenModel(
|
|||||||
* @param removeCategories the categories to remove in all mangas.
|
* @param removeCategories the categories to remove in all mangas.
|
||||||
*/
|
*/
|
||||||
fun setMangaCategories(mangaList: List<Manga>, addCategories: List<Long>, removeCategories: List<Long>) {
|
fun setMangaCategories(mangaList: List<Manga>, addCategories: List<Long>, removeCategories: List<Long>) {
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
mangaList.forEach { manga ->
|
mangaList.forEach { manga ->
|
||||||
val categoryIds = getCategories.await(manga.id)
|
val categoryIds = getCategories.await(manga.id)
|
||||||
.map { it.id }
|
.map { it.id }
|
||||||
@@ -588,12 +588,12 @@ class LibraryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getDisplayMode(): PreferenceMutableState<LibraryDisplayMode> {
|
fun getDisplayMode(): PreferenceMutableState<LibraryDisplayMode> {
|
||||||
return libraryPreferences.displayMode.asState(screenModelScope)
|
return libraryPreferences.displayMode.asState(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getColumnsForOrientation(isLandscape: Boolean): PreferenceMutableState<Int> {
|
fun getColumnsForOrientation(isLandscape: Boolean): PreferenceMutableState<Int> {
|
||||||
return (if (isLandscape) libraryPreferences.landscapeColumns else libraryPreferences.portraitColumns)
|
return (if (isLandscape) libraryPreferences.landscapeColumns else libraryPreferences.portraitColumns)
|
||||||
.asState(screenModelScope)
|
.asState(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRandomLibraryItemForCurrentCategory(): LibraryItem? {
|
fun getRandomLibraryItemForCurrentCategory(): LibraryItem? {
|
||||||
@@ -689,7 +689,7 @@ class LibraryScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun openChangeCategoryDialog() {
|
fun openChangeCategoryDialog() {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
// Create a copy of selected manga
|
// Create a copy of selected manga
|
||||||
val mangaList = state.value.selectedManga
|
val mangaList = state.value.selectedManga
|
||||||
|
|
||||||
+26
-11
@@ -3,8 +3,10 @@ package eu.kanade.tachiyomi.ui.manga
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import coil3.asDrawable
|
import coil3.asDrawable
|
||||||
import coil3.imageLoader
|
import coil3.imageLoader
|
||||||
import coil3.request.ImageRequest
|
import coil3.request.ImageRequest
|
||||||
@@ -20,6 +22,7 @@ import eu.kanade.tachiyomi.util.system.toShareIntent
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
@@ -31,7 +34,7 @@ import tachiyomi.i18n.MR
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class MangaCoverScreenModel(
|
class MangaCoverViewModel(
|
||||||
private val mangaId: Long,
|
private val mangaId: Long,
|
||||||
private val getManga: GetManga = Injekt.get(),
|
private val getManga: GetManga = Injekt.get(),
|
||||||
private val imageSaver: ImageSaver = Injekt.get(),
|
private val imageSaver: ImageSaver = Injekt.get(),
|
||||||
@@ -39,17 +42,29 @@ class MangaCoverScreenModel(
|
|||||||
private val updateManga: UpdateManga = Injekt.get(),
|
private val updateManga: UpdateManga = Injekt.get(),
|
||||||
|
|
||||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||||
) : StateScreenModel<Manga?>(null) {
|
) : StateViewModel<Manga?>(null) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val MANGA_ID_KEY = CreationExtras.Key<Long>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
MangaCoverViewModel(
|
||||||
|
mangaId = get(MANGA_ID_KEY)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
getManga.subscribe(mangaId)
|
getManga.subscribe(mangaId)
|
||||||
.collect { newManga -> mutableState.update { newManga } }
|
.collect { newManga -> mutableState.update { newManga } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveCover(context: Context) {
|
fun saveCover(context: Context) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
saveCoverInternal(context, temp = false)
|
saveCoverInternal(context, temp = false)
|
||||||
snackbarHostState.showSnackbar(
|
snackbarHostState.showSnackbar(
|
||||||
@@ -67,7 +82,7 @@ class MangaCoverScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun shareCover(context: Context) {
|
fun shareCover(context: Context) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val uri = saveCoverInternal(context, temp = true) ?: return@launch
|
val uri = saveCoverInternal(context, temp = true) ?: return@launch
|
||||||
withUIContext {
|
withUIContext {
|
||||||
@@ -119,7 +134,7 @@ class MangaCoverScreenModel(
|
|||||||
*/
|
*/
|
||||||
fun editCover(context: Context, data: Uri) {
|
fun editCover(context: Context, data: Uri) {
|
||||||
val manga = state.value ?: return
|
val manga = state.value ?: return
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
context.contentResolver.openInputStream(data)?.use {
|
context.contentResolver.openInputStream(data)?.use {
|
||||||
try {
|
try {
|
||||||
manga.editCover(Injekt.get(), it, updateManga, coverCache)
|
manga.editCover(Injekt.get(), it, updateManga, coverCache)
|
||||||
@@ -133,7 +148,7 @@ class MangaCoverScreenModel(
|
|||||||
|
|
||||||
fun deleteCustomCover(context: Context) {
|
fun deleteCustomCover(context: Context) {
|
||||||
val mangaId = state.value?.id ?: return
|
val mangaId = state.value?.id ?: return
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
try {
|
try {
|
||||||
coverCache.deleteCustomCover(mangaId)
|
coverCache.deleteCustomCover(mangaId)
|
||||||
updateManga.awaitUpdateCoverLastModified(mangaId)
|
updateManga.awaitUpdateCoverLastModified(mangaId)
|
||||||
@@ -145,7 +160,7 @@ class MangaCoverScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyCoverUpdated(context: Context) {
|
private fun notifyCoverUpdated(context: Context) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
snackbarHostState.showSnackbar(
|
snackbarHostState.showSnackbar(
|
||||||
context.stringResource(MR.strings.cover_updated),
|
context.stringResource(MR.strings.cover_updated),
|
||||||
withDismissAction = true,
|
withDismissAction = true,
|
||||||
@@ -154,7 +169,7 @@ class MangaCoverScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun notifyFailedCoverUpdate(context: Context, e: Throwable) {
|
private fun notifyFailedCoverUpdate(context: Context, e: Throwable) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
snackbarHostState.showSnackbar(
|
snackbarHostState.showSnackbar(
|
||||||
context.stringResource(MR.strings.notification_cover_update_failed),
|
context.stringResource(MR.strings.notification_cover_update_failed),
|
||||||
withDismissAction = true,
|
withDismissAction = true,
|
||||||
@@ -17,9 +17,9 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
@@ -84,26 +84,29 @@ class MangaScreen(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val viewModel = viewModel<MangaViewModel>(
|
||||||
val screenModel = rememberScreenModel {
|
factory = MangaViewModel.Factory,
|
||||||
MangaScreenModel(context, lifecycleOwner.lifecycle, mangaId, fromSource)
|
extras = CreationExtras {
|
||||||
}
|
set(MangaViewModel.MANGA_ID_KEY, mangaId)
|
||||||
|
set(MangaViewModel.IS_FROM_SOURCE_KEY, fromSource)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
val state by screenModel.state.collectAsStateWithLifecycle()
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
if (state is MangaScreenModel.State.Loading) {
|
if (state is MangaViewModel.State.Loading) {
|
||||||
LoadingScreen()
|
LoadingScreen()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val successState = state as MangaScreenModel.State.Success
|
val successState = state as MangaViewModel.State.Success
|
||||||
val isHttpSource = remember { successState.source is HttpSource }
|
val isHttpSource = remember { successState.source is HttpSource }
|
||||||
|
|
||||||
LaunchedEffect(successState.manga, screenModel.source) {
|
LaunchedEffect(successState.manga, viewModel.source) {
|
||||||
if (isHttpSource) {
|
if (isHttpSource) {
|
||||||
try {
|
try {
|
||||||
withIOContext {
|
withIOContext {
|
||||||
assistUrl = getMangaUrl(screenModel.manga, screenModel.source)
|
assistUrl = getMangaUrl(viewModel.manga, viewModel.source)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to get manga URL" }
|
logcat(LogPriority.ERROR, e) { "Failed to get manga URL" }
|
||||||
@@ -113,101 +116,101 @@ class MangaScreen(
|
|||||||
|
|
||||||
MangaScreen(
|
MangaScreen(
|
||||||
state = successState,
|
state = successState,
|
||||||
snackbarHostState = screenModel.snackbarHostState,
|
snackbarHostState = viewModel.snackbarHostState,
|
||||||
nextUpdate = successState.manga.expectedNextUpdate,
|
nextUpdate = successState.manga.expectedNextUpdate,
|
||||||
isTabletUi = isTabletUi(),
|
isTabletUi = isTabletUi(),
|
||||||
chapterSwipeStartAction = screenModel.chapterSwipeStartAction,
|
chapterSwipeStartAction = viewModel.chapterSwipeStartAction,
|
||||||
chapterSwipeEndAction = screenModel.chapterSwipeEndAction,
|
chapterSwipeEndAction = viewModel.chapterSwipeEndAction,
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
onChapterClicked = { openChapter(context, it) },
|
onChapterClicked = { openChapter(context, it) },
|
||||||
onDownloadChapter = screenModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() },
|
onDownloadChapter = viewModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() },
|
||||||
onAddToLibraryClicked = {
|
onAddToLibraryClicked = {
|
||||||
screenModel.toggleFavorite()
|
viewModel.toggleFavorite()
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
},
|
},
|
||||||
onWebViewClicked = {
|
onWebViewClicked = {
|
||||||
openMangaInWebView(
|
openMangaInWebView(
|
||||||
navigator,
|
navigator,
|
||||||
screenModel.manga,
|
viewModel.manga,
|
||||||
screenModel.source,
|
viewModel.source,
|
||||||
)
|
)
|
||||||
}.takeIf { isHttpSource },
|
}.takeIf { isHttpSource },
|
||||||
onWebViewLongClicked = {
|
onWebViewLongClicked = {
|
||||||
copyMangaUrl(
|
copyMangaUrl(
|
||||||
context,
|
context,
|
||||||
screenModel.manga,
|
viewModel.manga,
|
||||||
screenModel.source,
|
viewModel.source,
|
||||||
)
|
)
|
||||||
}.takeIf { isHttpSource },
|
}.takeIf { isHttpSource },
|
||||||
onTrackingClicked = {
|
onTrackingClicked = {
|
||||||
if (!successState.hasLoggedInTrackers) {
|
if (!successState.hasLoggedInTrackers) {
|
||||||
navigator.push(SettingsScreen(SettingsScreen.Destination.Tracking))
|
navigator.push(SettingsScreen(SettingsScreen.Destination.Tracking))
|
||||||
} else {
|
} else {
|
||||||
screenModel.showTrackDialog()
|
viewModel.showTrackDialog()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onTagSearch = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } },
|
onTagSearch = { scope.launch { performGenreSearch(navigator, it, viewModel.source!!) } },
|
||||||
onFilterButtonClicked = screenModel::showSettingsDialog,
|
onFilterButtonClicked = viewModel::showSettingsDialog,
|
||||||
onRefresh = screenModel::fetchAllFromSource,
|
onRefresh = viewModel::fetchAllFromSource,
|
||||||
onContinueReading = { continueReading(context, screenModel.getNextUnreadChapter()) },
|
onContinueReading = { continueReading(context, viewModel.getNextUnreadChapter()) },
|
||||||
onSearch = { query, global -> scope.launch { performSearch(navigator, query, global) } },
|
onSearch = { query, global -> scope.launch { performSearch(navigator, query, global) } },
|
||||||
onCoverClicked = screenModel::showCoverDialog,
|
onCoverClicked = viewModel::showCoverDialog,
|
||||||
onShareClicked = { shareManga(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource },
|
onShareClicked = { shareManga(context, viewModel.manga, viewModel.source) }.takeIf { isHttpSource },
|
||||||
onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
|
onDownloadActionClicked = viewModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() },
|
||||||
onEditCategoryClicked = screenModel::showChangeCategoryDialog.takeIf { successState.manga.favorite },
|
onEditCategoryClicked = viewModel::showChangeCategoryDialog.takeIf { successState.manga.favorite },
|
||||||
onEditFetchIntervalClicked = screenModel::showSetFetchIntervalDialog.takeIf {
|
onEditFetchIntervalClicked = viewModel::showSetFetchIntervalDialog.takeIf {
|
||||||
successState.manga.favorite
|
successState.manga.favorite
|
||||||
},
|
},
|
||||||
onMigrateClicked = {
|
onMigrateClicked = {
|
||||||
navigator.push(MigrationConfigScreen(successState.manga.id))
|
navigator.push(MigrationConfigScreen(successState.manga.id))
|
||||||
}.takeIf { successState.manga.favorite },
|
}.takeIf { successState.manga.favorite },
|
||||||
onEditNotesClicked = { navigator.push(MangaNotesScreen(manga = successState.manga)) },
|
onEditNotesClicked = { navigator.push(MangaNotesScreen(manga = successState.manga)) },
|
||||||
onMultiBookmarkClicked = screenModel::bookmarkChapters,
|
onMultiBookmarkClicked = viewModel::bookmarkChapters,
|
||||||
onMultiMarkAsReadClicked = screenModel::markChaptersRead,
|
onMultiMarkAsReadClicked = viewModel::markChaptersRead,
|
||||||
onMarkPreviousAsReadClicked = screenModel::markPreviousChapterRead,
|
onMarkPreviousAsReadClicked = viewModel::markPreviousChapterRead,
|
||||||
onMultiDeleteClicked = screenModel::showDeleteChapterDialog,
|
onMultiDeleteClicked = viewModel::showDeleteChapterDialog,
|
||||||
onChapterSwipe = screenModel::chapterSwipe,
|
onChapterSwipe = viewModel::chapterSwipe,
|
||||||
onChapterSelected = screenModel::toggleSelection,
|
onChapterSelected = viewModel::toggleSelection,
|
||||||
onAllChapterSelected = screenModel::toggleAllSelection,
|
onAllChapterSelected = viewModel::toggleAllSelection,
|
||||||
onInvertSelection = screenModel::invertSelection,
|
onInvertSelection = viewModel::invertSelection,
|
||||||
)
|
)
|
||||||
|
|
||||||
var showScanlatorsDialog by remember { mutableStateOf(false) }
|
var showScanlatorsDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val onDismissRequest = { screenModel.dismissDialog() }
|
val onDismissRequest = { viewModel.dismissDialog() }
|
||||||
when (val dialog = successState.dialog) {
|
when (val dialog = successState.dialog) {
|
||||||
null -> {}
|
null -> {}
|
||||||
is MangaScreenModel.Dialog.ChangeCategory -> {
|
is MangaViewModel.Dialog.ChangeCategory -> {
|
||||||
ChangeCategoryDialog(
|
ChangeCategoryDialog(
|
||||||
initialSelection = dialog.initialSelection,
|
initialSelection = dialog.initialSelection,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onEditCategories = { navigator.push(CategoryScreen()) },
|
onEditCategories = { navigator.push(CategoryScreen()) },
|
||||||
onConfirm = { include, _ ->
|
onConfirm = { include, _ ->
|
||||||
screenModel.moveMangaToCategoriesAndAddToLibrary(dialog.manga, include)
|
viewModel.moveMangaToCategoriesAndAddToLibrary(dialog.manga, include)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is MangaScreenModel.Dialog.DeleteChapters -> {
|
is MangaViewModel.Dialog.DeleteChapters -> {
|
||||||
DeleteChaptersDialog(
|
DeleteChaptersDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
screenModel.toggleAllSelection(false)
|
viewModel.toggleAllSelection(false)
|
||||||
screenModel.deleteChapters(dialog.chapters)
|
viewModel.deleteChapters(dialog.chapters)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is MangaScreenModel.Dialog.DuplicateManga -> {
|
is MangaViewModel.Dialog.DuplicateManga -> {
|
||||||
DuplicateMangaDialog(
|
DuplicateMangaDialog(
|
||||||
duplicates = dialog.duplicates,
|
duplicates = dialog.duplicates,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onConfirm = { screenModel.toggleFavorite(onRemoved = {}, checkDuplicate = false) },
|
onConfirm = { viewModel.toggleFavorite(onRemoved = {}, checkDuplicate = false) },
|
||||||
onOpenManga = { navigator.push(MangaScreen(it.id)) },
|
onOpenManga = { navigator.push(MangaScreen(it.id)) },
|
||||||
onMigrate = { screenModel.showMigrateDialog(it) },
|
onMigrate = { viewModel.showMigrateDialog(it) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is MangaScreenModel.Dialog.Migrate -> {
|
is MangaViewModel.Dialog.Migrate -> {
|
||||||
MigrateMangaDialog(
|
MigrateMangaDialog(
|
||||||
current = dialog.current,
|
current = dialog.current,
|
||||||
target = dialog.target,
|
target = dialog.target,
|
||||||
@@ -216,20 +219,20 @@ class MangaScreen(
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
MangaScreenModel.Dialog.SettingsSheet -> ChapterSettingsDialog(
|
MangaViewModel.Dialog.SettingsSheet -> ChapterSettingsDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
manga = successState.manga,
|
manga = successState.manga,
|
||||||
onDownloadFilterChanged = screenModel::setDownloadedFilter,
|
onDownloadFilterChanged = viewModel::setDownloadedFilter,
|
||||||
onUnreadFilterChanged = screenModel::setUnreadFilter,
|
onUnreadFilterChanged = viewModel::setUnreadFilter,
|
||||||
onBookmarkedFilterChanged = screenModel::setBookmarkedFilter,
|
onBookmarkedFilterChanged = viewModel::setBookmarkedFilter,
|
||||||
onSortModeChanged = screenModel::setSorting,
|
onSortModeChanged = viewModel::setSorting,
|
||||||
onDisplayModeChanged = screenModel::setDisplayMode,
|
onDisplayModeChanged = viewModel::setDisplayMode,
|
||||||
onSetAsDefault = screenModel::setCurrentSettingsAsDefault,
|
onSetAsDefault = viewModel::setCurrentSettingsAsDefault,
|
||||||
onResetToDefault = screenModel::resetToDefaultSettings,
|
onResetToDefault = viewModel::resetToDefaultSettings,
|
||||||
scanlatorFilterActive = successState.scanlatorFilterActive,
|
scanlatorFilterActive = successState.scanlatorFilterActive,
|
||||||
onScanlatorFilterClicked = { showScanlatorsDialog = true },
|
onScanlatorFilterClicked = { showScanlatorsDialog = true },
|
||||||
)
|
)
|
||||||
MangaScreenModel.Dialog.TrackSheet -> {
|
MangaViewModel.Dialog.TrackSheet -> {
|
||||||
NavigatorAdaptiveSheet(
|
NavigatorAdaptiveSheet(
|
||||||
screen = TrackInfoDialogHomeScreen(
|
screen = TrackInfoDialogHomeScreen(
|
||||||
mangaId = successState.manga.id,
|
mangaId = successState.manga.id,
|
||||||
@@ -240,8 +243,13 @@ class MangaScreen(
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
MangaScreenModel.Dialog.FullCover -> {
|
MangaViewModel.Dialog.FullCover -> {
|
||||||
val sm = rememberScreenModel { MangaCoverScreenModel(successState.manga.id) }
|
val sm = viewModel<MangaCoverViewModel>(
|
||||||
|
factory = MangaCoverViewModel.Factory,
|
||||||
|
extras = CreationExtras {
|
||||||
|
set(MangaCoverViewModel.MANGA_ID_KEY, successState.manga.id)
|
||||||
|
},
|
||||||
|
)
|
||||||
val manga by sm.state.collectAsState()
|
val manga by sm.state.collectAsState()
|
||||||
if (manga != null) {
|
if (manga != null) {
|
||||||
val getContent = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
|
val getContent = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
|
||||||
@@ -266,13 +274,13 @@ class MangaScreen(
|
|||||||
LoadingScreen(Modifier.systemBarsPadding())
|
LoadingScreen(Modifier.systemBarsPadding())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is MangaScreenModel.Dialog.SetFetchInterval -> {
|
is MangaViewModel.Dialog.SetFetchInterval -> {
|
||||||
SetIntervalDialog(
|
SetIntervalDialog(
|
||||||
interval = dialog.manga.fetchInterval,
|
interval = dialog.manga.fetchInterval,
|
||||||
nextUpdate = dialog.manga.expectedNextUpdate,
|
nextUpdate = dialog.manga.expectedNextUpdate,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onValueChanged = { interval: Int -> screenModel.setFetchInterval(dialog.manga, interval) }
|
onValueChanged = { interval: Int -> viewModel.setFetchInterval(dialog.manga, interval) }
|
||||||
.takeIf { screenModel.isUpdateIntervalEnabled },
|
.takeIf { viewModel.isUpdateIntervalEnabled },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,7 +290,7 @@ class MangaScreen(
|
|||||||
availableScanlators = successState.availableScanlators,
|
availableScanlators = successState.availableScanlators,
|
||||||
excludedScanlators = successState.excludedScanlators,
|
excludedScanlators = successState.excludedScanlators,
|
||||||
onDismissRequest = { showScanlatorsDialog = false },
|
onDismissRequest = { showScanlatorsDialog = false },
|
||||||
onConfirm = screenModel::setExcludedScanlators,
|
onConfirm = viewModel::setExcludedScanlators,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-44
@@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.manga
|
package eu.kanade.tachiyomi.ui.manga
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.material3.SnackbarDuration
|
import androidx.compose.material3.SnackbarDuration
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
@@ -7,10 +8,10 @@ import androidx.compose.material3.SnackbarResult
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.flowWithLifecycle
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.core.util.addOrRemove
|
import eu.kanade.core.util.addOrRemove
|
||||||
import eu.kanade.core.util.insertSeparators
|
import eu.kanade.core.util.insertSeparators
|
||||||
@@ -49,6 +50,7 @@ import kotlinx.coroutines.flow.update
|
|||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||||
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
@@ -85,9 +87,8 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
class MangaScreenModel(
|
class MangaViewModel(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val lifecycle: Lifecycle,
|
|
||||||
private val mangaId: Long,
|
private val mangaId: Long,
|
||||||
private val isFromSource: Boolean,
|
private val isFromSource: Boolean,
|
||||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||||
@@ -115,7 +116,23 @@ class MangaScreenModel(
|
|||||||
private val filterChaptersForDownload: FilterChaptersForDownload = Injekt.get(),
|
private val filterChaptersForDownload: FilterChaptersForDownload = Injekt.get(),
|
||||||
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get(),
|
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get(),
|
||||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||||
) : StateScreenModel<MangaScreenModel.State>(State.Loading) {
|
) : StateViewModel<MangaViewModel.State>(State.Loading) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val MANGA_ID_KEY = CreationExtras.Key<Long>()
|
||||||
|
|
||||||
|
val IS_FROM_SOURCE_KEY = CreationExtras.Key<Boolean>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
MangaViewModel(
|
||||||
|
context = Injekt.get<Application>(),
|
||||||
|
mangaId = get(MANGA_ID_KEY)!!,
|
||||||
|
isFromSource = get(IS_FROM_SOURCE_KEY)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val successState: State.Success?
|
private val successState: State.Success?
|
||||||
get() = state.value as? State.Success
|
get() = state.value as? State.Success
|
||||||
@@ -139,7 +156,7 @@ class MangaScreenModel(
|
|||||||
val chapterSwipeEndAction = libraryPreferences.swipeToStartAction.get()
|
val chapterSwipeEndAction = libraryPreferences.swipeToStartAction.get()
|
||||||
var autoTrackState = trackPreferences.autoUpdateTrackOnMarkRead.get()
|
var autoTrackState = trackPreferences.autoUpdateTrackOnMarkRead.get()
|
||||||
|
|
||||||
private val skipFiltered by readerPreferences.skipFiltered.asState(screenModelScope)
|
private val skipFiltered by readerPreferences.skipFiltered.asState(viewModelScope)
|
||||||
|
|
||||||
val isUpdateIntervalEnabled =
|
val isUpdateIntervalEnabled =
|
||||||
LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateMangaRestrictions.get()
|
LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateMangaRestrictions.get()
|
||||||
@@ -160,13 +177,12 @@ class MangaScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
combine(
|
combine(
|
||||||
getMangaAndChapters.subscribe(mangaId, applyScanlatorFilter = true).distinctUntilChanged(),
|
getMangaAndChapters.subscribe(mangaId, applyScanlatorFilter = true).distinctUntilChanged(),
|
||||||
downloadCache.changes,
|
downloadCache.changes,
|
||||||
downloadManager.queueState,
|
downloadManager.queueState,
|
||||||
) { mangaAndChapters, _, _ -> mangaAndChapters }
|
) { mangaAndChapters, _, _ -> mangaAndChapters }
|
||||||
.flowWithLifecycle(lifecycle)
|
|
||||||
.collectLatest { (manga, chapters) ->
|
.collectLatest { (manga, chapters) ->
|
||||||
updateSuccessState {
|
updateSuccessState {
|
||||||
it.copy(
|
it.copy(
|
||||||
@@ -177,9 +193,8 @@ class MangaScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
getExcludedScanlators.subscribe(mangaId)
|
getExcludedScanlators.subscribe(mangaId)
|
||||||
.flowWithLifecycle(lifecycle)
|
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.collectLatest { excludedScanlators ->
|
.collectLatest { excludedScanlators ->
|
||||||
updateSuccessState {
|
updateSuccessState {
|
||||||
@@ -188,9 +203,8 @@ class MangaScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
getAvailableScanlators.subscribe(mangaId)
|
getAvailableScanlators.subscribe(mangaId)
|
||||||
.flowWithLifecycle(lifecycle)
|
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.collectLatest { availableScanlators ->
|
.collectLatest { availableScanlators ->
|
||||||
updateSuccessState {
|
updateSuccessState {
|
||||||
@@ -201,7 +215,7 @@ class MangaScreenModel(
|
|||||||
|
|
||||||
observeDownloads()
|
observeDownloads()
|
||||||
|
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val manga = getMangaAndChapters.awaitManga(mangaId)
|
val manga = getMangaAndChapters.awaitManga(mangaId)
|
||||||
val chapters = getMangaAndChapters.awaitChapters(mangaId, applyScanlatorFilter = true)
|
val chapters = getMangaAndChapters.awaitChapters(mangaId, applyScanlatorFilter = true)
|
||||||
.toChapterListItems(manga)
|
.toChapterListItems(manga)
|
||||||
@@ -232,7 +246,7 @@ class MangaScreenModel(
|
|||||||
observeTrackers()
|
observeTrackers()
|
||||||
|
|
||||||
// Fetch info-chapters when needed
|
// Fetch info-chapters when needed
|
||||||
if ((needRefreshInfo || needRefreshChapter) && screenModelScope.isActive) {
|
if ((needRefreshInfo || needRefreshChapter) && viewModelScope.isActive) {
|
||||||
fetchAllFromSource(
|
fetchAllFromSource(
|
||||||
manualFetch = false,
|
manualFetch = false,
|
||||||
fetchDetails = needRefreshInfo,
|
fetchDetails = needRefreshInfo,
|
||||||
@@ -246,7 +260,7 @@ class MangaScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun fetchAllFromSource(manualFetch: Boolean = true) {
|
fun fetchAllFromSource(manualFetch: Boolean = true) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
updateSuccessState { it.copy(isRefreshingData = true) }
|
updateSuccessState { it.copy(isRefreshingData = true) }
|
||||||
fetchAllFromSource(
|
fetchAllFromSource(
|
||||||
manualFetch = manualFetch,
|
manualFetch = manualFetch,
|
||||||
@@ -288,7 +302,7 @@ class MangaScreenModel(
|
|||||||
with(context) { e.formattedMessage }
|
with(context) { e.formattedMessage }
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
snackbarHostState.showSnackbar(message = message)
|
snackbarHostState.showSnackbar(message = message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,7 +313,7 @@ class MangaScreenModel(
|
|||||||
fun toggleFavorite() {
|
fun toggleFavorite() {
|
||||||
toggleFavorite(
|
toggleFavorite(
|
||||||
onRemoved = {
|
onRemoved = {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (!hasDownloads()) return@launch
|
if (!hasDownloads()) return@launch
|
||||||
val result = snackbarHostState.showSnackbar(
|
val result = snackbarHostState.showSnackbar(
|
||||||
message = context.stringResource(MR.strings.delete_downloads_for_manga),
|
message = context.stringResource(MR.strings.delete_downloads_for_manga),
|
||||||
@@ -322,7 +336,7 @@ class MangaScreenModel(
|
|||||||
checkDuplicate: Boolean = true,
|
checkDuplicate: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val state = successState ?: return
|
val state = successState ?: return
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val manga = state.manga
|
val manga = state.manga
|
||||||
|
|
||||||
if (isFavorited) {
|
if (isFavorited) {
|
||||||
@@ -377,7 +391,7 @@ class MangaScreenModel(
|
|||||||
|
|
||||||
fun showChangeCategoryDialog() {
|
fun showChangeCategoryDialog() {
|
||||||
val manga = successState?.manga ?: return
|
val manga = successState?.manga ?: return
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
val categories = getCategories()
|
val categories = getCategories()
|
||||||
val selection = getMangaCategoryIds(manga)
|
val selection = getMangaCategoryIds(manga)
|
||||||
updateSuccessState { successState ->
|
updateSuccessState { successState ->
|
||||||
@@ -399,7 +413,7 @@ class MangaScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setFetchInterval(manga: Manga, interval: Int) {
|
fun setFetchInterval(manga: Manga, interval: Int) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
if (
|
if (
|
||||||
updateManga.awaitUpdateFetchInterval(
|
updateManga.awaitUpdateFetchInterval(
|
||||||
// Custom intervals are negative
|
// Custom intervals are negative
|
||||||
@@ -452,7 +466,7 @@ class MangaScreenModel(
|
|||||||
moveMangaToCategory(categories)
|
moveMangaToCategory(categories)
|
||||||
if (manga.favorite) return
|
if (manga.favorite) return
|
||||||
|
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
updateManga.awaitUpdateFavorite(manga.id, true)
|
updateManga.awaitUpdateFavorite(manga.id, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,7 +482,7 @@ class MangaScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun moveMangaToCategory(categoryIds: List<Long>) {
|
private fun moveMangaToCategory(categoryIds: List<Long>) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
setMangaCategories.await(mangaId, categoryIds)
|
setMangaCategories.await(mangaId, categoryIds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -487,11 +501,10 @@ class MangaScreenModel(
|
|||||||
// Chapters list - start
|
// Chapters list - start
|
||||||
|
|
||||||
private fun observeDownloads() {
|
private fun observeDownloads() {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
downloadManager.statusFlow()
|
downloadManager.statusFlow()
|
||||||
.filter { it.manga.id == successState?.manga?.id }
|
.filter { it.manga.id == successState?.manga?.id }
|
||||||
.catch { error -> logcat(LogPriority.ERROR, error) }
|
.catch { error -> logcat(LogPriority.ERROR, error) }
|
||||||
.flowWithLifecycle(lifecycle)
|
|
||||||
.collect {
|
.collect {
|
||||||
withUIContext {
|
withUIContext {
|
||||||
updateDownloadState(it)
|
updateDownloadState(it)
|
||||||
@@ -499,11 +512,10 @@ class MangaScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
downloadManager.progressFlow()
|
downloadManager.progressFlow()
|
||||||
.filter { it.manga.id == successState?.manga?.id }
|
.filter { it.manga.id == successState?.manga?.id }
|
||||||
.catch { error -> logcat(LogPriority.ERROR, error) }
|
.catch { error -> logcat(LogPriority.ERROR, error) }
|
||||||
.flowWithLifecycle(lifecycle)
|
|
||||||
.collect {
|
.collect {
|
||||||
withUIContext {
|
withUIContext {
|
||||||
updateDownloadState(it)
|
updateDownloadState(it)
|
||||||
@@ -564,7 +576,7 @@ class MangaScreenModel(
|
|||||||
* @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled]
|
* @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled]
|
||||||
*/
|
*/
|
||||||
fun chapterSwipe(chapterItem: ChapterList.Item, swipeAction: LibraryPreferences.ChapterSwipeAction) {
|
fun chapterSwipe(chapterItem: ChapterList.Item, swipeAction: LibraryPreferences.ChapterSwipeAction) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
executeChapterSwipeAction(chapterItem, swipeAction)
|
executeChapterSwipeAction(chapterItem, swipeAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -637,7 +649,7 @@ class MangaScreenModel(
|
|||||||
) {
|
) {
|
||||||
val successState = successState ?: return
|
val successState = successState ?: return
|
||||||
|
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
if (startNow) {
|
if (startNow) {
|
||||||
val chapterId = chapters.singleOrNull()?.id ?: return@launchNonCancellable
|
val chapterId = chapters.singleOrNull()?.id ?: return@launchNonCancellable
|
||||||
downloadManager.startDownloadNow(chapterId)
|
downloadManager.startDownloadNow(chapterId)
|
||||||
@@ -722,7 +734,7 @@ class MangaScreenModel(
|
|||||||
fun markChaptersRead(chapters: List<Chapter>, read: Boolean) {
|
fun markChaptersRead(chapters: List<Chapter>, read: Boolean) {
|
||||||
toggleAllSelection(false)
|
toggleAllSelection(false)
|
||||||
if (chapters.isEmpty()) return
|
if (chapters.isEmpty()) return
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
setReadStatus.await(
|
setReadStatus.await(
|
||||||
read = read,
|
read = read,
|
||||||
chapters = chapters.toTypedArray(),
|
chapters = chapters.toTypedArray(),
|
||||||
@@ -796,7 +808,7 @@ class MangaScreenModel(
|
|||||||
* @param chapters the list of chapters to bookmark.
|
* @param chapters the list of chapters to bookmark.
|
||||||
*/
|
*/
|
||||||
fun bookmarkChapters(chapters: List<Chapter>, bookmarked: Boolean) {
|
fun bookmarkChapters(chapters: List<Chapter>, bookmarked: Boolean) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
chapters
|
chapters
|
||||||
.filterNot { it.bookmark == bookmarked }
|
.filterNot { it.bookmark == bookmarked }
|
||||||
.map { ChapterUpdate(id = it.id, bookmark = bookmarked) }
|
.map { ChapterUpdate(id = it.id, bookmark = bookmarked) }
|
||||||
@@ -811,7 +823,7 @@ class MangaScreenModel(
|
|||||||
* @param chapters the list of chapters to delete.
|
* @param chapters the list of chapters to delete.
|
||||||
*/
|
*/
|
||||||
fun deleteChapters(chapters: List<Chapter>) {
|
fun deleteChapters(chapters: List<Chapter>) {
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
try {
|
try {
|
||||||
successState?.let { state ->
|
successState?.let { state ->
|
||||||
downloadManager.deleteChapters(
|
downloadManager.deleteChapters(
|
||||||
@@ -827,7 +839,7 @@ class MangaScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadNewChapters(chapters: List<Chapter>) {
|
private fun downloadNewChapters(chapters: List<Chapter>) {
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
val manga = successState?.manga ?: return@launchNonCancellable
|
val manga = successState?.manga ?: return@launchNonCancellable
|
||||||
val chaptersToDownload = filterChaptersForDownload.await(manga, chapters)
|
val chaptersToDownload = filterChaptersForDownload.await(manga, chapters)
|
||||||
|
|
||||||
@@ -849,7 +861,7 @@ class MangaScreenModel(
|
|||||||
TriState.ENABLED_IS -> Manga.CHAPTER_SHOW_UNREAD
|
TriState.ENABLED_IS -> Manga.CHAPTER_SHOW_UNREAD
|
||||||
TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_READ
|
TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_READ
|
||||||
}
|
}
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
setMangaChapterFlags.awaitSetUnreadFilter(manga, flag)
|
setMangaChapterFlags.awaitSetUnreadFilter(manga, flag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -867,7 +879,7 @@ class MangaScreenModel(
|
|||||||
TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_DOWNLOADED
|
TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_DOWNLOADED
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
setMangaChapterFlags.awaitSetDownloadedFilter(manga, flag)
|
setMangaChapterFlags.awaitSetDownloadedFilter(manga, flag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -885,7 +897,7 @@ class MangaScreenModel(
|
|||||||
TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_BOOKMARKED
|
TriState.ENABLED_NOT -> Manga.CHAPTER_SHOW_NOT_BOOKMARKED
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
setMangaChapterFlags.awaitSetBookmarkFilter(manga, flag)
|
setMangaChapterFlags.awaitSetBookmarkFilter(manga, flag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -897,7 +909,7 @@ class MangaScreenModel(
|
|||||||
fun setDisplayMode(mode: Long) {
|
fun setDisplayMode(mode: Long) {
|
||||||
val manga = successState?.manga ?: return
|
val manga = successState?.manga ?: return
|
||||||
|
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
setMangaChapterFlags.awaitSetDisplayMode(manga, mode)
|
setMangaChapterFlags.awaitSetDisplayMode(manga, mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -909,14 +921,14 @@ class MangaScreenModel(
|
|||||||
fun setSorting(sort: Long) {
|
fun setSorting(sort: Long) {
|
||||||
val manga = successState?.manga ?: return
|
val manga = successState?.manga ?: return
|
||||||
|
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
setMangaChapterFlags.awaitSetSortingModeOrFlipOrder(manga, sort)
|
setMangaChapterFlags.awaitSetSortingModeOrFlipOrder(manga, sort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setCurrentSettingsAsDefault(applyToExisting: Boolean) {
|
fun setCurrentSettingsAsDefault(applyToExisting: Boolean) {
|
||||||
val manga = successState?.manga ?: return
|
val manga = successState?.manga ?: return
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
libraryPreferences.setChapterSettingsDefault(manga)
|
libraryPreferences.setChapterSettingsDefault(manga)
|
||||||
if (applyToExisting) {
|
if (applyToExisting) {
|
||||||
setMangaDefaultChapterFlags.awaitAll()
|
setMangaDefaultChapterFlags.awaitAll()
|
||||||
@@ -927,7 +939,7 @@ class MangaScreenModel(
|
|||||||
|
|
||||||
fun resetToDefaultSettings() {
|
fun resetToDefaultSettings() {
|
||||||
val manga = successState?.manga ?: return
|
val manga = successState?.manga ?: return
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
setMangaDefaultChapterFlags.await(manga)
|
setMangaDefaultChapterFlags.await(manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1026,7 +1038,7 @@ class MangaScreenModel(
|
|||||||
private fun observeTrackers() {
|
private fun observeTrackers() {
|
||||||
val manga = successState?.manga ?: return
|
val manga = successState?.manga ?: return
|
||||||
|
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
combine(
|
combine(
|
||||||
getTracks.subscribe(manga.id).catch { logcat(LogPriority.ERROR, it) },
|
getTracks.subscribe(manga.id).catch { logcat(LogPriority.ERROR, it) },
|
||||||
trackerManager.loggedInTrackersFlow(),
|
trackerManager.loggedInTrackersFlow(),
|
||||||
@@ -1037,7 +1049,6 @@ class MangaScreenModel(
|
|||||||
val supportedTrackerTracks = mangaTracks.filter { it.trackerId in supportedTrackerIds }
|
val supportedTrackerTracks = mangaTracks.filter { it.trackerId in supportedTrackerIds }
|
||||||
supportedTrackerTracks.size to supportedTrackers.isNotEmpty()
|
supportedTrackerTracks.size to supportedTrackers.isNotEmpty()
|
||||||
}
|
}
|
||||||
.flowWithLifecycle(lifecycle)
|
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.collectLatest { (trackingCount, hasLoggedInTrackers) ->
|
.collectLatest { (trackingCount, hasLoggedInTrackers) ->
|
||||||
updateSuccessState {
|
updateSuccessState {
|
||||||
@@ -1092,7 +1103,7 @@ class MangaScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setExcludedScanlators(excludedScanlators: Set<String>) {
|
fun setExcludedScanlators(excludedScanlators: Set<String>) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
setExcludedScanlators.await(mangaId, excludedScanlators)
|
setExcludedScanlators.await(mangaId, excludedScanlators)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,14 +4,17 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.manga.MangaNotesScreen
|
import eu.kanade.presentation.manga.MangaNotesScreen
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||||
import tachiyomi.domain.manga.interactor.UpdateMangaNotes
|
import tachiyomi.domain.manga.interactor.UpdateMangaNotes
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
@@ -25,20 +28,37 @@ class MangaNotesScreen(
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { Model(manga) }
|
val viewModel = viewModel<Model>(
|
||||||
val state by screenModel.state.collectAsState()
|
factory = Model.Factory,
|
||||||
|
extras = CreationExtras {
|
||||||
|
set(Model.MANGA_KEY, manga)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
MangaNotesScreen(
|
MangaNotesScreen(
|
||||||
state = state,
|
state = state,
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
onUpdate = screenModel::updateNotes,
|
onUpdate = viewModel::updateNotes,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Model(
|
private class Model(
|
||||||
private val manga: Manga,
|
private val manga: Manga,
|
||||||
private val updateMangaNotes: UpdateMangaNotes = Injekt.get(),
|
private val updateMangaNotes: UpdateMangaNotes = Injekt.get(),
|
||||||
) : StateScreenModel<State>(State(manga, manga.notes)) {
|
) : StateViewModel<State>(State(manga, manga.notes)) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val MANGA_KEY = CreationExtras.Key<Manga>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
Model(
|
||||||
|
manga = get(MANGA_KEY)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateNotes(content: String) {
|
fun updateNotes(content: String) {
|
||||||
if (content == state.value.notes) return
|
if (content == state.value.notes) return
|
||||||
@@ -47,7 +67,7 @@ class MangaNotesScreen(
|
|||||||
it.copy(notes = content)
|
it.copy(notes = content)
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
updateMangaNotes(manga.id, content)
|
updateMangaNotes(manga.id, content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,10 +30,12 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import androidx.lifecycle.ViewModel
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
@@ -65,6 +67,7 @@ import kotlinx.coroutines.flow.map
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||||
import tachiyomi.core.common.util.lang.withIOContext
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
@@ -96,10 +99,16 @@ data class TrackInfoDialogHomeScreen(
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val screenModel = rememberScreenModel { Model(mangaId, sourceId) }
|
val viewModel = viewModel<Model>(
|
||||||
|
factory = Model.Factory,
|
||||||
|
extras = CreationExtras {
|
||||||
|
set(Model.MANGA_ID_KEY, mangaId)
|
||||||
|
set(Model.SOURCE_ID_KEY, sourceId)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
val dateFormat = remember { UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat.get()) }
|
val dateFormat = remember { UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat.get()) }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
TrackInfoDialogHome(
|
TrackInfoDialogHome(
|
||||||
trackItems = state.trackItems,
|
trackItems = state.trackItems,
|
||||||
@@ -148,7 +157,7 @@ data class TrackInfoDialogHomeScreen(
|
|||||||
},
|
},
|
||||||
onNewSearch = {
|
onNewSearch = {
|
||||||
if (it.tracker is EnhancedTracker) {
|
if (it.tracker is EnhancedTracker) {
|
||||||
screenModel.registerEnhancedTracking(it)
|
viewModel.registerEnhancedTracking(it)
|
||||||
} else {
|
} else {
|
||||||
navigator.push(
|
navigator.push(
|
||||||
TrackerSearchScreen(
|
TrackerSearchScreen(
|
||||||
@@ -171,7 +180,7 @@ data class TrackInfoDialogHomeScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
onCopyLink = { context.copyTrackerLink(it) },
|
onCopyLink = { context.copyTrackerLink(it) },
|
||||||
onTogglePrivate = screenModel::togglePrivate,
|
onTogglePrivate = viewModel::togglePrivate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,14 +205,28 @@ data class TrackInfoDialogHomeScreen(
|
|||||||
private val mangaId: Long,
|
private val mangaId: Long,
|
||||||
private val sourceId: Long,
|
private val sourceId: Long,
|
||||||
private val getTracks: GetTracks = Injekt.get(),
|
private val getTracks: GetTracks = Injekt.get(),
|
||||||
) : StateScreenModel<Model.State>(State()) {
|
) : StateViewModel<Model.State>(State()) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val MANGA_ID_KEY = CreationExtras.Key<Long>()
|
||||||
|
val SOURCE_ID_KEY = CreationExtras.Key<Long>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
Model(
|
||||||
|
mangaId = get(MANGA_ID_KEY)!!,
|
||||||
|
sourceId = get(SOURCE_ID_KEY)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
refreshTrackers()
|
refreshTrackers()
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
getTracks.subscribe(mangaId)
|
getTracks.subscribe(mangaId)
|
||||||
.catch { logcat(LogPriority.ERROR, it) }
|
.catch { logcat(LogPriority.ERROR, it) }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
@@ -214,7 +237,7 @@ data class TrackInfoDialogHomeScreen(
|
|||||||
|
|
||||||
fun registerEnhancedTracking(item: TrackItem) {
|
fun registerEnhancedTracking(item: TrackItem) {
|
||||||
item.tracker as EnhancedTracker
|
item.tracker as EnhancedTracker
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
val manga = Injekt.get<GetManga>().await(mangaId) ?: return@launchNonCancellable
|
val manga = Injekt.get<GetManga>().await(mangaId) ?: return@launchNonCancellable
|
||||||
try {
|
try {
|
||||||
val matchResult = item.tracker.match(manga) ?: throw Exception()
|
val matchResult = item.tracker.match(manga) ?: throw Exception()
|
||||||
@@ -248,7 +271,7 @@ data class TrackInfoDialogHomeScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun togglePrivate(item: TrackItem) {
|
fun togglePrivate(item: TrackItem) {
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
item.tracker.setRemotePrivate(item.track!!.toDbTrack(), !item.track.private)
|
item.tracker.setRemotePrivate(item.track!!.toDbTrack(), !item.track.private)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,19 +301,20 @@ private data class TrackStatusSelectorScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel {
|
val viewModel = viewModel<Model>(
|
||||||
Model(
|
factory = Model.Factory,
|
||||||
track = track,
|
extras = CreationExtras {
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
set(Model.TRACK_KEY, track)
|
||||||
|
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
val state by viewModel.state.collectAsState()
|
||||||
val state by screenModel.state.collectAsState()
|
|
||||||
TrackStatusSelector(
|
TrackStatusSelector(
|
||||||
selection = state.selection,
|
selection = state.selection,
|
||||||
onSelectionChange = screenModel::setSelection,
|
onSelectionChange = viewModel::setSelection,
|
||||||
selections = remember { screenModel.getSelections() },
|
selections = remember { viewModel.getSelections() },
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
screenModel.setStatus()
|
viewModel.setStatus()
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
onDismissRequest = navigator::pop,
|
onDismissRequest = navigator::pop,
|
||||||
@@ -300,7 +324,21 @@ private data class TrackStatusSelectorScreen(
|
|||||||
private class Model(
|
private class Model(
|
||||||
private val track: Track,
|
private val track: Track,
|
||||||
private val tracker: Tracker,
|
private val tracker: Tracker,
|
||||||
) : StateScreenModel<Model.State>(State(track.status)) {
|
) : StateViewModel<Model.State>(State(track.status)) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TRACK_KEY = CreationExtras.Key<Track>()
|
||||||
|
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
Model(
|
||||||
|
track = get(TRACK_KEY)!!,
|
||||||
|
tracker = get(TRACKER_KEY)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getSelections(): Map<Long, StringResource?> {
|
fun getSelections(): Map<Long, StringResource?> {
|
||||||
return tracker.getStatusList().associateWith { tracker.getStatus(it) }
|
return tracker.getStatusList().associateWith { tracker.getStatus(it) }
|
||||||
@@ -311,7 +349,7 @@ private data class TrackStatusSelectorScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setStatus() {
|
fun setStatus() {
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
tracker.setRemoteStatus(track.toDbTrack(), state.value.selection)
|
tracker.setRemoteStatus(track.toDbTrack(), state.value.selection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,20 +369,21 @@ private data class TrackChapterSelectorScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel {
|
val viewModel = viewModel<Model>(
|
||||||
Model(
|
factory = Model.Factory,
|
||||||
track = track,
|
extras = CreationExtras {
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
set(Model.TRACK_KEY, track)
|
||||||
|
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
val state by viewModel.state.collectAsState()
|
||||||
val state by screenModel.state.collectAsState()
|
|
||||||
|
|
||||||
TrackChapterSelector(
|
TrackChapterSelector(
|
||||||
selection = state.selection,
|
selection = state.selection,
|
||||||
onSelectionChange = screenModel::setSelection,
|
onSelectionChange = viewModel::setSelection,
|
||||||
range = remember { screenModel.getRange() },
|
range = remember { viewModel.getRange() },
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
screenModel.setChapter()
|
viewModel.setChapter()
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
onDismissRequest = navigator::pop,
|
onDismissRequest = navigator::pop,
|
||||||
@@ -354,7 +393,21 @@ private data class TrackChapterSelectorScreen(
|
|||||||
private class Model(
|
private class Model(
|
||||||
private val track: Track,
|
private val track: Track,
|
||||||
private val tracker: Tracker,
|
private val tracker: Tracker,
|
||||||
) : StateScreenModel<Model.State>(State(track.lastChapterRead.toInt())) {
|
) : StateViewModel<Model.State>(State(track.lastChapterRead.toInt())) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TRACK_KEY = CreationExtras.Key<Track>()
|
||||||
|
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
Model(
|
||||||
|
track = get(TRACK_KEY)!!,
|
||||||
|
tracker = get(TRACKER_KEY)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getRange(): Iterable<Int> {
|
fun getRange(): Iterable<Int> {
|
||||||
val endRange = if (track.totalChapters > 0) {
|
val endRange = if (track.totalChapters > 0) {
|
||||||
@@ -370,7 +423,7 @@ private data class TrackChapterSelectorScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setChapter() {
|
fun setChapter() {
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
tracker.setRemoteLastChapterRead(track.toDbTrack(), state.value.selection)
|
tracker.setRemoteLastChapterRead(track.toDbTrack(), state.value.selection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,20 +443,21 @@ private data class TrackScoreSelectorScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel {
|
val viewModel = viewModel<Model>(
|
||||||
Model(
|
factory = Model.Factory,
|
||||||
track = track,
|
extras = CreationExtras {
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
set(Model.TRACK_KEY, track)
|
||||||
|
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
val state by viewModel.state.collectAsState()
|
||||||
val state by screenModel.state.collectAsState()
|
|
||||||
|
|
||||||
TrackScoreSelector(
|
TrackScoreSelector(
|
||||||
selection = state.selection,
|
selection = state.selection,
|
||||||
onSelectionChange = screenModel::setSelection,
|
onSelectionChange = viewModel::setSelection,
|
||||||
selections = remember { screenModel.getSelections() },
|
selections = remember { viewModel.getSelections() },
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
screenModel.setScore()
|
viewModel.setScore()
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
onDismissRequest = navigator::pop,
|
onDismissRequest = navigator::pop,
|
||||||
@@ -413,7 +467,21 @@ private data class TrackScoreSelectorScreen(
|
|||||||
private class Model(
|
private class Model(
|
||||||
private val track: Track,
|
private val track: Track,
|
||||||
private val tracker: Tracker,
|
private val tracker: Tracker,
|
||||||
) : StateScreenModel<Model.State>(State(tracker.displayScore(track))) {
|
) : StateViewModel<Model.State>(State(tracker.displayScore(track))) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TRACK_KEY = CreationExtras.Key<Track>()
|
||||||
|
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
Model(
|
||||||
|
track = get(TRACK_KEY)!!,
|
||||||
|
tracker = get(TRACKER_KEY)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getSelections(): List<String> {
|
fun getSelections(): List<String> {
|
||||||
return tracker.getScoreList()
|
return tracker.getScoreList()
|
||||||
@@ -424,7 +492,7 @@ private data class TrackScoreSelectorScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setScore() {
|
fun setScore() {
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
tracker.setRemoteScore(track.toDbTrack(), state.value.selection)
|
tracker.setRemoteScore(track.toDbTrack(), state.value.selection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -492,13 +560,14 @@ private data class TrackDateSelectorScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel {
|
val viewModel = viewModel<Model>(
|
||||||
Model(
|
factory = Model.Factory,
|
||||||
track = track,
|
extras = CreationExtras {
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
set(Model.TRACK_KEY, track)
|
||||||
start = start,
|
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||||
|
set(Model.START_KEY, start)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
val canRemove = if (start) {
|
val canRemove = if (start) {
|
||||||
track.startDate > 0
|
track.startDate > 0
|
||||||
@@ -511,13 +580,13 @@ private data class TrackDateSelectorScreen(
|
|||||||
} else {
|
} else {
|
||||||
stringResource(MR.strings.track_finished_reading_date)
|
stringResource(MR.strings.track_finished_reading_date)
|
||||||
},
|
},
|
||||||
initialSelectedDateMillis = screenModel.initialSelection,
|
initialSelectedDateMillis = viewModel.initialSelection,
|
||||||
selectableDates = selectableDates,
|
selectableDates = selectableDates,
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
screenModel.setDate(it)
|
viewModel.setDate(it)
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
onRemove = { screenModel.confirmRemoveDate(navigator) }.takeIf { canRemove },
|
onRemove = { viewModel.confirmRemoveDate(navigator) }.takeIf { canRemove },
|
||||||
onDismissRequest = navigator::pop,
|
onDismissRequest = navigator::pop,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -526,7 +595,23 @@ private data class TrackDateSelectorScreen(
|
|||||||
private val track: Track,
|
private val track: Track,
|
||||||
private val tracker: Tracker,
|
private val tracker: Tracker,
|
||||||
private val start: Boolean,
|
private val start: Boolean,
|
||||||
) : ScreenModel {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TRACK_KEY = CreationExtras.Key<Track>()
|
||||||
|
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||||
|
val START_KEY = CreationExtras.Key<Boolean>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
Model(
|
||||||
|
track = get(TRACK_KEY) as Track,
|
||||||
|
tracker = get(TRACKER_KEY) as Tracker,
|
||||||
|
start = get(START_KEY) as Boolean,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// In UTC
|
// In UTC
|
||||||
val initialSelection: Long
|
val initialSelection: Long
|
||||||
@@ -541,7 +626,7 @@ private data class TrackDateSelectorScreen(
|
|||||||
fun setDate(millis: Long) {
|
fun setDate(millis: Long) {
|
||||||
// Convert to local time
|
// Convert to local time
|
||||||
val localMillis = millis.convertEpochMillisZone(ZoneOffset.UTC, ZoneOffset.systemDefault())
|
val localMillis = millis.convertEpochMillisZone(ZoneOffset.UTC, ZoneOffset.systemDefault())
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
if (start) {
|
if (start) {
|
||||||
tracker.setRemoteStartDate(track.toDbTrack(), localMillis)
|
tracker.setRemoteStartDate(track.toDbTrack(), localMillis)
|
||||||
} else {
|
} else {
|
||||||
@@ -565,13 +650,14 @@ private data class TrackDateRemoverScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel {
|
val viewModel = viewModel<Model>(
|
||||||
Model(
|
factory = Model.Factory,
|
||||||
track = track,
|
extras = CreationExtras {
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
set(Model.TRACK_KEY, track)
|
||||||
start = start,
|
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||||
|
set(Model.START_KEY, start)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
AlertDialogContent(
|
AlertDialogContent(
|
||||||
modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars),
|
modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars),
|
||||||
icon = {
|
icon = {
|
||||||
@@ -587,7 +673,7 @@ private data class TrackDateRemoverScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
val serviceName = screenModel.getServiceName()
|
val serviceName = viewModel.getServiceName()
|
||||||
Text(
|
Text(
|
||||||
text = if (start) {
|
text = if (start) {
|
||||||
stringResource(MR.strings.track_remove_start_date_conf_text, serviceName)
|
stringResource(MR.strings.track_remove_start_date_conf_text, serviceName)
|
||||||
@@ -606,7 +692,7 @@ private data class TrackDateRemoverScreen(
|
|||||||
}
|
}
|
||||||
FilledTonalButton(
|
FilledTonalButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
screenModel.removeDate()
|
viewModel.removeDate()
|
||||||
navigator.popUntil { it is TrackInfoDialogHomeScreen }
|
navigator.popUntil { it is TrackInfoDialogHomeScreen }
|
||||||
},
|
},
|
||||||
colors = ButtonDefaults.filledTonalButtonColors(
|
colors = ButtonDefaults.filledTonalButtonColors(
|
||||||
@@ -625,12 +711,28 @@ private data class TrackDateRemoverScreen(
|
|||||||
private val track: Track,
|
private val track: Track,
|
||||||
private val tracker: Tracker,
|
private val tracker: Tracker,
|
||||||
private val start: Boolean,
|
private val start: Boolean,
|
||||||
) : ScreenModel {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val TRACK_KEY = CreationExtras.Key<Track>()
|
||||||
|
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||||
|
val START_KEY = CreationExtras.Key<Boolean>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
Model(
|
||||||
|
track = get(TRACK_KEY) as Track,
|
||||||
|
tracker = get(TRACKER_KEY) as Tracker,
|
||||||
|
start = get(START_KEY) as Boolean,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getServiceName() = tracker.name
|
fun getServiceName() = tracker.name
|
||||||
|
|
||||||
fun removeDate() {
|
fun removeDate() {
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
if (start) {
|
if (start) {
|
||||||
tracker.setRemoteStartDate(track.toDbTrack(), 0)
|
tracker.setRemoteStartDate(track.toDbTrack(), 0)
|
||||||
} else {
|
} else {
|
||||||
@@ -651,41 +753,60 @@ data class TrackerSearchScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel {
|
val viewModel = viewModel<Model>(
|
||||||
Model(
|
factory = Model.Factory,
|
||||||
mangaId = mangaId,
|
extras = CreationExtras {
|
||||||
currentUrl = currentUrl,
|
set(Model.MANGA_ID_KEY, mangaId)
|
||||||
initialQuery = initialQuery,
|
set(Model.CURRENT_URL_KEY, currentUrl)
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
set(Model.INITIAL_QUERY_KEY, initialQuery)
|
||||||
|
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
val textFieldState = rememberTextFieldState(initialQuery)
|
val textFieldState = rememberTextFieldState(initialQuery)
|
||||||
TrackerSearch(
|
TrackerSearch(
|
||||||
state = textFieldState,
|
state = textFieldState,
|
||||||
onDispatchQuery = { screenModel.trackingSearch(textFieldState.text.toString()) },
|
onDispatchQuery = { viewModel.trackingSearch(textFieldState.text.toString()) },
|
||||||
queryResult = state.queryResult,
|
queryResult = state.queryResult,
|
||||||
selected = state.selected,
|
selected = state.selected,
|
||||||
onSelectedChange = screenModel::updateSelection,
|
onSelectedChange = viewModel::updateSelection,
|
||||||
onConfirmSelection = f@{ private: Boolean ->
|
onConfirmSelection = f@{ private: Boolean ->
|
||||||
val selected = state.selected ?: return@f
|
val selected = state.selected ?: return@f
|
||||||
selected.private = private
|
selected.private = private
|
||||||
screenModel.registerTracking(selected)
|
viewModel.registerTracking(selected)
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
onDismissRequest = navigator::pop,
|
onDismissRequest = navigator::pop,
|
||||||
supportsPrivateTracking = screenModel.supportsPrivateTracking,
|
supportsPrivateTracking = viewModel.supportsPrivateTracking,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Model(
|
private class Model(
|
||||||
private val mangaId: Long,
|
private val mangaId: Long,
|
||||||
private val currentUrl: String? = null,
|
private val currentUrl: String?,
|
||||||
initialQuery: String,
|
initialQuery: String,
|
||||||
private val tracker: Tracker,
|
private val tracker: Tracker,
|
||||||
) : StateScreenModel<Model.State>(State()) {
|
) : StateViewModel<Model.State>(State()) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val MANGA_ID_KEY = CreationExtras.Key<Long>()
|
||||||
|
val CURRENT_URL_KEY = CreationExtras.Key<String?>()
|
||||||
|
val INITIAL_QUERY_KEY = CreationExtras.Key<String>()
|
||||||
|
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
Model(
|
||||||
|
mangaId = get(MANGA_ID_KEY)!!,
|
||||||
|
currentUrl = get(CURRENT_URL_KEY),
|
||||||
|
initialQuery = get(INITIAL_QUERY_KEY)!!,
|
||||||
|
tracker = get(TRACKER_KEY)!!,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val supportsPrivateTracking = tracker.supportsPrivateTracking
|
val supportsPrivateTracking = tracker.supportsPrivateTracking
|
||||||
|
|
||||||
@@ -697,7 +818,7 @@ data class TrackerSearchScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun trackingSearch(query: String) {
|
fun trackingSearch(query: String) {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
// To show loading state
|
// To show loading state
|
||||||
mutableState.update { it.copy(queryResult = null, selected = null) }
|
mutableState.update { it.copy(queryResult = null, selected = null) }
|
||||||
|
|
||||||
@@ -719,7 +840,7 @@ data class TrackerSearchScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun registerTracking(item: TrackSearch) {
|
fun registerTracking(item: TrackSearch) {
|
||||||
screenModelScope.launchNonCancellable { tracker.register(item, mangaId) }
|
viewModelScope.launchNonCancellable { tracker.register(item, mangaId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSelection(selected: TrackSearch) {
|
fun updateSelection(selected: TrackSearch) {
|
||||||
@@ -743,14 +864,15 @@ private data class TrackerRemoveScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel {
|
val viewModel = viewModel<Model>(
|
||||||
Model(
|
factory = Model.Factory,
|
||||||
mangaId = mangaId,
|
extras = CreationExtras {
|
||||||
track = track,
|
set(Model.MANGA_ID_KEY, mangaId)
|
||||||
tracker = Injekt.get<TrackerManager>().get(serviceId)!!,
|
set(Model.TRACK_KEY, track)
|
||||||
|
set(Model.TRACKER_KEY, Injekt.get<TrackerManager>().get(serviceId)!!)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
val serviceName = viewModel.getName()
|
||||||
val serviceName = screenModel.getName()
|
|
||||||
var removeRemoteTrack by remember { mutableStateOf(false) }
|
var removeRemoteTrack by remember { mutableStateOf(false) }
|
||||||
AlertDialogContent(
|
AlertDialogContent(
|
||||||
modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars),
|
modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars),
|
||||||
@@ -774,7 +896,7 @@ private data class TrackerRemoveScreen(
|
|||||||
text = stringResource(MR.strings.track_delete_text, serviceName),
|
text = stringResource(MR.strings.track_delete_text, serviceName),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (screenModel.isDeletable()) {
|
if (viewModel.isDeletable()) {
|
||||||
LabeledCheckbox(
|
LabeledCheckbox(
|
||||||
label = stringResource(MR.strings.track_delete_remote_text, serviceName),
|
label = stringResource(MR.strings.track_delete_remote_text, serviceName),
|
||||||
checked = removeRemoteTrack,
|
checked = removeRemoteTrack,
|
||||||
@@ -796,8 +918,8 @@ private data class TrackerRemoveScreen(
|
|||||||
}
|
}
|
||||||
FilledTonalButton(
|
FilledTonalButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
screenModel.unregisterTracking(serviceId)
|
viewModel.unregisterTracking(serviceId)
|
||||||
if (removeRemoteTrack) screenModel.deleteMangaFromService()
|
if (removeRemoteTrack) viewModel.deleteMangaFromService()
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
},
|
},
|
||||||
colors = ButtonDefaults.filledTonalButtonColors(
|
colors = ButtonDefaults.filledTonalButtonColors(
|
||||||
@@ -817,14 +939,30 @@ private data class TrackerRemoveScreen(
|
|||||||
private val track: Track,
|
private val track: Track,
|
||||||
private val tracker: Tracker,
|
private val tracker: Tracker,
|
||||||
private val deleteTrack: DeleteTrack = Injekt.get(),
|
private val deleteTrack: DeleteTrack = Injekt.get(),
|
||||||
) : ScreenModel {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val MANGA_ID_KEY = CreationExtras.Key<Long>()
|
||||||
|
val TRACK_KEY = CreationExtras.Key<Track>()
|
||||||
|
val TRACKER_KEY = CreationExtras.Key<Tracker>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
Model(
|
||||||
|
mangaId = get(MANGA_ID_KEY) as Long,
|
||||||
|
track = get(TRACK_KEY) as Track,
|
||||||
|
tracker = get(TRACKER_KEY) as Tracker,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getName() = tracker.name
|
fun getName() = tracker.name
|
||||||
|
|
||||||
fun isDeletable() = tracker is DeletableTracker
|
fun isDeletable() = tracker is DeletableTracker
|
||||||
|
|
||||||
fun deleteMangaFromService() {
|
fun deleteMangaFromService() {
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
try {
|
try {
|
||||||
(tracker as DeletableTracker).delete(track)
|
(tracker as DeletableTracker).delete(track)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -834,7 +972,7 @@ private data class TrackerRemoveScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun unregisterTracking(serviceId: Long) {
|
fun unregisterTracking(serviceId: Long) {
|
||||||
screenModelScope.launchNonCancellable { deleteTrack.await(mangaId, serviceId) }
|
viewModelScope.launchNonCancellable { deleteTrack.await(mangaId, serviceId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import androidx.lifecycle.ViewModel
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
@@ -58,14 +58,14 @@ data object MoreTab : Tab {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { MoreScreenModel() }
|
val viewModel = viewModel<MoreViewModel>()
|
||||||
val downloadQueueState by screenModel.downloadQueueState.collectAsState()
|
val downloadQueueState by viewModel.downloadQueueState.collectAsState()
|
||||||
MoreScreen(
|
MoreScreen(
|
||||||
downloadQueueStateProvider = { downloadQueueState },
|
downloadQueueStateProvider = { downloadQueueState },
|
||||||
downloadedOnly = screenModel.downloadedOnly,
|
downloadedOnly = viewModel.downloadedOnly,
|
||||||
onDownloadedOnlyChange = { screenModel.downloadedOnly = it },
|
onDownloadedOnlyChange = { viewModel.downloadedOnly = it },
|
||||||
incognitoMode = screenModel.incognitoMode,
|
incognitoMode = viewModel.incognitoMode,
|
||||||
onIncognitoModeChange = { screenModel.incognitoMode = it },
|
onIncognitoModeChange = { viewModel.incognitoMode = it },
|
||||||
onClickDownloadQueue = { navigator.push(DownloadQueueScreen) },
|
onClickDownloadQueue = { navigator.push(DownloadQueueScreen) },
|
||||||
onClickCategories = { navigator.push(CategoryScreen()) },
|
onClickCategories = { navigator.push(CategoryScreen()) },
|
||||||
onClickStats = { navigator.push(StatsScreen()) },
|
onClickStats = { navigator.push(StatsScreen()) },
|
||||||
@@ -77,20 +77,20 @@ data object MoreTab : Tab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MoreScreenModel(
|
private class MoreViewModel(
|
||||||
private val downloadManager: DownloadManager = Injekt.get(),
|
private val downloadManager: DownloadManager = Injekt.get(),
|
||||||
preferences: BasePreferences = Injekt.get(),
|
preferences: BasePreferences = Injekt.get(),
|
||||||
) : ScreenModel {
|
) : ViewModel() {
|
||||||
|
|
||||||
var downloadedOnly by preferences.downloadedOnly.asState(screenModelScope)
|
var downloadedOnly by preferences.downloadedOnly.asState(viewModelScope)
|
||||||
var incognitoMode by preferences.incognitoMode.asState(screenModelScope)
|
var incognitoMode by preferences.incognitoMode.asState(viewModelScope)
|
||||||
|
|
||||||
private var _downloadQueueState: MutableStateFlow<DownloadQueueState> = MutableStateFlow(DownloadQueueState.Stopped)
|
private var _downloadQueueState: MutableStateFlow<DownloadQueueState> = MutableStateFlow(DownloadQueueState.Stopped)
|
||||||
val downloadQueueState: StateFlow<DownloadQueueState> = _downloadQueueState.asStateFlow()
|
val downloadQueueState: StateFlow<DownloadQueueState> = _downloadQueueState.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Handle running/paused status change and queue progress updating
|
// Handle running/paused status change and queue progress updating
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
combine(
|
combine(
|
||||||
downloadManager.isDownloaderRunning,
|
downloadManager.isDownloaderRunning,
|
||||||
downloadManager.queueState,
|
downloadManager.queueState,
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
|||||||
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsViewModel
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||||
@@ -252,8 +252,8 @@ class ReaderActivity : BaseActivity() {
|
|||||||
private fun ReaderActivityBinding.setComposeOverlay(): Unit = composeOverlay.setComposeContent {
|
private fun ReaderActivityBinding.setComposeOverlay(): Unit = composeOverlay.setComposeContent {
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
val showPageNumber by readerPreferences.showPageNumber.collectAsState()
|
val showPageNumber by readerPreferences.showPageNumber.collectAsState()
|
||||||
val settingsScreenModel = remember {
|
val settingsviewModel = remember {
|
||||||
ReaderSettingsScreenModel(
|
ReaderSettingsViewModel(
|
||||||
readerState = viewModel.state,
|
readerState = viewModel.state,
|
||||||
onChangeReadingMode = viewModel::setMangaReadingMode,
|
onChangeReadingMode = viewModel::setMangaReadingMode,
|
||||||
onChangeOrientation = viewModel::setMangaOrientationType,
|
onChangeOrientation = viewModel::setMangaOrientationType,
|
||||||
@@ -298,13 +298,13 @@ class ReaderActivity : BaseActivity() {
|
|||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
onShowMenus = { setMenuVisibility(true) },
|
onShowMenus = { setMenuVisibility(true) },
|
||||||
onHideMenus = { setMenuVisibility(false) },
|
onHideMenus = { setMenuVisibility(false) },
|
||||||
screenModel = settingsScreenModel,
|
viewModel = settingsviewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ReaderViewModel.Dialog.ReadingModeSelect -> {
|
is ReaderViewModel.Dialog.ReadingModeSelect -> {
|
||||||
ReadingModeSelectDialog(
|
ReadingModeSelectDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
screenModel = settingsScreenModel,
|
viewModel = settingsviewModel,
|
||||||
onChange = { stringRes ->
|
onChange = { stringRes ->
|
||||||
menuToggleToast?.cancel()
|
menuToggleToast?.cancel()
|
||||||
if (!readerPreferences.showReadingMode.get()) {
|
if (!readerPreferences.showReadingMode.get()) {
|
||||||
@@ -316,7 +316,7 @@ class ReaderActivity : BaseActivity() {
|
|||||||
is ReaderViewModel.Dialog.OrientationModeSelect -> {
|
is ReaderViewModel.Dialog.OrientationModeSelect -> {
|
||||||
OrientationSelectDialog(
|
OrientationSelectDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
screenModel = settingsScreenModel,
|
viewModel = settingsviewModel,
|
||||||
onChange = { stringRes ->
|
onChange = { stringRes ->
|
||||||
menuToggleToast?.cancel()
|
menuToggleToast?.cancel()
|
||||||
menuToggleToast = toast(stringRes)
|
menuToggleToast = toast(stringRes)
|
||||||
|
|||||||
+6
-6
@@ -1,7 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.setting
|
package eu.kanade.tachiyomi.ui.reader.setting
|
||||||
|
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import androidx.lifecycle.ViewModel
|
||||||
import eu.kanade.presentation.util.ioCoroutineScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel
|
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -11,20 +11,20 @@ import kotlinx.coroutines.flow.stateIn
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ReaderSettingsScreenModel(
|
class ReaderSettingsViewModel(
|
||||||
readerState: StateFlow<ReaderViewModel.State>,
|
readerState: StateFlow<ReaderViewModel.State>,
|
||||||
val onChangeReadingMode: (ReadingMode) -> Unit,
|
val onChangeReadingMode: (ReadingMode) -> Unit,
|
||||||
val onChangeOrientation: (ReaderOrientation) -> Unit,
|
val onChangeOrientation: (ReaderOrientation) -> Unit,
|
||||||
val preferences: ReaderPreferences = Injekt.get(),
|
val preferences: ReaderPreferences = Injekt.get(),
|
||||||
) : ScreenModel {
|
) : ViewModel() {
|
||||||
|
|
||||||
val viewerFlow = readerState
|
val viewerFlow = readerState
|
||||||
.map { it.viewer }
|
.map { it.viewer }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.stateIn(ioCoroutineScope, SharingStarted.Lazily, null)
|
.stateIn(viewModelScope, SharingStarted.Lazily, null)
|
||||||
|
|
||||||
val mangaFlow = readerState
|
val mangaFlow = readerState
|
||||||
.map { it.manga }
|
.map { it.manga }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.stateIn(ioCoroutineScope, SharingStarted.Lazily, null)
|
.stateIn(viewModelScope, SharingStarted.Lazily, null)
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.stats
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
@@ -21,8 +21,8 @@ class StatsScreen : Screen() {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { StatsScreenModel() }
|
val viewModel = viewModel<StatsViewModel>()
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
|
|||||||
+5
-5
@@ -2,8 +2,7 @@ package eu.kanade.tachiyomi.ui.stats
|
|||||||
|
|
||||||
import androidx.compose.ui.util.fastDistinctBy
|
import androidx.compose.ui.util.fastDistinctBy
|
||||||
import androidx.compose.ui.util.fastFilter
|
import androidx.compose.ui.util.fastFilter
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import eu.kanade.core.util.fastCountNot
|
import eu.kanade.core.util.fastCountNot
|
||||||
import eu.kanade.presentation.more.stats.StatsScreenState
|
import eu.kanade.presentation.more.stats.StatsScreenState
|
||||||
import eu.kanade.presentation.more.stats.data.StatsData
|
import eu.kanade.presentation.more.stats.data.StatsData
|
||||||
@@ -11,6 +10,7 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
|
|||||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.domain.history.interactor.GetTotalReadDuration
|
import tachiyomi.domain.history.interactor.GetTotalReadDuration
|
||||||
import tachiyomi.domain.library.model.LibraryManga
|
import tachiyomi.domain.library.model.LibraryManga
|
||||||
@@ -25,19 +25,19 @@ import tachiyomi.source.local.isLocal
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class StatsScreenModel(
|
class StatsViewModel(
|
||||||
private val downloadManager: DownloadManager = Injekt.get(),
|
private val downloadManager: DownloadManager = Injekt.get(),
|
||||||
private val getLibraryManga: GetLibraryManga = Injekt.get(),
|
private val getLibraryManga: GetLibraryManga = Injekt.get(),
|
||||||
private val getTotalReadDuration: GetTotalReadDuration = Injekt.get(),
|
private val getTotalReadDuration: GetTotalReadDuration = Injekt.get(),
|
||||||
private val getTracks: GetTracks = Injekt.get(),
|
private val getTracks: GetTracks = Injekt.get(),
|
||||||
private val preferences: LibraryPreferences = Injekt.get(),
|
private val preferences: LibraryPreferences = Injekt.get(),
|
||||||
private val trackerManager: TrackerManager = Injekt.get(),
|
private val trackerManager: TrackerManager = Injekt.get(),
|
||||||
) : StateScreenModel<StatsScreenState>(StatsScreenState.Loading) {
|
) : StateViewModel<StatsScreenState>(StatsScreenState.Loading) {
|
||||||
|
|
||||||
private val loggedInTrackers by lazy { trackerManager.loggedInTrackers() }
|
private val loggedInTrackers by lazy { trackerManager.loggedInTrackers() }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val libraryManga = getLibraryManga.await()
|
val libraryManga = getLibraryManga.await()
|
||||||
|
|
||||||
val distinctLibraryManga = libraryManga.fastDistinctBy { it.id }
|
val distinctLibraryManga = libraryManga.fastDistinctBy { it.id }
|
||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.updates
|
package eu.kanade.tachiyomi.ui.updates
|
||||||
|
|
||||||
import cafe.adriel.voyager.core.model.ScreenModel
|
import androidx.lifecycle.ViewModel
|
||||||
import tachiyomi.core.common.preference.Preference
|
import tachiyomi.core.common.preference.Preference
|
||||||
import tachiyomi.core.common.preference.TriState
|
import tachiyomi.core.common.preference.TriState
|
||||||
import tachiyomi.core.common.preference.getAndSet
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
@@ -8,9 +8,9 @@ import tachiyomi.domain.updates.service.UpdatesPreferences
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class UpdatesSettingsScreenModel(
|
class UpdatesSettingsViewModel(
|
||||||
val updatesPreferences: UpdatesPreferences = Injekt.get(),
|
val updatesPreferences: UpdatesPreferences = Injekt.get(),
|
||||||
) : ScreenModel {
|
) : ViewModel() {
|
||||||
|
|
||||||
fun toggleFilter(preference: (UpdatesPreferences) -> Preference<TriState>) {
|
fun toggleFilter(preference: (UpdatesPreferences) -> Preference<TriState>) {
|
||||||
preference(updatesPreferences).getAndSet {
|
preference(updatesPreferences).getAndSet {
|
||||||
@@ -9,7 +9,7 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
@@ -25,7 +25,7 @@ import eu.kanade.tachiyomi.ui.home.HomeScreen
|
|||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel.Event
|
import eu.kanade.tachiyomi.ui.updates.UpdatesViewModel.Event
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import mihon.feature.upcoming.UpcomingScreen
|
import mihon.feature.upcoming.UpcomingScreen
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
@@ -54,53 +54,53 @@ data object UpdatesTab : Tab {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { UpdatesScreenModel() }
|
val viewModel = viewModel<UpdatesViewModel>()
|
||||||
val settingsScreenModel = rememberScreenModel { UpdatesSettingsScreenModel() }
|
val settingsViewModel = viewModel<UpdatesSettingsViewModel>()
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
UpdateScreen(
|
UpdateScreen(
|
||||||
state = state,
|
state = state,
|
||||||
snackbarHostState = screenModel.snackbarHostState,
|
snackbarHostState = viewModel.snackbarHostState,
|
||||||
lastUpdated = screenModel.lastUpdated,
|
lastUpdated = viewModel.lastUpdated,
|
||||||
onClickCover = { item -> navigator.push(MangaScreen(item.update.mangaId)) },
|
onClickCover = { item -> navigator.push(MangaScreen(item.update.mangaId)) },
|
||||||
onSelectAll = screenModel::toggleAllSelection,
|
onSelectAll = viewModel::toggleAllSelection,
|
||||||
onInvertSelection = screenModel::invertSelection,
|
onInvertSelection = viewModel::invertSelection,
|
||||||
onUpdateLibrary = screenModel::updateLibrary,
|
onUpdateLibrary = viewModel::updateLibrary,
|
||||||
onDownloadChapter = screenModel::downloadChapters,
|
onDownloadChapter = viewModel::downloadChapters,
|
||||||
onMultiBookmarkClicked = screenModel::bookmarkUpdates,
|
onMultiBookmarkClicked = viewModel::bookmarkUpdates,
|
||||||
onMultiMarkAsReadClicked = screenModel::markUpdatesRead,
|
onMultiMarkAsReadClicked = viewModel::markUpdatesRead,
|
||||||
onMultiDeleteClicked = screenModel::showConfirmDeleteChapters,
|
onMultiDeleteClicked = viewModel::showConfirmDeleteChapters,
|
||||||
onUpdateSelected = screenModel::toggleSelection,
|
onUpdateSelected = viewModel::toggleSelection,
|
||||||
onOpenChapter = {
|
onOpenChapter = {
|
||||||
val intent = ReaderActivity.newIntent(context, it.update.mangaId, it.update.chapterId)
|
val intent = ReaderActivity.newIntent(context, it.update.mangaId, it.update.chapterId)
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
},
|
},
|
||||||
onCalendarClicked = { navigator.push(UpcomingScreen()) },
|
onCalendarClicked = { navigator.push(UpcomingScreen()) },
|
||||||
onFilterClicked = screenModel::showFilterDialog,
|
onFilterClicked = viewModel::showFilterDialog,
|
||||||
hasActiveFilters = state.hasActiveFilters,
|
hasActiveFilters = state.hasActiveFilters,
|
||||||
)
|
)
|
||||||
|
|
||||||
val onDismissDialog = { screenModel.setDialog(null) }
|
val onDismissDialog = { viewModel.setDialog(null) }
|
||||||
when (val dialog = state.dialog) {
|
when (val dialog = state.dialog) {
|
||||||
is UpdatesScreenModel.Dialog.DeleteConfirmation -> {
|
is UpdatesViewModel.Dialog.DeleteConfirmation -> {
|
||||||
UpdatesDeleteConfirmationDialog(
|
UpdatesDeleteConfirmationDialog(
|
||||||
onDismissRequest = onDismissDialog,
|
onDismissRequest = onDismissDialog,
|
||||||
onConfirm = { screenModel.deleteChapters(dialog.toDelete) },
|
onConfirm = { viewModel.deleteChapters(dialog.toDelete) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is UpdatesScreenModel.Dialog.FilterSheet -> {
|
is UpdatesViewModel.Dialog.FilterSheet -> {
|
||||||
UpdatesFilterDialog(
|
UpdatesFilterDialog(
|
||||||
onDismissRequest = onDismissDialog,
|
onDismissRequest = onDismissDialog,
|
||||||
screenModel = settingsScreenModel,
|
viewModel = settingsViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
null -> {}
|
null -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
screenModel.events.collectLatest { event ->
|
viewModel.events.collectLatest { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
Event.InternalError -> screenModel.snackbarHostState.showSnackbar(
|
Event.InternalError -> viewModel.snackbarHostState.showSnackbar(
|
||||||
context.stringResource(MR.strings.internal_error),
|
context.stringResource(MR.strings.internal_error),
|
||||||
)
|
)
|
||||||
is Event.LibraryUpdateTriggered -> {
|
is Event.LibraryUpdateTriggered -> {
|
||||||
@@ -109,7 +109,7 @@ data object UpdatesTab : Tab {
|
|||||||
} else {
|
} else {
|
||||||
MR.strings.update_already_running
|
MR.strings.update_already_running
|
||||||
}
|
}
|
||||||
screenModel.snackbarHostState.showSnackbar(context.stringResource(msg))
|
viewModel.snackbarHostState.showSnackbar(context.stringResource(msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,10 +125,10 @@ data object UpdatesTab : Tab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
screenModel.resetNewUpdatesCount()
|
viewModel.resetNewUpdatesCount()
|
||||||
|
|
||||||
onDispose {
|
onDispose {
|
||||||
screenModel.resetNewUpdatesCount()
|
viewModel.resetNewUpdatesCount()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-15
@@ -5,8 +5,7 @@ import androidx.compose.material3.SnackbarHostState
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.util.fastFilter
|
import androidx.compose.ui.util.fastFilter
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
import eu.kanade.core.util.addOrRemove
|
import eu.kanade.core.util.addOrRemove
|
||||||
import eu.kanade.core.util.insertSeparators
|
import eu.kanade.core.util.insertSeparators
|
||||||
@@ -33,6 +32,7 @@ import kotlinx.coroutines.flow.receiveAsFlow
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import tachiyomi.core.common.preference.TriState
|
import tachiyomi.core.common.preference.TriState
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||||
@@ -51,7 +51,7 @@ import uy.kohesive.injekt.Injekt
|
|||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
class UpdatesScreenModel(
|
class UpdatesViewModel(
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
private val downloadManager: DownloadManager = Injekt.get(),
|
private val downloadManager: DownloadManager = Injekt.get(),
|
||||||
private val downloadCache: DownloadCache = Injekt.get(),
|
private val downloadCache: DownloadCache = Injekt.get(),
|
||||||
@@ -63,19 +63,19 @@ class UpdatesScreenModel(
|
|||||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||||
private val updatesPreferences: UpdatesPreferences = Injekt.get(),
|
private val updatesPreferences: UpdatesPreferences = Injekt.get(),
|
||||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||||
) : StateScreenModel<UpdatesScreenModel.State>(State()) {
|
) : StateViewModel<UpdatesViewModel.State>(State()) {
|
||||||
|
|
||||||
private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
|
private val _events: Channel<Event> = Channel(Int.MAX_VALUE)
|
||||||
val events: Flow<Event> = _events.receiveAsFlow()
|
val events: Flow<Event> = _events.receiveAsFlow()
|
||||||
|
|
||||||
val lastUpdated by libraryPreferences.lastUpdatedTimestamp.asState(screenModelScope)
|
val lastUpdated by libraryPreferences.lastUpdatedTimestamp.asState(viewModelScope)
|
||||||
|
|
||||||
// First and last selected index in list
|
// First and last selected index in list
|
||||||
private val selectedPositions: Array<Int> = arrayOf(-1, -1)
|
private val selectedPositions: Array<Int> = arrayOf(-1, -1)
|
||||||
private val selectedChapterIds: HashSet<Long> = HashSet()
|
private val selectedChapterIds: HashSet<Long> = HashSet()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
// Set date limit for recent chapters
|
// Set date limit for recent chapters
|
||||||
val limit = ZonedDateTime.now().minusMonths(3).toInstant()
|
val limit = ZonedDateTime.now().minusMonths(3).toInstant()
|
||||||
|
|
||||||
@@ -113,10 +113,10 @@ class UpdatesScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
merge(downloadManager.statusFlow(), downloadManager.progressFlow())
|
merge(downloadManager.statusFlow(), downloadManager.progressFlow())
|
||||||
.catch { logcat(LogPriority.ERROR, it) }
|
.catch { logcat(LogPriority.ERROR, it) }
|
||||||
.collect(this@UpdatesScreenModel::updateDownloadState)
|
.collect(this@UpdatesViewModel::updateDownloadState)
|
||||||
}
|
}
|
||||||
|
|
||||||
getUpdatesItemPreferenceFlow()
|
getUpdatesItemPreferenceFlow()
|
||||||
@@ -135,7 +135,7 @@ class UpdatesScreenModel(
|
|||||||
state.copy(hasActiveFilters = it)
|
state.copy(hasActiveFilters = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.launchIn(screenModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<UpdatesItem>.applyFilters(
|
private fun List<UpdatesItem>.applyFilters(
|
||||||
@@ -181,7 +181,7 @@ class UpdatesScreenModel(
|
|||||||
|
|
||||||
fun updateLibrary(): Boolean {
|
fun updateLibrary(): Boolean {
|
||||||
val started = LibraryUpdateJob.startNow(Injekt.get<Application>())
|
val started = LibraryUpdateJob.startNow(Injekt.get<Application>())
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
_events.send(Event.LibraryUpdateTriggered(started))
|
_events.send(Event.LibraryUpdateTriggered(started))
|
||||||
}
|
}
|
||||||
return started
|
return started
|
||||||
@@ -210,7 +210,7 @@ class UpdatesScreenModel(
|
|||||||
|
|
||||||
fun downloadChapters(items: List<UpdatesItem>, action: ChapterDownloadAction) {
|
fun downloadChapters(items: List<UpdatesItem>, action: ChapterDownloadAction) {
|
||||||
if (items.isEmpty()) return
|
if (items.isEmpty()) return
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
when (action) {
|
when (action) {
|
||||||
ChapterDownloadAction.START -> {
|
ChapterDownloadAction.START -> {
|
||||||
downloadChapters(items)
|
downloadChapters(items)
|
||||||
@@ -250,7 +250,7 @@ class UpdatesScreenModel(
|
|||||||
* @param read whether to mark chapters as read or unread.
|
* @param read whether to mark chapters as read or unread.
|
||||||
*/
|
*/
|
||||||
fun markUpdatesRead(updates: List<UpdatesItem>, read: Boolean) {
|
fun markUpdatesRead(updates: List<UpdatesItem>, read: Boolean) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
setReadStatus.await(
|
setReadStatus.await(
|
||||||
read = read,
|
read = read,
|
||||||
chapters = updates
|
chapters = updates
|
||||||
@@ -266,7 +266,7 @@ class UpdatesScreenModel(
|
|||||||
* @param updates the list of chapters to bookmark.
|
* @param updates the list of chapters to bookmark.
|
||||||
*/
|
*/
|
||||||
fun bookmarkUpdates(updates: List<UpdatesItem>, bookmark: Boolean) {
|
fun bookmarkUpdates(updates: List<UpdatesItem>, bookmark: Boolean) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
updates
|
updates
|
||||||
.filterNot { it.update.bookmark == bookmark }
|
.filterNot { it.update.bookmark == bookmark }
|
||||||
.map { ChapterUpdate(id = it.update.chapterId, bookmark = bookmark) }
|
.map { ChapterUpdate(id = it.update.chapterId, bookmark = bookmark) }
|
||||||
@@ -280,7 +280,7 @@ class UpdatesScreenModel(
|
|||||||
* @param updatesItem the list of chapters to download.
|
* @param updatesItem the list of chapters to download.
|
||||||
*/
|
*/
|
||||||
private fun downloadChapters(updatesItem: List<UpdatesItem>) {
|
private fun downloadChapters(updatesItem: List<UpdatesItem>) {
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
val groupedUpdates = updatesItem.groupBy { it.update.mangaId }.values
|
val groupedUpdates = updatesItem.groupBy { it.update.mangaId }.values
|
||||||
for (updates in groupedUpdates) {
|
for (updates in groupedUpdates) {
|
||||||
val mangaId = updates.first().update.mangaId
|
val mangaId = updates.first().update.mangaId
|
||||||
@@ -299,7 +299,7 @@ class UpdatesScreenModel(
|
|||||||
* @param updatesItem list of chapters
|
* @param updatesItem list of chapters
|
||||||
*/
|
*/
|
||||||
fun deleteChapters(updatesItem: List<UpdatesItem>) {
|
fun deleteChapters(updatesItem: List<UpdatesItem>) {
|
||||||
screenModelScope.launchNonCancellable {
|
viewModelScope.launchNonCancellable {
|
||||||
updatesItem
|
updatesItem
|
||||||
.groupBy { it.update.mangaId }
|
.groupBy { it.update.mangaId }
|
||||||
.entries
|
.entries
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.ui.webview
|
package eu.kanade.tachiyomi.ui.webview
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.assist.AssistContent
|
import android.app.assist.AssistContent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package eu.kanade.tachiyomi.ui.webview
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.util.AssistContentScreen
|
import eu.kanade.presentation.util.AssistContentScreen
|
||||||
@@ -23,17 +24,22 @@ class WebViewScreen(
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val screenModel = rememberScreenModel { WebViewScreenModel(sourceId) }
|
val viewModel = viewModel<WebViewViewModel>(
|
||||||
|
factory = WebViewViewModel.Factory,
|
||||||
|
extras = CreationExtras {
|
||||||
|
set(WebViewViewModel.SOURCE_ID_KEY, sourceId)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
WebViewScreenContent(
|
WebViewScreenContent(
|
||||||
onNavigateUp = { navigator.pop() },
|
onNavigateUp = { navigator.pop() },
|
||||||
initialTitle = initialTitle,
|
initialTitle = initialTitle,
|
||||||
url = url,
|
url = url,
|
||||||
headers = screenModel.headers,
|
headers = viewModel.headers,
|
||||||
onUrlChange = { assistUrl = it },
|
onUrlChange = { assistUrl = it },
|
||||||
onShare = { screenModel.shareWebpage(context, it) },
|
onShare = { viewModel.shareWebpage(context, it) },
|
||||||
onOpenInBrowser = { screenModel.openInBrowser(context, it) },
|
onOpenInBrowser = { viewModel.openInBrowser(context, it) },
|
||||||
onClearCookies = screenModel::clearCookies,
|
onClearCookies = viewModel::clearCookies,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-3
@@ -2,7 +2,9 @@ package eu.kanade.tachiyomi.ui.webview
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import eu.kanade.presentation.more.stats.StatsScreenState
|
import eu.kanade.presentation.more.stats.StatsScreenState
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
@@ -10,17 +12,30 @@ import eu.kanade.tachiyomi.util.system.openInBrowser
|
|||||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class WebViewScreenModel(
|
class WebViewViewModel(
|
||||||
val sourceId: Long?,
|
val sourceId: Long?,
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
private val network: NetworkHelper = Injekt.get(),
|
private val network: NetworkHelper = Injekt.get(),
|
||||||
) : StateScreenModel<StatsScreenState>(StatsScreenState.Loading) {
|
) : StateViewModel<StatsScreenState>(StatsScreenState.Loading) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val SOURCE_ID_KEY = CreationExtras.Key<Long?>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
WebViewViewModel(
|
||||||
|
sourceId = get(SOURCE_ID_KEY),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var headers = emptyMap<String, String>()
|
var headers = emptyMap<String, String>()
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.util.system
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.os.LocaleListCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesViewModel
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -30,8 +30,8 @@ object LocaleHelper {
|
|||||||
*/
|
*/
|
||||||
fun getSourceDisplayName(lang: String?, context: Context): String {
|
fun getSourceDisplayName(lang: String?, context: Context): String {
|
||||||
return when (lang) {
|
return when (lang) {
|
||||||
SourcesScreenModel.LAST_USED_KEY -> context.stringResource(MR.strings.last_used_source)
|
SourcesViewModel.LAST_USED_KEY -> context.stringResource(MR.strings.last_used_source)
|
||||||
SourcesScreenModel.PINNED_KEY -> context.stringResource(MR.strings.pinned_sources)
|
SourcesViewModel.PINNED_KEY -> context.stringResource(MR.strings.pinned_sources)
|
||||||
"other" -> context.stringResource(MR.strings.other_source)
|
"other" -> context.stringResource(MR.strings.other_source)
|
||||||
"all" -> context.stringResource(MR.strings.multi_lang)
|
"all" -> context.stringResource(MR.strings.multi_lang)
|
||||||
else -> getLocalizedDisplayName(lang)
|
else -> getLocalizedDisplayName(lang)
|
||||||
|
|||||||
@@ -36,9 +36,8 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.util.fastForEachIndexed
|
import androidx.compose.ui.util.fastForEachIndexed
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
@@ -50,6 +49,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
|
|||||||
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
|
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import mihon.feature.migration.list.MigrationListScreen
|
import mihon.feature.migration.list.MigrationListScreen
|
||||||
import sh.calvin.reorderable.ReorderableCollectionItemScope
|
import sh.calvin.reorderable.ReorderableCollectionItemScope
|
||||||
import sh.calvin.reorderable.ReorderableItem
|
import sh.calvin.reorderable.ReorderableItem
|
||||||
@@ -77,8 +77,8 @@ class MigrationConfigScreen(private val mangaIds: Collection<Long>) : Screen() {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { ScreenModel() }
|
val viewModel = viewModel<ViewModel>()
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
var migrationSheetOpen by rememberSaveable { mutableStateOf(false) }
|
var migrationSheetOpen by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
@@ -121,20 +121,20 @@ class MigrationConfigScreen(private val mangaIds: Collection<Long>) : Screen() {
|
|||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(MR.strings.migrationConfigScreen_selectAllLabel),
|
title = stringResource(MR.strings.migrationConfigScreen_selectAllLabel),
|
||||||
icon = Icons.Outlined.SelectAll,
|
icon = Icons.Outlined.SelectAll,
|
||||||
onClick = { screenModel.toggleSelection(ScreenModel.SelectionConfig.All) },
|
onClick = { viewModel.toggleSelection(ViewModel.SelectionConfig.All) },
|
||||||
),
|
),
|
||||||
AppBar.Action(
|
AppBar.Action(
|
||||||
title = stringResource(MR.strings.migrationConfigScreen_selectNoneLabel),
|
title = stringResource(MR.strings.migrationConfigScreen_selectNoneLabel),
|
||||||
icon = Icons.Outlined.Deselect,
|
icon = Icons.Outlined.Deselect,
|
||||||
onClick = { screenModel.toggleSelection(ScreenModel.SelectionConfig.None) },
|
onClick = { viewModel.toggleSelection(ViewModel.SelectionConfig.None) },
|
||||||
),
|
),
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(MR.strings.migrationConfigScreen_selectEnabledLabel),
|
title = stringResource(MR.strings.migrationConfigScreen_selectEnabledLabel),
|
||||||
onClick = { screenModel.toggleSelection(ScreenModel.SelectionConfig.Enabled) },
|
onClick = { viewModel.toggleSelection(ViewModel.SelectionConfig.Enabled) },
|
||||||
),
|
),
|
||||||
AppBar.OverflowAction(
|
AppBar.OverflowAction(
|
||||||
title = stringResource(MR.strings.migrationConfigScreen_selectPinnedLabel),
|
title = stringResource(MR.strings.migrationConfigScreen_selectPinnedLabel),
|
||||||
onClick = { screenModel.toggleSelection(ScreenModel.SelectionConfig.Pinned) },
|
onClick = { viewModel.toggleSelection(ViewModel.SelectionConfig.Pinned) },
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -146,7 +146,7 @@ class MigrationConfigScreen(private val mangaIds: Collection<Long>) : Screen() {
|
|||||||
text = { Text(text = stringResource(MR.strings.migrationConfigScreen_continueButtonText)) },
|
text = { Text(text = stringResource(MR.strings.migrationConfigScreen_continueButtonText)) },
|
||||||
icon = { Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null) },
|
||||||
onClick = {
|
onClick = {
|
||||||
screenModel.saveSources()
|
viewModel.saveSources()
|
||||||
continueMigration(openSheet = true, extraSearchQuery = null)
|
continueMigration(openSheet = true, extraSearchQuery = null)
|
||||||
},
|
},
|
||||||
expanded = lazyListState.shouldExpandFAB(),
|
expanded = lazyListState.shouldExpandFAB(),
|
||||||
@@ -157,7 +157,7 @@ class MigrationConfigScreen(private val mangaIds: Collection<Long>) : Screen() {
|
|||||||
val fromIndex = selectedSources.indexOfFirst { it.id == from.key }
|
val fromIndex = selectedSources.indexOfFirst { it.id == from.key }
|
||||||
val toIndex = selectedSources.indexOfFirst { it.id == to.key }
|
val toIndex = selectedSources.indexOfFirst { it.id == to.key }
|
||||||
if (fromIndex == -1 || toIndex == -1) return@rememberReorderableLazyListState
|
if (fromIndex == -1 || toIndex == -1) return@rememberReorderableLazyListState
|
||||||
screenModel.orderSource(fromIndex, toIndex)
|
viewModel.orderSource(fromIndex, toIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
FastScrollLazyColumn(
|
FastScrollLazyColumn(
|
||||||
@@ -197,7 +197,7 @@ class MigrationConfigScreen(private val mangaIds: Collection<Long>) : Screen() {
|
|||||||
dragEnabled = selectedSourceList && sources.size > 1,
|
dragEnabled = selectedSourceList && sources.size > 1,
|
||||||
state = reorderableState,
|
state = reorderableState,
|
||||||
key = { if (selectedSourceList) it.id else "available-${it.id}" },
|
key = { if (selectedSourceList) it.id else "available-${it.id}" },
|
||||||
onClick = { screenModel.toggleSelection(item.id) },
|
onClick = { viewModel.toggleSelection(item.id) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,7 +206,7 @@ class MigrationConfigScreen(private val mangaIds: Collection<Long>) : Screen() {
|
|||||||
|
|
||||||
if (migrationSheetOpen) {
|
if (migrationSheetOpen) {
|
||||||
MigrationConfigScreenSheet(
|
MigrationConfigScreenSheet(
|
||||||
preferences = screenModel.sourcePreferences,
|
preferences = viewModel.sourcePreferences,
|
||||||
onDismissRequest = { migrationSheetOpen = false },
|
onDismissRequest = { migrationSheetOpen = false },
|
||||||
onStartMigration = { extraSearchQuery ->
|
onStartMigration = { extraSearchQuery ->
|
||||||
migrationSheetOpen = false
|
migrationSheetOpen = false
|
||||||
@@ -306,10 +306,10 @@ class MigrationConfigScreen(private val mangaIds: Collection<Long>) : Screen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScreenModel(
|
private class ViewModel(
|
||||||
val sourcePreferences: SourcePreferences = Injekt.get(),
|
val sourcePreferences: SourcePreferences = Injekt.get(),
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
) : StateScreenModel<ScreenModel.State>(State()) {
|
) : StateViewModel<ViewModel.State>(State()) {
|
||||||
|
|
||||||
private val sourcesComparator = { includedSources: List<Long> ->
|
private val sourcesComparator = { includedSources: List<Long> ->
|
||||||
compareBy<MigrationSource>(
|
compareBy<MigrationSource>(
|
||||||
@@ -320,7 +320,7 @@ class MigrationConfigScreen(private val mangaIds: Collection<Long>) : Screen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
initSources()
|
initSources()
|
||||||
mutableState.update { it.copy(isLoading = false) }
|
mutableState.update { it.copy(isLoading = false) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
|
||||||
import cafe.adriel.voyager.core.screen.Screen
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
import eu.kanade.domain.manga.model.hasCustomCover
|
import eu.kanade.domain.manga.model.hasCustomCover
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import mihon.domain.migration.models.MigrationFlag
|
import mihon.domain.migration.models.MigrationFlag
|
||||||
import mihon.domain.migration.usecases.MigrateMangaUseCase
|
import mihon.domain.migration.usecases.MigrateMangaUseCase
|
||||||
import mihon.feature.common.utils.getLabel
|
import mihon.feature.common.utils.getLabel
|
||||||
@@ -50,11 +50,11 @@ internal fun Screen.MigrateMangaDialog(
|
|||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { MigrateDialogScreenModel() }
|
val viewModel = viewModel<MigrateDialogViewModel>()
|
||||||
LaunchedEffect(current, target) {
|
LaunchedEffect(current, target) {
|
||||||
screenModel.init(current, target)
|
viewModel.init(current, target)
|
||||||
}
|
}
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
if (state.isMigrated) return
|
if (state.isMigrated) return
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ internal fun Screen.MigrateMangaDialog(
|
|||||||
LabeledCheckbox(
|
LabeledCheckbox(
|
||||||
label = stringResource(flag.getLabel()),
|
label = stringResource(flag.getLabel()),
|
||||||
checked = flag in state.selectedFlags,
|
checked = flag in state.selectedFlags,
|
||||||
onCheckedChange = { screenModel.toggleSelection(flag) },
|
onCheckedChange = { viewModel.toggleSelection(flag) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,7 +101,7 @@ internal fun Screen.MigrateMangaDialog(
|
|||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launchIO {
|
scope.launchIO {
|
||||||
screenModel.migrateManga(replace = false)
|
viewModel.migrateManga(replace = false)
|
||||||
withUIContext { onComplete() }
|
withUIContext { onComplete() }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -111,7 +111,7 @@ internal fun Screen.MigrateMangaDialog(
|
|||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launchIO {
|
scope.launchIO {
|
||||||
screenModel.migrateManga(replace = true)
|
viewModel.migrateManga(replace = true)
|
||||||
withUIContext { onComplete() }
|
withUIContext { onComplete() }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -123,12 +123,12 @@ internal fun Screen.MigrateMangaDialog(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MigrateDialogScreenModel(
|
private class MigrateDialogViewModel(
|
||||||
private val sourcePreference: SourcePreferences = Injekt.get(),
|
private val sourcePreference: SourcePreferences = Injekt.get(),
|
||||||
private val coverCache: CoverCache = Injekt.get(),
|
private val coverCache: CoverCache = Injekt.get(),
|
||||||
private val downloadManager: DownloadManager = Injekt.get(),
|
private val downloadManager: DownloadManager = Injekt.get(),
|
||||||
private val migrateManga: MigrateMangaUseCase = Injekt.get(),
|
private val migrateManga: MigrateMangaUseCase = Injekt.get(),
|
||||||
) : StateScreenModel<MigrateDialogScreenModel.State>(State()) {
|
) : StateViewModel<MigrateDialogViewModel.State>(State()) {
|
||||||
|
|
||||||
fun init(current: Manga, target: Manga) {
|
fun init(current: Manga, target: Manga) {
|
||||||
val applicableFlags = buildList {
|
val applicableFlags = buildList {
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
@@ -30,13 +31,19 @@ class MigrationListScreen(private val mangaIds: Collection<Long>, private val ex
|
|||||||
@Composable
|
@Composable
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { MigrationListScreenModel(mangaIds, extraSearchQuery) }
|
val viewModel = viewModel<MigrationListViewModel>(
|
||||||
val state by screenModel.state.collectAsState()
|
factory = MigrationListViewModel.Factory,
|
||||||
|
extras = CreationExtras {
|
||||||
|
set(MigrationListViewModel.MANGA_IDS_KEY, mangaIds)
|
||||||
|
set(MigrationListViewModel.EXTRA_SEARCH_QUERY_KEY, extraSearchQuery)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
LaunchedEffect(matchOverride) {
|
LaunchedEffect(matchOverride) {
|
||||||
val (current, target) = matchOverride ?: return@LaunchedEffect
|
val (current, target) = matchOverride ?: return@LaunchedEffect
|
||||||
screenModel.useMangaForMigration(
|
viewModel.useMangaForMigration(
|
||||||
current = current,
|
current = current,
|
||||||
target = target,
|
target = target,
|
||||||
onMissingChapters = {
|
onMissingChapters = {
|
||||||
@@ -46,8 +53,8 @@ class MigrationListScreen(private val mangaIds: Collection<Long>, private val ex
|
|||||||
matchOverride = null
|
matchOverride = null
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(screenModel) {
|
LaunchedEffect(viewModel) {
|
||||||
screenModel.navigateBackEvent.collect {
|
viewModel.navigateBackEvent.collect {
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,37 +68,37 @@ class MigrationListScreen(private val mangaIds: Collection<Long>, private val ex
|
|||||||
onSearchManually = { migrationItem ->
|
onSearchManually = { migrationItem ->
|
||||||
navigator push MigrateSearchScreen(migrationItem.manga.id)
|
navigator push MigrateSearchScreen(migrationItem.manga.id)
|
||||||
},
|
},
|
||||||
onSkip = { screenModel.removeManga(it) },
|
onSkip = { viewModel.removeManga(it) },
|
||||||
onMigrate = { screenModel.migrateNow(mangaId = it, replace = true) },
|
onMigrate = { viewModel.migrateNow(mangaId = it, replace = true) },
|
||||||
onCopy = { screenModel.migrateNow(mangaId = it, replace = false) },
|
onCopy = { viewModel.migrateNow(mangaId = it, replace = false) },
|
||||||
openMigrationDialog = screenModel::showMigrateDialog,
|
openMigrationDialog = viewModel::showMigrateDialog,
|
||||||
)
|
)
|
||||||
|
|
||||||
when (val dialog = state.dialog) {
|
when (val dialog = state.dialog) {
|
||||||
is MigrationListScreenModel.Dialog.Migrate -> {
|
is MigrationListViewModel.Dialog.Migrate -> {
|
||||||
MigrationMangaDialog(
|
MigrationMangaDialog(
|
||||||
onDismissRequest = screenModel::dismissDialog,
|
onDismissRequest = viewModel::dismissDialog,
|
||||||
copy = dialog.copy,
|
copy = dialog.copy,
|
||||||
totalCount = dialog.totalCount,
|
totalCount = dialog.totalCount,
|
||||||
skippedCount = dialog.skippedCount,
|
skippedCount = dialog.skippedCount,
|
||||||
onMigrate = {
|
onMigrate = {
|
||||||
if (dialog.copy) {
|
if (dialog.copy) {
|
||||||
screenModel.copyMangas()
|
viewModel.copyMangas()
|
||||||
} else {
|
} else {
|
||||||
screenModel.migrateMangas()
|
viewModel.migrateMangas()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is MigrationListScreenModel.Dialog.Progress -> {
|
is MigrationListViewModel.Dialog.Progress -> {
|
||||||
MigrationProgressDialog(
|
MigrationProgressDialog(
|
||||||
progress = dialog.progress,
|
progress = dialog.progress,
|
||||||
exitMigration = screenModel::cancelMigrate,
|
exitMigration = viewModel::cancelMigrate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
MigrationListScreenModel.Dialog.Exit -> {
|
MigrationListViewModel.Dialog.Exit -> {
|
||||||
MigrationExitDialog(
|
MigrationExitDialog(
|
||||||
onDismissRequest = screenModel::dismissDialog,
|
onDismissRequest = viewModel::dismissDialog,
|
||||||
exitMigration = navigator::pop,
|
exitMigration = navigator::pop,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -99,7 +106,7 @@ class MigrationListScreen(private val mangaIds: Collection<Long>, private val ex
|
|||||||
}
|
}
|
||||||
|
|
||||||
BackHandler(true) {
|
BackHandler(true) {
|
||||||
screenModel.showExitDialog()
|
viewModel.showExitDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-14
@@ -1,9 +1,10 @@
|
|||||||
package mihon.feature.migration.list
|
package mihon.feature.migration.list
|
||||||
|
|
||||||
import androidx.annotation.FloatRange
|
import androidx.annotation.FloatRange
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import androidx.lifecycle.viewmodel.CreationExtras
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
import eu.kanade.tachiyomi.source.getNameForMangaInfo
|
||||||
@@ -21,6 +22,7 @@ import kotlinx.coroutines.isActive
|
|||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import kotlinx.coroutines.sync.withPermit
|
import kotlinx.coroutines.sync.withPermit
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import mihon.domain.migration.usecases.MigrateMangaUseCase
|
import mihon.domain.migration.usecases.MigrateMangaUseCase
|
||||||
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
||||||
import mihon.feature.migration.list.models.MigratingManga
|
import mihon.feature.migration.list.models.MigratingManga
|
||||||
@@ -37,18 +39,32 @@ import tachiyomi.domain.source.service.SourceManager
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class MigrationListScreenModel(
|
class MigrationListViewModel(
|
||||||
mangaIds: Collection<Long>,
|
mangaIds: Collection<Long>,
|
||||||
extraSearchQuery: String?,
|
extraSearchQuery: String?,
|
||||||
private val preferences: SourcePreferences = Injekt.get(),
|
private val preferences: SourcePreferences = Injekt.get(),
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
private val getManga: GetManga = Injekt.get(),
|
private val getManga: GetManga = Injekt.get(),
|
||||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
|
||||||
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
|
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get(),
|
||||||
private val migrateManga: MigrateMangaUseCase = Injekt.get(),
|
private val migrateManga: MigrateMangaUseCase = Injekt.get(),
|
||||||
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get(),
|
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get(),
|
||||||
) : StateScreenModel<MigrationListScreenModel.State>(State()) {
|
) : StateViewModel<MigrationListViewModel.State>(State()) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val MANGA_IDS_KEY = CreationExtras.Key<Collection<Long>>()
|
||||||
|
|
||||||
|
val EXTRA_SEARCH_QUERY_KEY = CreationExtras.Key<String?>()
|
||||||
|
|
||||||
|
val Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
MigrationListViewModel(
|
||||||
|
mangaIds = get(MANGA_IDS_KEY)!!,
|
||||||
|
extraSearchQuery = get(EXTRA_SEARCH_QUERY_KEY),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val smartSearchEngine = SmartSourceSearchEngine(extraSearchQuery)
|
private val smartSearchEngine = SmartSourceSearchEngine(extraSearchQuery)
|
||||||
|
|
||||||
@@ -64,7 +80,7 @@ class MigrationListScreenModel(
|
|||||||
private var migrateJob: Job? = null
|
private var migrateJob: Job? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val manga = mangaIds
|
val manga = mangaIds
|
||||||
.map {
|
.map {
|
||||||
async {
|
async {
|
||||||
@@ -75,7 +91,7 @@ class MigrationListScreenModel(
|
|||||||
chapterCount = chapterInfo.chapterCount,
|
chapterCount = chapterInfo.chapterCount,
|
||||||
latestChapter = chapterInfo.latestChapter,
|
latestChapter = chapterInfo.latestChapter,
|
||||||
source = sourceManager.getOrStub(manga.source).getNameForMangaInfo(),
|
source = sourceManager.getOrStub(manga.source).getNameForMangaInfo(),
|
||||||
parentContext = screenModelScope.coroutineContext,
|
parentContext = viewModelScope.coroutineContext,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,7 +232,7 @@ class MigrationListScreenModel(
|
|||||||
fun useMangaForMigration(current: Long, target: Long, onMissingChapters: () -> Unit) {
|
fun useMangaForMigration(current: Long, target: Long, onMissingChapters: () -> Unit) {
|
||||||
val migratingManga = items.find { it.manga.id == current } ?: return
|
val migratingManga = items.find { it.manga.id == current } ?: return
|
||||||
migratingManga.searchResult.value = SearchResult.Searching
|
migratingManga.searchResult.value = SearchResult.Searching
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val result = migratingManga.migrationScope.async {
|
val result = migratingManga.migrationScope.async {
|
||||||
val manga = getManga.await(target) ?: return@async null
|
val manga = getManga.await(target) ?: return@async null
|
||||||
try {
|
try {
|
||||||
@@ -248,7 +264,7 @@ class MigrationListScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun migrateMangas(replace: Boolean) {
|
private fun migrateMangas(replace: Boolean) {
|
||||||
migrateJob = screenModelScope.launchIO {
|
migrateJob = viewModelScope.launchIO {
|
||||||
mutableState.update { it.copy(dialog = Dialog.Progress(0f)) }
|
mutableState.update { it.copy(dialog = Dialog.Progress(0f)) }
|
||||||
val items = items
|
val items = items
|
||||||
try {
|
try {
|
||||||
@@ -292,7 +308,7 @@ class MigrationListScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun migrateNow(mangaId: Long, replace: Boolean) {
|
fun migrateNow(mangaId: Long, replace: Boolean) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val manga = items.find { it.manga.id == mangaId } ?: return@launchIO
|
val manga = items.find { it.manga.id == mangaId } ?: return@launchIO
|
||||||
val target = (manga.searchResult.value as? SearchResult.Success)?.manga ?: return@launchIO
|
val target = (manga.searchResult.value as? SearchResult.Success)?.manga ?: return@launchIO
|
||||||
migrateManga(current = manga.manga, target = target, replace = replace)
|
migrateManga(current = manga.manga, target = target, replace = replace)
|
||||||
@@ -302,7 +318,7 @@ class MigrationListScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun removeManga(mangaId: Long) {
|
fun removeManga(mangaId: Long) {
|
||||||
screenModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val item = items.find { it.manga.id == mangaId } ?: return@launchIO
|
val item = items.find { it.manga.id == mangaId } ?: return@launchIO
|
||||||
removeManga(item)
|
removeManga(item)
|
||||||
item.migrationScope.cancel()
|
item.migrationScope.cancel()
|
||||||
@@ -314,8 +330,7 @@ class MigrationListScreenModel(
|
|||||||
mutableState.update { it.copy(items = items.toMutableList().apply { remove(item) }) }
|
mutableState.update { it.copy(items = items.toMutableList().apply { remove(item) }) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDispose() {
|
override fun onCleared() {
|
||||||
super.onDispose()
|
|
||||||
items.forEach {
|
items.forEach {
|
||||||
it.migrationScope.cancel()
|
it.migrationScope.cancel()
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ package mihon.feature.upcoming
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
@@ -15,12 +15,12 @@ class UpcomingScreen : Screen() {
|
|||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val screenModel = rememberScreenModel { UpcomingScreenModel() }
|
val viewModel = viewModel<UpcomingViewModel>()
|
||||||
val state by screenModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
UpcomingScreenContent(
|
UpcomingScreenContent(
|
||||||
state = state,
|
state = state,
|
||||||
setSelectedYearMonth = screenModel::setSelectedYearMonth,
|
setSelectedYearMonth = viewModel::setSelectedYearMonth,
|
||||||
onClickUpcoming = { navigator.push(MangaScreen(it.id)) },
|
onClickUpcoming = { navigator.push(MangaScreen(it.id)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import java.time.YearMonth
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UpcomingScreenContent(
|
fun UpcomingScreenContent(
|
||||||
state: UpcomingScreenModel.State,
|
state: UpcomingViewModel.State,
|
||||||
setSelectedYearMonth: (YearMonth) -> Unit,
|
setSelectedYearMonth: (YearMonth) -> Unit,
|
||||||
onClickUpcoming: (manga: Manga) -> Unit,
|
onClickUpcoming: (manga: Manga) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
|||||||
+5
-5
@@ -2,13 +2,13 @@ package mihon.feature.upcoming
|
|||||||
|
|
||||||
import androidx.compose.ui.util.fastMap
|
import androidx.compose.ui.util.fastMap
|
||||||
import androidx.compose.ui.util.fastMapIndexedNotNull
|
import androidx.compose.ui.util.fastMapIndexedNotNull
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import androidx.lifecycle.viewModelScope
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
|
||||||
import eu.kanade.core.util.insertSeparatorsReversed
|
import eu.kanade.core.util.insertSeparatorsReversed
|
||||||
import eu.kanade.tachiyomi.util.lang.toLocalDate
|
import eu.kanade.tachiyomi.util.lang.toLocalDate
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import mihon.core.viewmodel.StateViewModel
|
||||||
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@@ -16,12 +16,12 @@ import uy.kohesive.injekt.api.get
|
|||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.YearMonth
|
import java.time.YearMonth
|
||||||
|
|
||||||
class UpcomingScreenModel(
|
class UpcomingViewModel(
|
||||||
private val getUpcomingManga: GetUpcomingManga = Injekt.get(),
|
private val getUpcomingManga: GetUpcomingManga = Injekt.get(),
|
||||||
) : StateScreenModel<UpcomingScreenModel.State>(State()) {
|
) : StateViewModel<UpcomingViewModel.State>(State()) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launch {
|
viewModelScope.launch {
|
||||||
getUpcomingManga.subscribe().collectLatest {
|
getUpcomingManga.subscribe().collectLatest {
|
||||||
mutableState.update { state ->
|
mutableState.update { state ->
|
||||||
val upcomingItems = it.toUpcomingUIModels()
|
val upcomingItems = it.toUpcomingUIModels()
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
plugins {
|
||||||
|
alias(mihonx.plugins.android.library)
|
||||||
|
alias(mihonx.plugins.spotless)
|
||||||
|
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "mihon.core.viewmodel"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.androidx.lifecycle.viewmodel)
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest />
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package mihon.core.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
abstract class StateViewModel<S>(initialState: S) : ViewModel() {
|
||||||
|
protected val mutableState: MutableStateFlow<S> = MutableStateFlow(initialState)
|
||||||
|
val state: StateFlow<S> = mutableState.asStateFlow()
|
||||||
|
}
|
||||||
@@ -100,6 +100,8 @@ androidx-interpolator = { module = "androidx.interpolator:interpolator", version
|
|||||||
androidx-lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "androidx-lifecycle" }
|
androidx-lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "androidx-lifecycle" }
|
||||||
androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "androidx-lifecycle" }
|
androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "androidx-lifecycle" }
|
||||||
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "androidx-lifecycle" }
|
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "androidx-lifecycle" }
|
||||||
|
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
|
||||||
|
androidx-lifecycle-viewmodelCompose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
|
||||||
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" }
|
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "androidx-paging" }
|
||||||
androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "androidx-paging" }
|
androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "androidx-paging" }
|
||||||
androidx-preference = { module = "androidx.preference:preference-ktx", version.ref = "androidx-preference" }
|
androidx-preference = { module = "androidx.preference:preference-ktx", version.ref = "androidx-preference" }
|
||||||
@@ -178,7 +180,6 @@ swipe = { module = "me.saket.swipe:swipe", version.ref = "swipe" }
|
|||||||
tapmoc-gradle = { module = "com.gradleup.tapmoc:tapmoc-gradle-plugin", version.ref = "tapmoc" }
|
tapmoc-gradle = { module = "com.gradleup.tapmoc:tapmoc-gradle-plugin", version.ref = "tapmoc" }
|
||||||
unifile = { module = "com.github.mihon:unifile", version.ref = "unifile" }
|
unifile = { module = "com.github.mihon:unifile", version.ref = "unifile" }
|
||||||
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
|
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
|
||||||
voyager-screenModel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" }
|
|
||||||
voyager-tabNavigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" }
|
voyager-tabNavigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" }
|
||||||
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" }
|
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" }
|
||||||
xmlutil-core = { module = "io.github.pdvrieze.xmlutil:core-android", version.ref = "xmlutil" }
|
xmlutil-core = { module = "io.github.pdvrieze.xmlutil:core-android", version.ref = "xmlutil" }
|
||||||
@@ -203,7 +204,7 @@ spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
|
|||||||
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
|
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
androidx-lifecycle = ["androidx-lifecycle-common", "androidx-lifecycle-process", "androidx-lifecycle-runtime"]
|
androidx-lifecycle = ["androidx-lifecycle-common", "androidx-lifecycle-process", "androidx-lifecycle-runtime", "androidx-lifecycle-viewmodel", "androidx-lifecycle-viewmodelCompose"]
|
||||||
coil = ["coil-core", "coil-gif", "coil-compose", "coil-network-okhttp"]
|
coil = ["coil-core", "coil-gif", "coil-compose", "coil-network-okhttp"]
|
||||||
kotlinx-coroutines = ["kotlinx-coroutines-core", "kotlinx-coroutines-android", "kotlinx-coroutines-guava"]
|
kotlinx-coroutines = ["kotlinx-coroutines-core", "kotlinx-coroutines-android", "kotlinx-coroutines-guava"]
|
||||||
markdown = ["markdown-core", "markdown-coil"]
|
markdown = ["markdown-core", "markdown-coil"]
|
||||||
@@ -212,4 +213,4 @@ serialization = ["kotlinx-serialization-json", "kotlinx-serialization-jsonOkio",
|
|||||||
shizuku = ["shizuku-api", "shizuku-provider"]
|
shizuku = ["shizuku-api", "shizuku-provider"]
|
||||||
sqldelight = ["sqldelight-androidxDriver", "sqldelight-async", "sqldelight-coroutines", "sqldelight-androidxPaging"]
|
sqldelight = ["sqldelight-androidxDriver", "sqldelight-async", "sqldelight-coroutines", "sqldelight-androidxPaging"]
|
||||||
test = ["junit-jupiter", "kotest-assertions", "mockk"]
|
test = ["junit-jupiter", "kotest-assertions", "mockk"]
|
||||||
voyager = ["voyager-navigator", "voyager-screenModel", "voyager-tabNavigator", "voyager-transitions"]
|
voyager = ["voyager-navigator", "voyager-tabNavigator", "voyager-transitions"]
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ include(":baseline-profile")
|
|||||||
include(":core-metadata")
|
include(":core-metadata")
|
||||||
include(":core:archive")
|
include(":core:archive")
|
||||||
include(":core:common")
|
include(":core:common")
|
||||||
|
include(":core:viewmodel")
|
||||||
include(":data")
|
include(":data")
|
||||||
include(":domain")
|
include(":domain")
|
||||||
include(":i18n")
|
include(":i18n")
|
||||||
|
|||||||
Reference in New Issue
Block a user