diff --git a/app/src/main/java/eu/kanade/domain/SYDomainModule.kt b/app/src/main/java/eu/kanade/domain/SYDomainModule.kt index 61e62a2f6..b93d1f51e 100644 --- a/app/src/main/java/eu/kanade/domain/SYDomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/SYDomainModule.kt @@ -69,7 +69,7 @@ import tachiyomi.domain.source.interactor.GetSavedSearchBySourceIdFeed import tachiyomi.domain.source.interactor.GetSavedSearchGlobalFeed import tachiyomi.domain.source.interactor.InsertFeedSavedSearch import tachiyomi.domain.source.interactor.InsertSavedSearch -import tachiyomi.domain.source.interactor.SwapFeedOrder +import tachiyomi.domain.source.interactor.ReorderFeed import tachiyomi.domain.source.repository.FeedSavedSearchRepository import tachiyomi.domain.source.repository.SavedSearchRepository import tachiyomi.domain.track.interactor.IsTrackUnfollowed @@ -157,7 +157,7 @@ class SYDomainModule : InjektModule { addFactory { GetSavedSearchGlobalFeed(get()) } addFactory { GetSavedSearchBySourceIdFeed(get()) } // KMK --> - addFactory { SwapFeedOrder(get()) } + addFactory { ReorderFeed(get()) } // KMK <-- addSingletonFactory { CustomMangaRepositoryImpl(get()) } diff --git a/app/src/main/java/eu/kanade/presentation/browse/FeedOrderScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/FeedOrderScreen.kt new file mode 100644 index 000000000..723083d45 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/browse/FeedOrderScreen.kt @@ -0,0 +1,60 @@ +package eu.kanade.presentation.browse + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import eu.kanade.presentation.browse.components.FeedOrderListItem +import eu.kanade.tachiyomi.ui.browse.feed.FeedScreenState +import tachiyomi.domain.source.model.FeedSavedSearch +import tachiyomi.i18n.MR +import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.components.material.topSmallPaddingValues +import tachiyomi.presentation.core.screens.EmptyScreen +import tachiyomi.presentation.core.screens.LoadingScreen +import tachiyomi.presentation.core.util.plus + +@Composable +fun FeedOrderScreen( + state: FeedScreenState, + onClickDelete: (FeedSavedSearch) -> Unit, + onClickMoveUp: (FeedSavedSearch) -> Unit, + onClickMoveDown: (FeedSavedSearch) -> Unit, +) { + when { + state.isLoading -> LoadingScreen() + state.isEmpty -> EmptyScreen( + stringRes = MR.strings.empty_screen, + ) + + else -> { + val lazyListState = rememberLazyListState() + val feeds = state.items ?: emptyList() + LazyColumn( + state = lazyListState, + contentPadding = topSmallPaddingValues + + PaddingValues(horizontal = MaterialTheme.padding.medium), + verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + ) { + itemsIndexed( + items = feeds, + key = { _, feed -> "feed-${feed.feed.id}" }, + ) { index, feed -> + FeedOrderListItem( + modifier = Modifier.animateItem(), + title = feed.title, + canMoveUp = index != 0, + canMoveDown = index != feeds.lastIndex, + onMoveUp = { onClickMoveUp(feed.feed) }, + onMoveDown = { onClickMoveDown(feed.feed) }, + onDelete = { onClickDelete(feed.feed) }, + ) + } + } + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/browse/FeedScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/FeedScreen.kt index 9f60de4e2..5fad60d1b 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/FeedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/FeedScreen.kt @@ -1,9 +1,7 @@ package eu.kanade.presentation.browse import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -12,7 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog @@ -48,7 +46,6 @@ import tachiyomi.domain.source.model.FeedSavedSearch import tachiyomi.domain.source.model.SavedSearch import tachiyomi.domain.source.model.Source import tachiyomi.i18n.MR -import tachiyomi.i18n.kmk.KMR import tachiyomi.i18n.sy.SYMR import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.material.PullRefresh @@ -76,7 +73,7 @@ fun FeedScreen( onClickSavedSearch: (SavedSearch, CatalogueSource) -> Unit, onClickSource: (CatalogueSource) -> Unit, // KMK --> - onLongClickFeed: (FeedItemUI, FeedSavedSearch?, FeedSavedSearch?) -> Unit, + onLongClickFeed: (FeedItemUI, Boolean, Boolean) -> Unit, // KMK <-- onClickManga: (Manga) -> Unit, // KMK --> @@ -114,15 +111,10 @@ fun FeedScreen( ) { // KMK --> val feeds = state.items.orEmpty() - // KMK <-- - items( - feeds, - key = { "feed-${it.feed.id}" }, - ) { item -> - // KMK --> - val index = feeds.indexOf(item) - val prevFeed = feeds.getOrNull(index - 1) - val nextFeed = feeds.getOrNull(index + 1) + itemsIndexed( + items = feeds, + key = { _, it -> "feed-${it.feed.id}" }, + ) { index, item -> // KMK <-- GlobalSearchResultItem( modifier = Modifier.animateItem(), @@ -130,7 +122,11 @@ fun FeedScreen( subtitle = item.subtitle, onLongClick = { // KMK --> - onLongClickFeed(item, prevFeed?.feed, nextFeed?.feed) + onLongClickFeed( + item, + index != 0, + index != feeds.lastIndex, + ) // KMK <-- }, onClick = { @@ -337,7 +333,8 @@ fun RadioSelectorSearchable( SourcesSearch( searchQuery = queryString, onChangeSearchQuery = onChangeSearchQuery ?: {}, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() .padding(bottom = MaterialTheme.padding.small), ).takeIf { onChangeSearchQuery != null } options.forEachIndexed { index, option -> @@ -356,65 +353,3 @@ fun RadioSelectorSearchable( } } // KMK <-- - -@Composable -fun FeedDeleteConfirmDialog( - feed: FeedSavedSearch, - onDismiss: () -> Unit, - onClickDeleteConfirm: (FeedSavedSearch) -> Unit, -) { - AlertDialog( - title = { - Text(text = stringResource(SYMR.strings.feed)) - }, - text = { - Text(text = stringResource(SYMR.strings.feed_delete)) - }, - onDismissRequest = onDismiss, - confirmButton = { - TextButton(onClick = { onClickDeleteConfirm(feed) }) { - Text(text = stringResource(MR.strings.action_delete)) - } - }, - ) -} - -// KMK --> -@Composable -fun FeedActionsDialog( - feedItem: FeedItemUI, - hasPrevFeed: Boolean, - hasNextFeed: Boolean, - onDismiss: () -> Unit, - onClickDelete: () -> Unit, - onMoveUp: () -> Unit, - onMoveDown: () -> Unit, - onMoveBottom: () -> Unit, -) { - AlertDialog( - title = { - Text(text = stringResource(SYMR.strings.feed)) - }, - text = { - Text(text = if (feedItem.savedSearch != null) feedItem.savedSearch.name else feedItem.source?.name ?: "") - }, - onDismissRequest = onDismiss, - confirmButton = { - FlowRow(horizontalArrangement = Arrangement.SpaceEvenly) { - TextButton(onClick = onMoveUp, enabled = hasPrevFeed) { - Text(text = stringResource(KMR.strings.action_move_up)) - } - TextButton(onClick = onMoveDown, enabled = hasNextFeed) { - Text(text = stringResource(KMR.strings.action_move_down)) - } - TextButton(onClick = onMoveBottom, enabled = hasNextFeed) { - Text(text = stringResource(KMR.strings.action_move_bottom)) - } - TextButton(onClick = onClickDelete) { - Text(text = stringResource(MR.strings.action_delete)) - } - } - }, - ) -} -// KMK <-- diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourceFeedOrderScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourceFeedOrderScreen.kt new file mode 100644 index 000000000..637c61450 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/browse/SourceFeedOrderScreen.kt @@ -0,0 +1,95 @@ +package eu.kanade.presentation.browse + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.SortByAlpha +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import eu.kanade.presentation.browse.components.FeedOrderListItem +import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.AppBarTitle +import eu.kanade.tachiyomi.ui.browse.source.feed.SourceFeedState +import kotlinx.collections.immutable.persistentListOf +import tachiyomi.domain.source.model.FeedSavedSearch +import tachiyomi.i18n.MR +import tachiyomi.i18n.kmk.KMR +import tachiyomi.presentation.core.components.material.Scaffold +import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.components.material.topSmallPaddingValues +import tachiyomi.presentation.core.i18n.stringResource +import tachiyomi.presentation.core.screens.EmptyScreen +import tachiyomi.presentation.core.screens.LoadingScreen +import tachiyomi.presentation.core.util.plus + +@Composable +fun SourceFeedOrderScreen( + state: SourceFeedState, + onClickDelete: (FeedSavedSearch) -> Unit, + onClickMoveUp: (FeedSavedSearch) -> Unit, + onClickMoveDown: (FeedSavedSearch) -> Unit, + onClickSortAlphabetically: () -> Unit, + navigateUp: (() -> Unit)? = null, +) { + Scaffold( + topBar = { scrollBehavior -> + AppBar( + titleContent = { + AppBarTitle(stringResource(KMR.strings.action_sort_feed)) + }, + navigateUp = navigateUp, + actions = { + persistentListOf( + AppBar.Action( + title = stringResource(MR.strings.action_sort), + icon = Icons.Outlined.SortByAlpha, + onClick = onClickSortAlphabetically, + ), + ) + }, + isActionMode = false, + scrollBehavior = scrollBehavior, + ) + }, + ) { paddingValues -> + when { + state.isLoading -> LoadingScreen() + state.items + .filterIsInstance() + .isEmpty() -> EmptyScreen( + stringRes = MR.strings.empty_screen, + ) + + else -> { + val lazyListState = rememberLazyListState() + val feeds = state.items + .filterIsInstance() + LazyColumn( + state = lazyListState, + contentPadding = paddingValues + topSmallPaddingValues + + PaddingValues(horizontal = MaterialTheme.padding.medium), + verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + ) { + itemsIndexed( + items = feeds, + key = { _, feed -> "feed-${feed.feed.id}" }, + ) { index, feed -> + FeedOrderListItem( + modifier = Modifier.animateItem(), + title = feed.title, + canMoveUp = index != 0, + canMoveDown = index != feeds.lastIndex, + onMoveUp = { onClickMoveUp(feed.feed) }, + onMoveDown = { onClickMoveDown(feed.feed) }, + onDelete = { onClickDelete(feed.feed) }, + ) + } + } + } + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourceFeedScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourceFeedScreen.kt index 59f465bb8..8aa8c89dc 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/SourceFeedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/SourceFeedScreen.kt @@ -2,24 +2,22 @@ package eu.kanade.presentation.browse import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Public -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import dev.icerock.moko.resources.StringResource import eu.kanade.presentation.browse.components.BrowseSourceFloatingActionButton import eu.kanade.presentation.browse.components.GlobalSearchCardRow import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem import eu.kanade.presentation.browse.components.GlobalSearchResultItem -import eu.kanade.presentation.browse.components.SourceSettingsButton +import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBarActions import eu.kanade.presentation.components.AppBarTitle import eu.kanade.presentation.components.SearchToolbar @@ -32,21 +30,18 @@ import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.source.model.FeedSavedSearch import tachiyomi.domain.source.model.SavedSearch import tachiyomi.i18n.MR +import tachiyomi.i18n.kmk.KMR import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.topSmallPaddingValues import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.presentation.core.util.plus -import tachiyomi.source.local.LocalSource sealed class SourceFeedUI { abstract val id: Long - abstract val title: String - @Composable - @ReadOnlyComposable - get + abstract val title: Any abstract val results: List? @@ -54,10 +49,8 @@ sealed class SourceFeedUI { data class Latest(override val results: List?) : SourceFeedUI() { override val id: Long = -1 - override val title: String - @Composable - @ReadOnlyComposable - get() = stringResource(MR.strings.latest) + override val title: StringResource + get() = MR.strings.latest override fun withResults(results: List?): SourceFeedUI { return copy(results = results) @@ -65,10 +58,8 @@ sealed class SourceFeedUI { } data class Browse(override val results: List?) : SourceFeedUI() { override val id: Long = -2 - override val title: String - @Composable - @ReadOnlyComposable - get() = stringResource(MR.strings.browse) + override val title: StringResource + get() = MR.strings.browse override fun withResults(results: List?): SourceFeedUI { return copy(results = results) @@ -83,8 +74,6 @@ sealed class SourceFeedUI { get() = feed.id override val title: String - @Composable - @ReadOnlyComposable get() = savedSearch.name override fun withResults(results: List?): SourceFeedUI { @@ -103,7 +92,10 @@ fun SourceFeedScreen( onClickBrowse: () -> Unit, onClickLatest: () -> Unit, onClickSavedSearch: (SavedSearch) -> Unit, - onClickDelete: (FeedSavedSearch) -> Unit, + // KMK --> + // onClickDelete: (FeedSavedSearch) -> Unit, + onLongClickFeed: (SourceFeedUI.SourceSavedSearch, Boolean, Boolean) -> Unit, + // KMK <-- onClickManga: (Manga) -> Unit, onClickSearch: (String) -> Unit, searchQuery: String?, @@ -111,8 +103,9 @@ fun SourceFeedScreen( getMangaState: @Composable (Manga) -> State, // KMK --> navigateUp: () -> Unit, - onWebViewClick: () -> Unit, - sourceId: Long, + onWebViewClick: (() -> Unit)?, + onSourceSettingClick: (() -> Unit?)?, + onSortFeedClick: (() -> Unit)?, onLongClickManga: (Manga) -> Unit, bulkFavoriteScreenModel: BulkFavoriteScreenModel, // KMK <-- @@ -150,7 +143,8 @@ fun SourceFeedScreen( // KMK --> navigateUp = navigateUp, onWebViewClick = onWebViewClick, - sourceId = sourceId, + onSourceSettingClick = onSourceSettingClick, + onSortFeedClick = onSortFeedClick, toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode, // KMK <-- ) @@ -174,7 +168,10 @@ fun SourceFeedScreen( onClickBrowse = onClickBrowse, onClickLatest = onClickLatest, onClickSavedSearch = onClickSavedSearch, - onClickDelete = onClickDelete, + // KMK --> + // onClickDelete = onClickDelete, + onLongClickFeed = onLongClickFeed, + // KMK <-- onClickManga = onClickManga, // KMK --> onLongClickManga = onLongClickManga, @@ -195,7 +192,10 @@ fun SourceFeedList( onClickBrowse: () -> Unit, onClickLatest: () -> Unit, onClickSavedSearch: (SavedSearch) -> Unit, - onClickDelete: (FeedSavedSearch) -> Unit, + // KMK --> + // onClickDelete: (FeedSavedSearch) -> Unit, + onLongClickFeed: (SourceFeedUI.SourceSavedSearch, Boolean, Boolean) -> Unit, + // KMK <-- onClickManga: (Manga) -> Unit, // KMK --> onLongClickManga: (Manga) -> Unit, @@ -205,17 +205,32 @@ fun SourceFeedList( ScrollbarLazyColumn( contentPadding = paddingValues + topSmallPaddingValues, ) { - items( + // KMK --> + itemsIndexed( items, - key = { "source-feed-${it.id}" }, - ) { item -> + key = { _, it -> "source-feed-${it.id}" }, + ) { index, item -> + // KMK <-- GlobalSearchResultItem( modifier = Modifier.animateItem(), - title = item.title, + title = + // KMK --> + if (item !is SourceFeedUI.SourceSavedSearch) { + stringResource(item.title as StringResource) + } else { + // KMK <-- + item.title + }, subtitle = null, onLongClick = if (item is SourceFeedUI.SourceSavedSearch) { { - onClickDelete(item.feed) + // KMK --> + onLongClickFeed( + item, + index != items.size - items.filterIsInstance().size, + index != items.lastIndex, + ) + // KMK <-- } } else { null @@ -283,8 +298,9 @@ fun SourceFeedToolbar( onClickSearch: (String) -> Unit, // KMK --> navigateUp: () -> Unit, - onWebViewClick: () -> Unit, - sourceId: Long, + onWebViewClick: (() -> Unit)?, + onSourceSettingClick: (() -> Unit?)?, + onSortFeedClick: (() -> Unit)?, toggleSelectionMode: () -> Unit, // KMK <-- ) { @@ -302,24 +318,39 @@ fun SourceFeedToolbar( // KMK --> actions = { AppBarActions( - actions = persistentListOf( - bulkSelectionButton(toggleSelectionMode), - ), - ) - persistentListOf( - if (sourceId != LocalSource.ID) { - IconButton( - onClick = onWebViewClick, - ) { - Icon( - imageVector = Icons.Outlined.Public, - contentDescription = stringResource(MR.strings.action_web_view), - ) + actions = persistentListOf().builder() + .apply { + add(bulkSelectionButton(toggleSelectionMode)) + + onWebViewClick?.let { + add( + AppBar.Action( + title = stringResource(MR.strings.action_web_view), + onClick = { onWebViewClick() }, + icon = Icons.Outlined.Public, + ), + ) + } + + onSortFeedClick?.let { + add( + AppBar.OverflowAction( + title = stringResource(KMR.strings.action_sort_feed), + onClick = { onSortFeedClick() }, + ), + ) + } + + onSourceSettingClick?.let { + add( + AppBar.OverflowAction( + title = stringResource(MR.strings.label_settings), + onClick = { onSourceSettingClick() }, + ), + ) + } } - } else { - null - }, - SourceSettingsButton(sourceId), + .build(), ) }, // KMK <-- diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/FeedOrderListItem.kt b/app/src/main/java/eu/kanade/presentation/browse/components/FeedOrderListItem.kt new file mode 100644 index 000000000..ebba5a22c --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/browse/components/FeedOrderListItem.kt @@ -0,0 +1,90 @@ +package eu.kanade.presentation.browse.components + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowDropDown +import androidx.compose.material.icons.outlined.ArrowDropUp +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.Splitscreen +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import tachiyomi.i18n.MR +import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.i18n.stringResource + +@Composable +fun FeedOrderListItem( + title: String, + canMoveUp: Boolean, + canMoveDown: Boolean, + onMoveUp: () -> Unit, + onMoveDown: () -> Unit, + onDelete: () -> Unit, + modifier: Modifier = Modifier, +) { + ElevatedCard( + modifier = modifier, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding( + start = MaterialTheme.padding.medium, + top = MaterialTheme.padding.medium, + end = MaterialTheme.padding.medium, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = Icons.Outlined.Splitscreen, + contentDescription = null, + ) + Text( + text = title, + modifier = Modifier + .padding(start = MaterialTheme.padding.medium), + ) + } + Row { + IconButton( + onClick = onMoveUp, + enabled = canMoveUp, + ) { + Icon(imageVector = Icons.Outlined.ArrowDropUp, contentDescription = null) + } + IconButton( + onClick = onMoveDown, + enabled = canMoveDown, + ) { + Icon(imageVector = Icons.Outlined.ArrowDropDown, contentDescription = null) + } + Spacer(modifier = Modifier.weight(1f)) + IconButton(onClick = onDelete) { + Icon(imageVector = Icons.Outlined.Delete, contentDescription = stringResource(MR.strings.action_delete)) + } + } + } +} + +@Preview +@Composable +private fun FeedOrderListItemPreview() { + FeedOrderListItem( + title = "Feed 1", + canMoveUp = true, + canMoveDown = true, + onMoveUp = {}, + onMoveDown = {}, + onDelete = {}, + ) +} diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/SourceFeedDialogs.kt b/app/src/main/java/eu/kanade/presentation/browse/components/SourceFeedDialogs.kt index 1e321f6c8..f16b850dc 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/SourceFeedDialogs.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/SourceFeedDialogs.kt @@ -1,10 +1,38 @@ package eu.kanade.presentation.browse.components +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ArrowDownward +import androidx.compose.material.icons.outlined.ArrowUpward +import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.AlertDialog +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import eu.kanade.presentation.components.AdaptiveSheet +import eu.kanade.presentation.components.TabbedDialogPaddings +import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight +import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget +import tachiyomi.domain.source.model.FeedSavedSearch import tachiyomi.i18n.MR +import tachiyomi.i18n.kmk.KMR import tachiyomi.i18n.sy.SYMR import tachiyomi.presentation.core.i18n.stringResource @@ -60,3 +88,153 @@ fun SourceFeedDeleteDialog( }, ) } + +// KMK --> +private val PaddingSize = 16.dp + +private val ButtonPadding = PaddingValues(top = 16.dp, bottom = 16.dp) +private val TitlePadding = PaddingValues(bottom = 16.dp, top = 8.dp) + +@Composable +fun FeedActionsDialog( + feed: FeedSavedSearch, + title: String, + canMoveUp: Boolean, + canMoveDown: Boolean, + onDismissRequest: () -> Unit, + onClickDelete: (FeedSavedSearch) -> Unit, + onMoveUp: (FeedSavedSearch) -> Unit, + onMoveDown: (FeedSavedSearch) -> Unit, + modifier: Modifier = Modifier, +) { + val minHeight = LocalPreferenceMinHeight.current + + AdaptiveSheet( + modifier = modifier, + onDismissRequest = onDismissRequest, + ) { + Column( + modifier = Modifier + .padding( + vertical = TabbedDialogPaddings.Vertical, + horizontal = TabbedDialogPaddings.Horizontal, + ) + .fillMaxWidth(), + ) { + Text( + modifier = Modifier.padding(TitlePadding), + text = title, + style = MaterialTheme.typography.headlineMedium, + ) + + Spacer(Modifier.height(PaddingSize)) + + if (canMoveUp) { + TextPreferenceWidget( + title = stringResource(KMR.strings.action_move_up), + icon = Icons.Outlined.ArrowUpward, + onPreferenceClick = { + onDismissRequest() + onMoveUp(feed) + }, + ) + + HorizontalDivider() + } + + if (canMoveDown) { + TextPreferenceWidget( + title = stringResource(KMR.strings.action_move_down), + icon = Icons.Outlined.ArrowDownward, + onPreferenceClick = { + onDismissRequest() + onMoveDown(feed) + }, + ) + + HorizontalDivider() + } + + TextPreferenceWidget( + title = stringResource(MR.strings.action_delete), + icon = Icons.Outlined.Delete, + onPreferenceClick = { + onDismissRequest() + onClickDelete(feed) + }, + ) + + Row( + modifier = Modifier + .sizeIn(minHeight = minHeight) + .clickable { onDismissRequest.invoke() } + .padding(ButtonPadding) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + OutlinedButton(onClick = onDismissRequest, modifier = Modifier.fillMaxWidth()) { + Text( + modifier = Modifier + .padding(vertical = 8.dp), + text = stringResource(MR.strings.action_cancel), + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.titleLarge, + fontSize = 16.sp, + ) + } + } + } + } +} + +@Preview +@Composable +private fun FeedActionsDialogPreview() { + FeedActionsDialog( + feed = FeedSavedSearch( + id = 1, + source = 1, + savedSearch = null, + global = false, + feedOrder = 0, + ), + title = "Feed 1", + canMoveUp = true, + canMoveDown = true, + onDismissRequest = { }, + onClickDelete = { }, + onMoveUp = { }, + onMoveDown = { }, + ) +} + +@Composable +fun FeedSortAlphabeticallyDialog( + onDismissRequest: () -> Unit, + onSort: () -> Unit, +) { + AlertDialog( + onDismissRequest = onDismissRequest, + confirmButton = { + TextButton(onClick = { + onSort() + onDismissRequest() + }) { + Text(text = stringResource(MR.strings.action_ok)) + } + }, + dismissButton = { + TextButton(onClick = onDismissRequest) { + Text(text = stringResource(MR.strings.action_cancel)) + } + }, + title = { + Text(text = stringResource(KMR.strings.action_sort_feed)) + }, + text = { + Text(text = stringResource(KMR.strings.sort_feed_confirmation)) + }, + ) +} +// KMK <-- diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/SourceSettingsButton.kt b/app/src/main/java/eu/kanade/presentation/browse/components/SourceSettingsButton.kt deleted file mode 100644 index 2a7a4d338..000000000 --- a/app/src/main/java/eu/kanade/presentation/browse/components/SourceSettingsButton.kt +++ /dev/null @@ -1,49 +0,0 @@ -package eu.kanade.presentation.browse.components - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Settings -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.domain.source.model.installedExtension -import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreen -import exh.source.EH_SOURCE_ID -import exh.source.EXH_SOURCE_ID -import tachiyomi.domain.source.model.Source -import tachiyomi.i18n.MR -import tachiyomi.presentation.core.i18n.stringResource -import tachiyomi.source.local.LocalSource - -@Composable -fun SourceSettingsButton( - id: Long, - @Suppress("UNUSED_PARAMETER") modifier: Modifier = Modifier, -) { - // Create a fake source - val source = Source(id, "", "", supportsLatest = false, isStub = false) - SourceSettingsButton(source = source) -} - -@Composable -fun SourceSettingsButton( - source: Source, - @Suppress("UNUSED_PARAMETER") modifier: Modifier = Modifier, -) { - // Avoid E-Hentai & ExHentai which is built-in & not actually installed extensions - if (source.id == LocalSource.ID || source.id == EH_SOURCE_ID || source.id == EXH_SOURCE_ID) return - - val navigator = LocalNavigator.currentOrThrow - IconButton(onClick = { - if (source.installedExtension !== null) { - navigator.push(ExtensionDetailsScreen(source.installedExtension!!.pkgName)) - } - }) { - Icon( - imageVector = Icons.Outlined.Settings, - contentDescription = stringResource(MR.strings.label_settings), - ) - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedScreenModel.kt index d72df0b57..794395b8a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedScreenModel.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.produceState import androidx.compose.ui.util.fastAny import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope -import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.toDomainManga import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.browse.FeedItemUI @@ -41,8 +40,9 @@ import tachiyomi.domain.source.interactor.GetFeedSavedSearchGlobal import tachiyomi.domain.source.interactor.GetSavedSearchBySourceId import tachiyomi.domain.source.interactor.GetSavedSearchGlobalFeed import tachiyomi.domain.source.interactor.InsertFeedSavedSearch -import tachiyomi.domain.source.interactor.SwapFeedOrder +import tachiyomi.domain.source.interactor.ReorderFeed import tachiyomi.domain.source.model.FeedSavedSearch +import tachiyomi.domain.source.model.FeedSavedSearchUpdate import tachiyomi.domain.source.model.SavedSearch import tachiyomi.domain.source.service.SourceManager import uy.kohesive.injekt.Injekt @@ -59,15 +59,14 @@ open class FeedScreenModel( val sourcePreferences: SourcePreferences = Injekt.get(), private val getManga: GetManga = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), - private val updateManga: UpdateManga = Injekt.get(), - private val getFeedSavedSearchGlobal: GetFeedSavedSearchGlobal = Injekt.get(), + getFeedSavedSearchGlobal: GetFeedSavedSearchGlobal = Injekt.get(), private val getSavedSearchGlobalFeed: GetSavedSearchGlobalFeed = Injekt.get(), private val countFeedSavedSearchGlobal: CountFeedSavedSearchGlobal = Injekt.get(), private val getSavedSearchBySourceId: GetSavedSearchBySourceId = Injekt.get(), private val insertFeedSavedSearch: InsertFeedSavedSearch = Injekt.get(), private val deleteFeedSavedSearchById: DeleteFeedSavedSearchById = Injekt.get(), // KMK --> - private val swapFeedOrder: SwapFeedOrder = Injekt.get(), + private val reorderFeed: ReorderFeed = Injekt.get(), // KMK <-- ) : StateScreenModel(FeedScreenState()) { @@ -159,13 +158,17 @@ open class FeedScreenModel( // KMK --> fun openActionsDialog( feed: FeedItemUI, - prevFeed: FeedSavedSearch? = null, - nextFeed: FeedSavedSearch? = null, + canMoveUp: Boolean, + canMoveDown: Boolean, ) { screenModelScope.launchIO { mutableState.update { state -> state.copy( - dialog = Dialog.FeedActions(feed, prevFeed, nextFeed), + dialog = Dialog.FeedActions( + feedItem = feed, + canMoveUp = canMoveUp, + canMoveDown = canMoveDown, + ), ) } } @@ -176,7 +179,7 @@ open class FeedScreenModel( return countFeedSavedSearchGlobal.await() > MaxFeedItems } - fun getEnabledSources(): ImmutableList { + private fun getEnabledSources(): ImmutableList { val languages = sourcePreferences.enabledLanguages().get() val pinnedSources = sourcePreferences.pinnedSources().get() val disabledSources = sourcePreferences.disabledSources().get() @@ -190,7 +193,7 @@ open class FeedScreenModel( return list.sortedBy { it.id.toString() !in pinnedSources }.toImmutableList() } - suspend fun getSourceSavedSearches(sourceId: Long): ImmutableList { + private suspend fun getSourceSavedSearches(sourceId: Long): ImmutableList { return getSavedSearchBySourceId.await(sourceId).toImmutableList() } @@ -215,15 +218,30 @@ open class FeedScreenModel( } // KMK --> - fun swapFeedOrder(feed1: FeedSavedSearch, feed2: FeedSavedSearch) { - screenModelScope.launchNonCancellable { - swapFeedOrder.swapOrder(feed1, feed2) + fun moveUp(feed: FeedSavedSearch) { + screenModelScope.launch { + reorderFeed.moveUp(feed) } } - fun moveToBottom(feed: FeedSavedSearch) { - screenModelScope.launchNonCancellable { - swapFeedOrder.moveToBottom(feed) + fun moveDown(feed: FeedSavedSearch) { + screenModelScope.launch { + reorderFeed.moveDown(feed) + } + } + + fun sortAlphabetically() { + screenModelScope.launch { + reorderFeed.sortAlphabetically( + state.value.items + ?.sortedBy { feed -> feed.title } + ?.mapIndexed { index, feed -> + FeedSavedSearchUpdate( + id = feed.feed.id, + feedOrder = index.toLong(), + ) + }, + ) } } // KMK <-- @@ -242,7 +260,7 @@ open class FeedScreenModel( feed: FeedSavedSearch, savedSearch: SavedSearch?, source: CatalogueSource?, - results: List?, + @Suppress("SameParameterValue") results: List?, ): FeedItemUI { return FeedItemUI( feed, @@ -340,6 +358,16 @@ open class FeedScreenModel( coroutineDispatcher.close() } + // KMK --> + fun showDialog(dialog: Dialog) { + if (!state.value.isLoading) { + mutableState.update { + it.copy(dialog = dialog) + } + } + } + // KMK <-- + fun dismissDialog() { mutableState.update { it.copy(dialog = null) } } @@ -352,9 +380,11 @@ open class FeedScreenModel( // KMK --> data class FeedActions( val feedItem: FeedItemUI, - val prevFeed: FeedSavedSearch?, - val nextFeed: FeedSavedSearch?, + val canMoveUp: Boolean, + val canMoveDown: Boolean, ) : Dialog() + + data object SortAlphabetically : Dialog() // KMK <-- } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt index b53d192f8..7a2a1fd4e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt @@ -1,22 +1,30 @@ package eu.kanade.tachiyomi.ui.browse.feed import androidx.activity.compose.BackHandler +import androidx.compose.animation.Crossfade import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add +import androidx.compose.material.icons.outlined.SortByAlpha +import androidx.compose.material.icons.outlined.SwapVert +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.platform.LocalHapticFeedback import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.presentation.browse.FeedActionsDialog import eu.kanade.presentation.browse.FeedAddDialog import eu.kanade.presentation.browse.FeedAddSearchDialog -import eu.kanade.presentation.browse.FeedDeleteConfirmDialog +import eu.kanade.presentation.browse.FeedOrderScreen import eu.kanade.presentation.browse.FeedScreen +import eu.kanade.presentation.browse.components.FeedActionsDialog +import eu.kanade.presentation.browse.components.FeedSortAlphabeticallyDialog +import eu.kanade.presentation.browse.components.SourceFeedDeleteDialog import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.TabContent import eu.kanade.tachiyomi.ui.browse.AddDuplicateMangaDialog @@ -50,11 +58,15 @@ fun feedTab( // KMK --> val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() + val showingFeedOrderScreen = rememberSaveable { mutableStateOf(false) } val haptic = LocalHapticFeedback.current - BackHandler(enabled = bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.backHandler() + BackHandler(enabled = bulkFavoriteState.selectionMode || showingFeedOrderScreen.value) { + when { + bulkFavoriteState.selectionMode -> bulkFavoriteScreenModel.backHandler() + showingFeedOrderScreen.value -> showingFeedOrderScreen.value = false + } } LaunchedEffect(bulkFavoriteState.selectionMode) { @@ -78,84 +90,125 @@ fun feedTab( return TabContent( titleRes = SYMR.strings.feed, - actions = persistentListOf( - AppBar.Action( - title = stringResource(MR.strings.action_add), - icon = Icons.Outlined.Add, - onClick = { - screenModel.openAddDialog() - }, - ), - // KMK --> - bulkSelectionButton(bulkFavoriteScreenModel::toggleSelectionMode), + actions = + // KMK --> + if (showingFeedOrderScreen.value) { + persistentListOf( + AppBar.Action( + title = stringResource(KMR.strings.action_sort_feed), + icon = Icons.Outlined.SwapVert, + iconTint = MaterialTheme.colorScheme.primary, + onClick = { showingFeedOrderScreen.value = false }, + ), + AppBar.Action( + title = stringResource(MR.strings.action_sort), + icon = Icons.Outlined.SortByAlpha, + onClick = { screenModel.showDialog(FeedScreenModel.Dialog.SortAlphabetically) }, + ), + ) + } else { // KMK <-- - ), + persistentListOf( + AppBar.Action( + title = stringResource(MR.strings.action_add), + icon = Icons.Outlined.Add, + onClick = { + screenModel.openAddDialog() + }, + ), + // KMK --> + AppBar.Action( + title = stringResource(KMR.strings.action_sort_feed), + icon = Icons.Outlined.SwapVert, + onClick = { showingFeedOrderScreen.value = true }, + ), + bulkSelectionButton(bulkFavoriteScreenModel::toggleSelectionMode), + // KMK <-- + ) + }, content = { contentPadding, snackbarHostState -> - FeedScreen( - state = state, - contentPadding = contentPadding, - onClickSavedSearch = { savedSearch, source -> - screenModel.sourcePreferences.lastUsedSource().set(savedSearch.source) - navigator.push( - BrowseSourceScreen( - source.id, - listingQuery = null, - savedSearch = savedSearch.id, - ), + // KMK --> + Crossfade( + targetState = showingFeedOrderScreen.value, + label = "feed_order_crossfade", + ) { showingFeedOrderScreen -> + if (showingFeedOrderScreen) { + FeedOrderScreen( + state = state, + onClickDelete = screenModel::openDeleteDialog, + onClickMoveUp = screenModel::moveUp, + onClickMoveDown = screenModel::moveDown, ) - }, - onClickSource = { source -> - screenModel.sourcePreferences.lastUsedSource().set(source.id) - navigator.push( - BrowseSourceScreen( - source.id, + } else { + // KMK <-- + FeedScreen( + state = state, + contentPadding = contentPadding, + onClickSavedSearch = { savedSearch, source -> + screenModel.sourcePreferences.lastUsedSource().set(savedSearch.source) + navigator.push( + BrowseSourceScreen( + source.id, + listingQuery = null, + savedSearch = savedSearch.id, + ), + ) + }, + onClickSource = { source -> + screenModel.sourcePreferences.lastUsedSource().set(source.id) + navigator.push( + BrowseSourceScreen( + source.id, + // KMK --> + listingQuery = if (!source.supportsLatest) { + GetRemoteManga.QUERY_POPULAR + } else { + // KMK <-- + GetRemoteManga.QUERY_LATEST + }, + ), + ) + }, + // KMK --> + onLongClickFeed = screenModel::openActionsDialog, + // KMK <-- + onClickManga = { manga -> // KMK --> - listingQuery = if (!source.supportsLatest) { - GetRemoteManga.QUERY_POPULAR + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) } else { // KMK <-- - GetRemoteManga.QUERY_LATEST - }, - ), - ) - }, - // KMK --> - onLongClickFeed = screenModel::openActionsDialog, - // KMK <-- - onClickManga = { manga -> - // KMK --> - if (bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelection(manga) - } else { + navigator.push(MangaScreen(manga.id, true)) + } + }, + // KMK --> + onLongClickManga = { manga -> + if (!bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.addRemoveManga(manga, haptic) + } else { + navigator.push(MangaScreen(manga.id, true)) + } + }, + selection = bulkFavoriteState.selection, // KMK <-- - navigator.push(MangaScreen(manga.id, true)) - } - }, - // KMK --> - onLongClickManga = { manga -> - if (!bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.addRemoveManga(manga, haptic) - } else { - navigator.push(MangaScreen(manga.id, true)) - } - }, - selection = bulkFavoriteState.selection, - // KMK <-- - onRefresh = screenModel::init, - getMangaState = { manga -> screenModel.getManga(initialManga = manga) }, - ) + onRefresh = screenModel::init, + getMangaState = { manga -> screenModel.getManga(initialManga = manga) }, + ) + } + } state.dialog?.let { dialog -> + val onDismissRequest = screenModel::dismissDialog when (dialog) { is FeedScreenModel.Dialog.AddFeed -> { FeedAddDialog( sources = dialog.options, - onDismiss = screenModel::dismissDialog, + onDismiss = onDismissRequest, onClickAdd = { if (it != null) { screenModel.openAddSearchDialog(it) } - screenModel.dismissDialog() + onDismissRequest() }, ) } @@ -163,46 +216,39 @@ fun feedTab( FeedAddSearchDialog( source = dialog.source, savedSearches = dialog.options, - onDismiss = screenModel::dismissDialog, + onDismiss = onDismissRequest, onClickAdd = { source, savedSearch -> screenModel.createFeed(source, savedSearch) - screenModel.dismissDialog() + onDismissRequest() }, ) } is FeedScreenModel.Dialog.DeleteFeed -> { - FeedDeleteConfirmDialog( - feed = dialog.feed, - onDismiss = screenModel::dismissDialog, - onClickDeleteConfirm = { - screenModel.deleteFeed(it) - screenModel.dismissDialog() + SourceFeedDeleteDialog( + onDismissRequest = onDismissRequest, + deleteFeed = { + screenModel.deleteFeed(dialog.feed) + onDismissRequest() }, ) } // KMK --> is FeedScreenModel.Dialog.FeedActions -> { FeedActionsDialog( - feedItem = dialog.feedItem, - hasPrevFeed = dialog.prevFeed != null, - hasNextFeed = dialog.nextFeed != null, - onDismiss = screenModel::dismissDialog, - onClickDelete = { - screenModel.openDeleteDialog(dialog.feedItem.feed) - screenModel.dismissDialog() - }, - onMoveUp = { - dialog.prevFeed?.let { screenModel.swapFeedOrder(dialog.feedItem.feed, it) } - screenModel.dismissDialog() - }, - onMoveDown = { - dialog.nextFeed?.let { screenModel.swapFeedOrder(dialog.feedItem.feed, it) } - screenModel.dismissDialog() - }, - onMoveBottom = { - dialog.nextFeed?.let { screenModel.moveToBottom(dialog.feedItem.feed) } - screenModel.dismissDialog() - }, + feed = dialog.feedItem.feed, + title = dialog.feedItem.title, + canMoveUp = dialog.canMoveUp, + canMoveDown = dialog.canMoveDown, + onDismissRequest = onDismissRequest, + onClickDelete = { screenModel.openDeleteDialog(it) }, + onMoveUp = { screenModel.moveUp(it) }, + onMoveDown = { screenModel.moveDown(it) }, + ) + } + is FeedScreenModel.Dialog.SortAlphabetically -> { + FeedSortAlphabeticallyDialog( + onDismissRequest = onDismissRequest, + onSort = { screenModel.sortAlphabetically() }, ) } // KMK <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt index 878990f2e..6e235b788 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt @@ -1,21 +1,30 @@ package eu.kanade.tachiyomi.ui.browse.source.feed import androidx.activity.compose.BackHandler +import androidx.compose.animation.Crossfade import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.domain.source.model.installedExtension import eu.kanade.presentation.browse.MissingSourceScreen +import eu.kanade.presentation.browse.SourceFeedOrderScreen import eu.kanade.presentation.browse.SourceFeedScreen +import eu.kanade.presentation.browse.SourceFeedUI +import eu.kanade.presentation.browse.components.FeedActionsDialog +import eu.kanade.presentation.browse.components.FeedSortAlphabeticallyDialog import eu.kanade.presentation.browse.components.SourceFeedAddDialog import eu.kanade.presentation.browse.components.SourceFeedDeleteDialog import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.isLocalOrStub import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.browse.AddDuplicateMangaDialog import eu.kanade.tachiyomi.ui.browse.AllowDuplicateDialog @@ -23,12 +32,14 @@ import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel import eu.kanade.tachiyomi.ui.browse.ChangeMangaCategoryDialog import eu.kanade.tachiyomi.ui.browse.ChangeMangasCategoryDialog import eu.kanade.tachiyomi.ui.browse.RemoveMangaDialog +import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreen import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterDialog import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.webview.WebViewScreen import eu.kanade.tachiyomi.util.system.toast import exh.md.follows.MangaDexFollowsScreen +import exh.source.isEhBasedSource import exh.ui.ifSourcesLoaded import exh.util.nullIfBlank import tachiyomi.domain.manga.model.Manga @@ -38,6 +49,7 @@ import tachiyomi.domain.source.model.StubSource import tachiyomi.i18n.kmk.KMR import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.LoadingScreen +import tachiyomi.domain.source.model.Source as ModelSource class SourceFeedScreen(val sourceId: Long) : Screen() { @@ -66,56 +78,107 @@ class SourceFeedScreen(val sourceId: Long) : Screen() { val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() + val showingFeedOrderScreen = rememberSaveable { mutableStateOf(false) } val haptic = LocalHapticFeedback.current - // KMK <-- - SourceFeedScreen( - name = screenModel.source.name, - isLoading = state.isLoading, - items = state.items, - hasFilters = state.filters.isNotEmpty(), - onFabClick = screenModel::openFilterSheet, - onClickBrowse = { onBrowseClick(navigator, screenModel.source) }, - onClickLatest = { onLatestClick(navigator, screenModel.source) }, - onClickSavedSearch = { onSavedSearchClick(navigator, screenModel.source, it) }, - onClickDelete = screenModel::openDeleteFeed, - onClickManga = { - // KMK --> - if (bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelection(it) - } else { - // KMK <-- - onMangaClick(navigator, it) - } - }, - onClickSearch = { onSearchClick(navigator, screenModel.source, it) }, - searchQuery = state.searchQuery, - onSearchQueryChange = screenModel::search, - getMangaState = { screenModel.getManga(initialManga = it) }, - // KMK --> - navigateUp = { navigator.pop() }, - onWebViewClick = { - val source = screenModel.source as? HttpSource ?: return@SourceFeedScreen - navigator.push( - WebViewScreen( - url = source.baseUrl, - initialTitle = source.name, - sourceId = source.id, - ), + BackHandler(enabled = bulkFavoriteState.selectionMode || showingFeedOrderScreen.value) { + when { + bulkFavoriteState.selectionMode -> bulkFavoriteScreenModel.backHandler() + showingFeedOrderScreen.value -> showingFeedOrderScreen.value = false + } + } + Crossfade( + targetState = showingFeedOrderScreen.value, + label = "feed_order_crossfade", + ) { targetState -> + if (targetState) { + SourceFeedOrderScreen( + state = state, + onClickDelete = screenModel::openDeleteFeed, + onClickMoveUp = screenModel::moveUp, + onClickMoveDown = screenModel::moveDown, + onClickSortAlphabetically = { + screenModel.showDialog(SourceFeedScreenModel.Dialog.SortAlphabetically) + }, + navigateUp = { showingFeedOrderScreen.value = false }, ) - }, - sourceId = screenModel.source.id, - onLongClickManga = { manga -> - if (!bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.addRemoveManga(manga, haptic) - } else { - navigator.push(MangaScreen(manga.id, true)) - } - }, - bulkFavoriteScreenModel = bulkFavoriteScreenModel, - // KMK <-- - ) + } else { + // KMK <-- + SourceFeedScreen( + name = screenModel.source.name, + isLoading = state.isLoading, + items = state.items, + hasFilters = state.filters.isNotEmpty(), + onFabClick = screenModel::openFilterSheet, + onClickBrowse = { onBrowseClick(navigator, screenModel.source) }, + onClickLatest = { onLatestClick(navigator, screenModel.source) }, + onClickSavedSearch = { onSavedSearchClick(navigator, screenModel.source, it) }, + // KMK --> + // onClickDelete = screenModel::openDeleteFeed, + onLongClickFeed = screenModel::openActionsDialog, + // KMK <-- + onClickManga = { + // KMK --> + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(it) + } else { + // KMK <-- + onMangaClick(navigator, it) + } + }, + onClickSearch = { onSearchClick(navigator, screenModel.source, it) }, + searchQuery = state.searchQuery, + onSearchQueryChange = screenModel::search, + getMangaState = { screenModel.getManga(initialManga = it) }, + // KMK --> + navigateUp = { navigator.pop() }, + onWebViewClick = { + val source = screenModel.source as HttpSource + navigator.push( + WebViewScreen( + url = source.baseUrl, + initialTitle = source.name, + sourceId = source.id, + ), + ) + }.takeIf { screenModel.source is HttpSource }, + onSourceSettingClick = { + val dummy = ModelSource( + sourceId, + "", + "", + supportsLatest = false, + isStub = false, + ) + dummy.installedExtension?.let { + navigator.push(ExtensionDetailsScreen(it.pkgName)) + } + }.takeIf { + !screenModel.source.isLocalOrStub() && + !screenModel.source.isEhBasedSource() && + screenModel.state.value.items + .filterIsInstance() + .isNotEmpty() + }, + onSortFeedClick = { showingFeedOrderScreen.value = true } + .takeIf { + screenModel.state.value.items + .filterIsInstance() + .isNotEmpty() + }, + onLongClickManga = { manga -> + if (!bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.addRemoveManga(manga, haptic) + } else { + navigator.push(MangaScreen(manga.id, true)) + } + }, + bulkFavoriteScreenModel = bulkFavoriteScreenModel, + // KMK <-- + ) + } + } val onDismissRequest = screenModel::dismissDialog when (val dialog = state.dialog) { @@ -138,7 +201,27 @@ class SourceFeedScreen(val sourceId: Long) : Screen() { }, ) } - SourceFeedScreenModel.Dialog.Filter -> { + // KMK --> + is SourceFeedScreenModel.Dialog.FeedActions -> { + FeedActionsDialog( + feed = dialog.feedItem.feed, + title = dialog.feedItem.title, + canMoveUp = dialog.canMoveUp, + canMoveDown = dialog.canMoveDown, + onDismissRequest = screenModel::dismissDialog, + onClickDelete = { screenModel.openDeleteFeed(it) }, + onMoveUp = { screenModel.moveUp(it) }, + onMoveDown = { screenModel.moveDown(it) }, + ) + } + is SourceFeedScreenModel.Dialog.SortAlphabetically -> { + FeedSortAlphabeticallyDialog( + onDismissRequest = screenModel::dismissDialog, + onSort = { screenModel.sortAlphabetically() }, + ) + } + // KMK <-- + is SourceFeedScreenModel.Dialog.Filter -> { SourceFilterDialog( onDismissRequest = onDismissRequest, filters = state.filters, @@ -228,19 +311,13 @@ class SourceFeedScreen(val sourceId: Long) : Screen() { else -> {} } // KMK <-- - - BackHandler(bulkFavoriteState.selectionMode) { - // KMK --> - bulkFavoriteScreenModel.backHandler() - // KMK <-- - } } private fun onMangaClick(navigator: Navigator, manga: Manga) { navigator.push(MangaScreen(manga.id, true)) } - fun onBrowseClick(navigator: Navigator, sourceId: Long, search: String? = null, savedSearch: Long? = null, filters: String? = null) { + private fun onBrowseClick(navigator: Navigator, sourceId: Long, search: String? = null, savedSearch: Long? = null, filters: String? = null) { // KMK --> // navigator.replace(BrowseSourceScreen(sourceId, search, savedSearch = savedSearch, filtersJson = filters)) navigator.push(BrowseSourceScreen(sourceId, search, savedSearch = savedSearch, filtersJson = filters)) @@ -254,7 +331,7 @@ class SourceFeedScreen(val sourceId: Long) : Screen() { // KMK <-- } - fun onBrowseClick(navigator: Navigator, source: Source) { + private fun onBrowseClick(navigator: Navigator, source: Source) { // KMK --> // navigator.replace(BrowseSourceScreen(source.id, GetRemoteManga.QUERY_POPULAR)) navigator.push(BrowseSourceScreen(source.id, GetRemoteManga.QUERY_POPULAR)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt index 4d493bb0c..34a41b811 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt @@ -9,7 +9,6 @@ import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope import dev.icerock.moko.resources.StringResource import eu.kanade.core.preference.asState -import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.toDomainManga import eu.kanade.domain.source.interactor.GetExhSavedSearch import eu.kanade.domain.ui.UiPreferences @@ -47,8 +46,10 @@ import tachiyomi.domain.source.interactor.DeleteFeedSavedSearchById import tachiyomi.domain.source.interactor.GetFeedSavedSearchBySourceId import tachiyomi.domain.source.interactor.GetSavedSearchBySourceIdFeed import tachiyomi.domain.source.interactor.InsertFeedSavedSearch +import tachiyomi.domain.source.interactor.ReorderFeed import tachiyomi.domain.source.model.EXHSavedSearch import tachiyomi.domain.source.model.FeedSavedSearch +import tachiyomi.domain.source.model.FeedSavedSearchUpdate import tachiyomi.domain.source.model.SavedSearch import tachiyomi.domain.source.service.SourceManager import tachiyomi.i18n.kmk.KMR @@ -65,13 +66,15 @@ open class SourceFeedScreenModel( private val sourceManager: SourceManager = Injekt.get(), private val getManga: GetManga = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), - private val updateManga: UpdateManga = Injekt.get(), private val getFeedSavedSearchBySourceId: GetFeedSavedSearchBySourceId = Injekt.get(), private val getSavedSearchBySourceIdFeed: GetSavedSearchBySourceIdFeed = Injekt.get(), private val countFeedSavedSearchBySourceId: CountFeedSavedSearchBySourceId = Injekt.get(), private val insertFeedSavedSearch: InsertFeedSavedSearch = Injekt.get(), private val deleteFeedSavedSearchById: DeleteFeedSavedSearchById = Injekt.get(), private val getExhSavedSearch: GetExhSavedSearch = Injekt.get(), + // KMK --> + private val reorderFeed: ReorderFeed = Injekt.get(), + // KMK <-- ) : StateScreenModel(SourceFeedState()) { var source = sourceManager.getOrStub(sourceId) @@ -142,6 +145,36 @@ open class SourceFeedScreenModel( } } + // KMK --> + fun moveUp(feed: FeedSavedSearch) { + screenModelScope.launch { + reorderFeed.moveUp(feed, false) + } + } + + fun moveDown(feed: FeedSavedSearch) { + screenModelScope.launch { + reorderFeed.moveDown(feed, false) + } + } + + fun sortAlphabetically() { + screenModelScope.launchNonCancellable { + reorderFeed.sortAlphabetically( + state.value.items + .filterIsInstance() + .sortedBy { feed -> feed.title } + .mapIndexed { index, feed -> + FeedSavedSearchUpdate( + id = feed.feed.id, + feedOrder = index.toLong(), + ) + }, + ) + } + } + // KMK <-- + private suspend fun getSourcesToGetFeed(feedSavedSearch: List): ImmutableList { // KMK --> val source = source @@ -324,7 +357,35 @@ open class SourceFeedScreenModel( mutableState.update { it.copy(dialog = Dialog.DeleteFeed(feed)) } } - fun openAddFeed(feedId: Long, name: String) { + // KMK --> + fun openActionsDialog( + feed: SourceFeedUI.SourceSavedSearch, + canMoveUp: Boolean, + canMoveDown: Boolean, + ) { + screenModelScope.launchIO { + mutableState.update { state -> + state.copy( + dialog = Dialog.FeedActions( + feedItem = feed, + canMoveUp = canMoveUp, + canMoveDown = canMoveDown, + ), + ) + } + } + } + + fun showDialog(dialog: Dialog) { + if (!state.value.isLoading) { + mutableState.update { + it.copy(dialog = dialog) + } + } + } + // KMK <-- + + private fun openAddFeed(feedId: Long, name: String) { mutableState.update { it.copy(dialog = Dialog.AddFeed(feedId, name)) } } @@ -336,6 +397,16 @@ open class SourceFeedScreenModel( data object Filter : Dialog() data class DeleteFeed(val feed: FeedSavedSearch) : Dialog() data class AddFeed(val feedId: Long, val name: String) : Dialog() + + // KMK --> + data class FeedActions( + val feedItem: SourceFeedUI.SourceSavedSearch, + val canMoveUp: Boolean, + val canMoveDown: Boolean, + ) : Dialog() + + data object SortAlphabetically : Dialog() + // KMK <-- } override fun onDispose() { diff --git a/data/src/main/java/tachiyomi/data/source/FeedSavedSearchRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/source/FeedSavedSearchRepositoryImpl.kt index 4bc284bda..8a1c7310d 100644 --- a/data/src/main/java/tachiyomi/data/source/FeedSavedSearchRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/source/FeedSavedSearchRepositoryImpl.kt @@ -1,8 +1,10 @@ package tachiyomi.data.source import kotlinx.coroutines.flow.Flow +import tachiyomi.data.Database import tachiyomi.data.DatabaseHandler import tachiyomi.domain.source.model.FeedSavedSearch +import tachiyomi.domain.source.model.FeedSavedSearchUpdate import tachiyomi.domain.source.model.SavedSearch import tachiyomi.domain.source.repository.FeedSavedSearchRepository @@ -86,25 +88,28 @@ class FeedSavedSearchRepositoryImpl( } // KMK --> - override suspend fun swapOrder(feed1: FeedSavedSearch, feed2: FeedSavedSearch) { - return handler.await(true) { - feed_saved_searchQueries.setOrder( - id = feed2.id, - order = feed1.feedOrder, - ) - feed_saved_searchQueries.setOrder( - id = feed1.id, - order = feed2.feedOrder, - ) + override suspend fun updatePartial(update: FeedSavedSearchUpdate) { + handler.await { + updatePartialBlocking(update) } } - override suspend fun moveToBottom(feed: FeedSavedSearch) { - return handler.await(true) { - feed_saved_searchQueries.moveToBottom( - id = feed.id, - ) + override suspend fun updatePartial(updates: List) { + handler.await(inTransaction = true) { + for (update in updates) { + updatePartialBlocking(update) + } } } + + private fun Database.updatePartialBlocking(update: FeedSavedSearchUpdate) { + feed_saved_searchQueries.update( + source = update.source, + saved_search = update.savedSearch, + global = update.global, + feed_order = update.feedOrder, + id = update.id, + ) + } // KMK <-- } diff --git a/data/src/main/sqldelight/tachiyomi/data/feed_saved_search.sq b/data/src/main/sqldelight/tachiyomi/data/feed_saved_search.sq index b493580e1..75fe806c4 100644 --- a/data/src/main/sqldelight/tachiyomi/data/feed_saved_search.sq +++ b/data/src/main/sqldelight/tachiyomi/data/feed_saved_search.sq @@ -75,10 +75,11 @@ SELECT coalesce(f.source, s.source) source, f.global, f.saved_search, s.name, s. FROM feed_saved_search AS f LEFT JOIN saved_search AS s ON f.saved_search = s._id ORDER BY f.feed_order; -setOrder: -UPDATE feed_saved_search SET feed_order = :order WHERE _id = :id; - -moveToBottom: -UPDATE feed_saved_search SET feed_order = (SELECT MAX(feed_order) + 1 FROM feed_saved_search) - WHERE _id = :id; +update: +UPDATE feed_saved_search +SET source = coalesce(:source, source), + saved_search = coalesce(:saved_search, saved_search), + global = coalesce(:global, global), + feed_order = coalesce(:feed_order, feed_order) +WHERE _id = :id; -- KMK <-- \ No newline at end of file diff --git a/domain/src/main/java/tachiyomi/domain/source/interactor/ReorderFeed.kt b/domain/src/main/java/tachiyomi/domain/source/interactor/ReorderFeed.kt new file mode 100644 index 000000000..a7d533bbe --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/source/interactor/ReorderFeed.kt @@ -0,0 +1,85 @@ +package tachiyomi.domain.source.interactor + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import logcat.LogPriority +import tachiyomi.core.common.util.lang.withNonCancellableContext +import tachiyomi.core.common.util.system.logcat +import tachiyomi.domain.source.model.FeedSavedSearch +import tachiyomi.domain.source.model.FeedSavedSearchUpdate +import tachiyomi.domain.source.repository.FeedSavedSearchRepository +import java.util.Collections + +class ReorderFeed( + private val feedSavedSearchRepository: FeedSavedSearchRepository, +) { + + private val mutex = Mutex() + + suspend fun moveUp(feed: FeedSavedSearch, global: Boolean = true): Result = awaitGlobal(feed, MoveTo.UP, global) + + suspend fun moveDown(feed: FeedSavedSearch, global: Boolean = true): Result = awaitGlobal(feed, MoveTo.DOWN, global) + + private suspend fun awaitGlobal(feed: FeedSavedSearch, moveTo: MoveTo, global: Boolean = true) = withNonCancellableContext { + mutex.withLock { + val feeds = if (global) { + feedSavedSearchRepository.getGlobal() + .toMutableList() + } else { + feedSavedSearchRepository.getBySourceId(feed.source) + .toMutableList() + } + + val currentIndex = feeds.indexOfFirst { it.id == feed.id } + if (currentIndex == -1) { + return@withNonCancellableContext Result.Unchanged + } + + val newPosition = when (moveTo) { + MoveTo.UP -> currentIndex - 1 + MoveTo.DOWN -> currentIndex + 1 + }.toInt() + + try { + Collections.swap(feeds, currentIndex, newPosition) + + val updates = feeds.mapIndexed { index, feed -> + FeedSavedSearchUpdate( + id = feed.id, + feedOrder = index.toLong(), + ) + } + + feedSavedSearchRepository.updatePartial(updates) + Result.Success + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + Result.InternalError(e) + } + } + } + + suspend fun sortAlphabetically(updates: List?) = withNonCancellableContext { + if (updates == null) return@withNonCancellableContext + mutex.withLock { + try { + feedSavedSearchRepository.updatePartial(updates) + Result.Success + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + Result.InternalError(e) + } + } + } + + sealed interface Result { + data object Success : Result + data object Unchanged : Result + data class InternalError(val error: Throwable) : Result + } + + private enum class MoveTo { + UP, + DOWN, + } +} diff --git a/domain/src/main/java/tachiyomi/domain/source/interactor/SwapFeedOrder.kt b/domain/src/main/java/tachiyomi/domain/source/interactor/SwapFeedOrder.kt deleted file mode 100644 index 42ca866dd..000000000 --- a/domain/src/main/java/tachiyomi/domain/source/interactor/SwapFeedOrder.kt +++ /dev/null @@ -1,17 +0,0 @@ -package tachiyomi.domain.source.interactor - -import tachiyomi.domain.source.model.FeedSavedSearch -import tachiyomi.domain.source.repository.FeedSavedSearchRepository - -class SwapFeedOrder( - private val feedSavedSearchRepository: FeedSavedSearchRepository, -) { - - suspend fun swapOrder(feed1: FeedSavedSearch, feed2: FeedSavedSearch) { - return feedSavedSearchRepository.swapOrder(feed1, feed2) - } - - suspend fun moveToBottom(feed: FeedSavedSearch) { - return feedSavedSearchRepository.moveToBottom(feed) - } -} diff --git a/domain/src/main/java/tachiyomi/domain/source/model/FeedSavedSearchUpdate.kt b/domain/src/main/java/tachiyomi/domain/source/model/FeedSavedSearchUpdate.kt new file mode 100644 index 000000000..95bafd4f3 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/source/model/FeedSavedSearchUpdate.kt @@ -0,0 +1,16 @@ +package tachiyomi.domain.source.model + +data class FeedSavedSearchUpdate( + // Tag identifier, unique + val id: Long, + + // Source for the saved search + val source: Long? = null, + + // If null then get latest/popular, if set get the saved search + val savedSearch: Long? = null, + + // If the feed is a global (FeedScreen) or source specific feed (SourceFeedScreen) + val global: Boolean? = null, + val feedOrder: Long? = null, +) diff --git a/domain/src/main/java/tachiyomi/domain/source/repository/FeedSavedSearchRepository.kt b/domain/src/main/java/tachiyomi/domain/source/repository/FeedSavedSearchRepository.kt index 915e2c8db..18058401d 100644 --- a/domain/src/main/java/tachiyomi/domain/source/repository/FeedSavedSearchRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/source/repository/FeedSavedSearchRepository.kt @@ -2,6 +2,7 @@ package tachiyomi.domain.source.repository import kotlinx.coroutines.flow.Flow import tachiyomi.domain.source.model.FeedSavedSearch +import tachiyomi.domain.source.model.FeedSavedSearchUpdate import tachiyomi.domain.source.model.SavedSearch interface FeedSavedSearchRepository { @@ -29,8 +30,8 @@ interface FeedSavedSearchRepository { suspend fun insertAll(feedSavedSearch: List) // KMK --> - suspend fun swapOrder(feed1: FeedSavedSearch, feed2: FeedSavedSearch) + suspend fun updatePartial(update: FeedSavedSearchUpdate) - suspend fun moveToBottom(feed: FeedSavedSearch) + suspend fun updatePartial(updates: List) // KMK <-- } diff --git a/i18n-kmk/src/commonMain/moko-resources/base/strings.xml b/i18n-kmk/src/commonMain/moko-resources/base/strings.xml index 6c4c7d267..adb09ad96 100644 --- a/i18n-kmk/src/commonMain/moko-resources/base/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/base/strings.xml @@ -9,7 +9,6 @@ FAQ and Guides Move up Move down - Move bottom Confirm Color Remove merged entries? Hide @@ -118,6 +117,8 @@ Too many sources in your feed, cannot add more than limited (20) + Sort feeds + Would you like to sort the feeds alphabetically? Current: %1$s