diff --git a/CHANGELOG.md b/CHANGELOG.md index 846498ae4..43fd38873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co - Support for private tracking with AniList and Bangumi ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#1736](https://github.com/mihonapp/mihon/pull/1736)) - Add private tracking support for Kitsu ([@MajorTanya](https://github.com/MajorTanya)) ([#1774](https://github.com/mihonapp/mihon/pull/1774)) - Add option to export minimal library information to a CSV file ([@Animeboynz](https://github.com/Animeboynz), [@AntsyLich](https://github.com/AntsyLich)) ([#1161](https://github.com/mihonapp/mihon/pull/1161)) +- Add back support for drag-and-drop category reordering ([@cuong-tran](https://github.com/cuong-tran)) ([#1427](https://github.com/mihonapp/mihon/pull/1427)) ### Changed - Apply "Downloaded only" filter to all entries regardless of favourite status ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#1603](https://github.com/mihonapp/mihon/pull/1603)) diff --git a/app/src/main/java/eu/kanade/presentation/browse/FeedOrderScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/FeedOrderScreen.kt index eeadabcf4..6bdb645da 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/FeedOrderScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/FeedOrderScreen.kt @@ -2,16 +2,14 @@ package eu.kanade.presentation.browse import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import eu.kanade.presentation.browse.components.FeedOrderListItem import eu.kanade.tachiyomi.ui.browse.feed.FeedScreenState @@ -29,7 +27,7 @@ import tachiyomi.presentation.core.util.plus fun FeedOrderScreen( state: FeedScreenState, onClickDelete: (FeedSavedSearch) -> Unit, - changeOrder: (FeedSavedSearch, Int) -> Unit, + onChangeOrder: (FeedSavedSearch, Int) -> Unit, ) { when { state.isLoading -> LoadingScreen() @@ -41,31 +39,32 @@ fun FeedOrderScreen( val lazyListState = rememberLazyListState() val feeds = state.items - var reorderableList by remember { mutableStateOf(feeds) } - val reorderableLazyColumnState = rememberReorderableLazyListState(lazyListState) { from, to -> - reorderableList = reorderableList.toMutableList().apply { - changeOrder(reorderableList[from.index].feed, to.index - from.index) - add(to.index, removeAt(from.index)) - } + val feedsState = remember { feeds.toMutableList() } + val reorderableState = rememberReorderableLazyListState(lazyListState) { from, to -> + val item = feedsState.removeAt(from.index) + feedsState.add(to.index, item) + onChangeOrder(item.feed, to.index) } LaunchedEffect(feeds) { - if (!reorderableLazyColumnState.isAnyItemDragging) { - reorderableList = feeds + if (!reorderableState.isAnyItemDragging) { + feedsState.clear() + feedsState.addAll(feeds) } } LazyColumn( + modifier = Modifier.fillMaxSize(), state = lazyListState, contentPadding = topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium), verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), ) { items( - items = reorderableList, + items = feedsState, key = { it.feed.key }, ) { feed -> - ReorderableItem(reorderableLazyColumnState, feed.feed.key) { + ReorderableItem(reorderableState, feed.feed.key) { FeedOrderListItem( modifier = Modifier.animateItem(), title = feed.title, @@ -78,4 +77,4 @@ fun FeedOrderScreen( } } -internal val FeedSavedSearch.key get() = "feed-$id" +internal val FeedSavedSearch.key inline get() = "feed-$id" diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourceFeedOrderScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourceFeedOrderScreen.kt index 58a33e21d..d2b1cd908 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/SourceFeedOrderScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/SourceFeedOrderScreen.kt @@ -2,6 +2,7 @@ package eu.kanade.presentation.browse import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState @@ -10,10 +11,7 @@ import androidx.compose.material.icons.outlined.SortByAlpha import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import eu.kanade.presentation.browse.components.FeedOrderListItem import eu.kanade.presentation.components.AppBar @@ -37,7 +35,7 @@ import tachiyomi.presentation.core.util.plus fun SourceFeedOrderScreen( state: SourceFeedState, onClickDelete: (FeedSavedSearch) -> Unit, - changeOrder: (FeedSavedSearch, Int) -> Unit, + onChangeOrder: (FeedSavedSearch, Int) -> Unit, onClickSortAlphabetically: () -> Unit, navigateUp: (() -> Unit)? = null, ) { @@ -75,31 +73,33 @@ fun SourceFeedOrderScreen( val feeds = state.items .filterIsInstance() - var reorderableList by remember { mutableStateOf(feeds) } - val reorderableLazyColumnState = rememberReorderableLazyListState(lazyListState) { from, to -> - reorderableList = reorderableList.toMutableList().apply { - changeOrder(reorderableList[from.index].feed, to.index - from.index) - add(to.index, removeAt(from.index)) - } + val feedsState = remember { feeds.toMutableList() } + val reorderableState = rememberReorderableLazyListState(lazyListState, paddingValues) { from, to -> + val item = feedsState.removeAt(from.index) + feedsState.add(to.index, item) + onChangeOrder(item.feed, to.index) } LaunchedEffect(feeds) { - if (!reorderableLazyColumnState.isAnyItemDragging) { - reorderableList = feeds + if (!reorderableState.isAnyItemDragging) { + feedsState.clear() + feedsState.addAll(feeds) } } LazyColumn( + modifier = Modifier.fillMaxSize(), state = lazyListState, - contentPadding = paddingValues + topSmallPaddingValues + + contentPadding = paddingValues + + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium), verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), ) { items( - items = reorderableList, + items = feedsState, key = { it.feed.key }, ) { feed -> - ReorderableItem(reorderableLazyColumnState, feed.feed.key) { + ReorderableItem(reorderableState, feed.feed.key) { FeedOrderListItem( modifier = Modifier.animateItem(), title = feed.title, 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 index d4e46b0a8..e57f62a9f 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/FeedOrderListItem.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/FeedOrderListItem.kt @@ -25,12 +25,11 @@ fun ReorderableCollectionItemScope.FeedOrderListItem( onDelete: () -> Unit, modifier: Modifier = Modifier, ) { - ElevatedCard( - modifier = modifier, - ) { + ElevatedCard(modifier = modifier) { Row( modifier = Modifier .fillMaxWidth() + .padding(vertical = MaterialTheme.padding.small) .padding( start = MaterialTheme.padding.small, end = MaterialTheme.padding.medium, @@ -46,11 +45,13 @@ fun ReorderableCollectionItemScope.FeedOrderListItem( ) Text( text = title, - modifier = Modifier - .weight(1f), + modifier = Modifier.weight(1f), ) IconButton(onClick = onDelete) { - Icon(imageVector = Icons.Outlined.Delete, contentDescription = stringResource(MR.strings.action_delete)) + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = stringResource(MR.strings.action_delete), + ) } } } diff --git a/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt b/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt index 281a01d93..9648cd753 100644 --- a/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt @@ -2,6 +2,7 @@ package eu.kanade.presentation.category import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState @@ -12,10 +13,8 @@ import androidx.compose.material.icons.outlined.SortByAlpha import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.Modifier import eu.kanade.presentation.category.components.CategoryFloatingActionButton import eu.kanade.presentation.category.components.CategoryListItem @@ -42,7 +41,7 @@ fun CategoryScreen( onClickSortAlphabetically: () -> Unit, onClickRename: (Category) -> Unit, onClickDelete: (Category) -> Unit, - changeOrder: (Category, Int) -> Unit, + onChangeOrder: (Category, Int) -> Unit, // KMK --> onClickHide: (Category) -> Unit, // KMK <-- @@ -86,11 +85,10 @@ fun CategoryScreen( CategoryContent( categories = state.categories, lazyListState = lazyListState, - paddingValues = paddingValues + topSmallPaddingValues + - PaddingValues(horizontal = MaterialTheme.padding.medium), + paddingValues = paddingValues, onClickRename = onClickRename, onClickDelete = onClickDelete, - changeOrder = changeOrder, + onChangeOrder = onChangeOrder, // KMK --> onClickHide = onClickHide, // KMK <-- @@ -105,35 +103,38 @@ private fun CategoryContent( paddingValues: PaddingValues, onClickRename: (Category) -> Unit, onClickDelete: (Category) -> Unit, - changeOrder: (Category, Int) -> Unit, + onChangeOrder: (Category, Int) -> Unit, // KMK --> onClickHide: (Category) -> Unit, // KMK <-- ) { - var reorderableList by remember { mutableStateOf(categories.toList()) } - val reorderableLazyColumnState = rememberReorderableLazyListState(lazyListState) { from, to -> - reorderableList = reorderableList.toMutableList().apply { - changeOrder(reorderableList[from.index], to.index - from.index) - add(to.index, removeAt(from.index)) - } + val categoriesState = remember { categories.toMutableStateList() } + val reorderableState = rememberReorderableLazyListState(lazyListState, paddingValues) { from, to -> + val item = categoriesState.removeAt(from.index) + categoriesState.add(to.index, item) + onChangeOrder(item, to.index) } LaunchedEffect(categories) { - if (!reorderableLazyColumnState.isAnyItemDragging) { - reorderableList = categories.toList() + if (!reorderableState.isAnyItemDragging) { + categoriesState.clear() + categoriesState.addAll(categories) } } LazyColumn( + modifier = Modifier.fillMaxSize(), state = lazyListState, - contentPadding = paddingValues, + contentPadding = paddingValues + + topSmallPaddingValues + + PaddingValues(horizontal = MaterialTheme.padding.medium), verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), ) { items( - items = reorderableList, + items = categoriesState, key = { category -> category.key }, ) { category -> - ReorderableItem(reorderableLazyColumnState, category.key) { + ReorderableItem(reorderableState, category.key) { CategoryListItem( modifier = Modifier.animateItem(), category = category, @@ -148,4 +149,4 @@ private fun CategoryContent( } } -private val Category.key get() = "category-$id" +private val Category.key inline get() = "category-$id" diff --git a/app/src/main/java/eu/kanade/presentation/category/components/CategoryListItem.kt b/app/src/main/java/eu/kanade/presentation/category/components/CategoryListItem.kt index b4e08785a..861183116 100644 --- a/app/src/main/java/eu/kanade/presentation/category/components/CategoryListItem.kt +++ b/app/src/main/java/eu/kanade/presentation/category/components/CategoryListItem.kt @@ -37,13 +37,12 @@ fun ReorderableCollectionItemScope.CategoryListItem( // KMK <-- modifier: Modifier = Modifier, ) { - ElevatedCard( - modifier = modifier, - ) { + ElevatedCard(modifier = modifier) { Row( modifier = Modifier .fillMaxWidth() - .clickable { onRename() } + .clickable(onClick = onRename) + .padding(vertical = MaterialTheme.padding.small) .padding( start = MaterialTheme.padding.small, end = MaterialTheme.padding.medium, @@ -63,8 +62,7 @@ fun ReorderableCollectionItemScope.CategoryListItem( color = LocalContentColor.current.let { if (category.hidden) it.copy(alpha = 0.6f) else it }, textDecoration = TextDecoration.LineThrough.takeIf { category.hidden }, // KMK <-- - modifier = Modifier - .weight(1f), + modifier = Modifier.weight(1f), ) IconButton(onClick = onRename) { Icon( @@ -88,7 +86,10 @@ fun ReorderableCollectionItemScope.CategoryListItem( ) // KMK <-- IconButton(onClick = onDelete) { - Icon(imageVector = Icons.Outlined.Delete, contentDescription = stringResource(MR.strings.action_delete)) + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = stringResource(MR.strings.action_delete), + ) } } } 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 736b85192..7484b82f0 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 @@ -218,9 +218,9 @@ open class FeedScreenModel( } // KMK --> - fun changeOrder(feed: FeedSavedSearch, newOrder: Int) { + fun changeOrder(feed: FeedSavedSearch, newIndex: Int) { screenModelScope.launch { - reorderFeed.changeOrder(feed, newOrder) + reorderFeed.changeOrder(feed, newIndex) } } 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 f3611311a..118b75cac 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 @@ -142,7 +142,7 @@ fun feedTab( FeedOrderScreen( state = state, onClickDelete = screenModel::openDeleteDialog, - changeOrder = screenModel::changeOrder, + onChangeOrder = screenModel::changeOrder, ) } else { // 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 600280645..03e3f2b5a 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 @@ -105,7 +105,7 @@ class SourceFeedScreen(val sourceId: Long) : Screen() { SourceFeedOrderScreen( state = state, onClickDelete = screenModel::openDeleteFeed, - changeOrder = screenModel::changeOrder, + onChangeOrder = screenModel::changeOrder, onClickSortAlphabetically = { screenModel.showDialog(SourceFeedScreenModel.Dialog.SortAlphabetically) }, 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 5b6447848..76ed00fd8 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 @@ -33,7 +33,6 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchNonCancellable @@ -156,9 +155,9 @@ open class SourceFeedScreenModel( } // KMK --> - fun changeOrder(feed: FeedSavedSearch, newOrder: Int) { + fun changeOrder(feed: FeedSavedSearch, newIndex: Int) { screenModelScope.launch { - reorderFeed.changeOrder(feed, newOrder, false) + reorderFeed.changeOrder(feed, newIndex, false) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt index 8a9b3a2cd..65e310178 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt @@ -43,7 +43,7 @@ class CategoryScreen : Screen() { onClickSortAlphabetically = { screenModel.showDialog(CategoryDialog.SortAlphabetically) }, onClickRename = { screenModel.showDialog(CategoryDialog.Rename(it)) }, onClickDelete = { screenModel.showDialog(CategoryDialog.Delete(it)) }, - changeOrder = screenModel::changeOrder, + onChangeOrder = screenModel::changeOrder, // KMK --> onClickHide = screenModel::hideCategory, // KMK <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreenModel.kt index a7cb95765..27a7f9ad0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreenModel.kt @@ -89,9 +89,9 @@ class CategoryScreenModel( } } - fun changeOrder(category: Category, newOrder: Int) { + fun changeOrder(category: Category, newIndex: Int) { screenModelScope.launch { - when (reorderCategory.changeOrder(category, newOrder)) { + when (reorderCategory.changeOrder(category, newIndex)) { is ReorderCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError) else -> {} } diff --git a/domain/src/main/java/tachiyomi/domain/category/interactor/ReorderCategory.kt b/domain/src/main/java/tachiyomi/domain/category/interactor/ReorderCategory.kt index b0f299953..4f91a9901 100644 --- a/domain/src/main/java/tachiyomi/domain/category/interactor/ReorderCategory.kt +++ b/domain/src/main/java/tachiyomi/domain/category/interactor/ReorderCategory.kt @@ -15,7 +15,7 @@ class ReorderCategory( private val mutex = Mutex() - suspend fun changeOrder(category: Category, newOrder: Int) = withNonCancellableContext { + suspend fun changeOrder(category: Category, newIndex: Int) = withNonCancellableContext { mutex.withLock { val categories = categoryRepository.getAll() .filterNot(Category::isSystemCategory) @@ -26,10 +26,8 @@ class ReorderCategory( return@withNonCancellableContext Result.Unchanged } - val newPosition = currentIndex + newOrder - try { - categories.add(newPosition, categories.removeAt(currentIndex)) + categories.add(newIndex, categories.removeAt(currentIndex)) val updates = categories.mapIndexed { index, category -> CategoryUpdate( diff --git a/domain/src/main/java/tachiyomi/domain/source/interactor/ReorderFeed.kt b/domain/src/main/java/tachiyomi/domain/source/interactor/ReorderFeed.kt index bd1ae9b32..4129a75ea 100644 --- a/domain/src/main/java/tachiyomi/domain/source/interactor/ReorderFeed.kt +++ b/domain/src/main/java/tachiyomi/domain/source/interactor/ReorderFeed.kt @@ -15,7 +15,7 @@ class ReorderFeed( private val mutex = Mutex() - suspend fun changeOrder(feed: FeedSavedSearch, newOrder: Int, global: Boolean = true) = withNonCancellableContext { + suspend fun changeOrder(feed: FeedSavedSearch, newIndex: Int, global: Boolean = true) = withNonCancellableContext { mutex.withLock { val feeds = if (global) { feedSavedSearchRepository.getGlobal() @@ -30,10 +30,8 @@ class ReorderFeed( return@withNonCancellableContext Result.Unchanged } - val newPosition = currentIndex + newOrder - try { - feeds.add(newPosition, feeds.removeAt(currentIndex)) + feeds.add(newIndex, feeds.removeAt(currentIndex)) val updates = feeds.mapIndexed { index, feed -> FeedSavedSearchUpdate( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2d79f8f93..94754d895 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,10 +64,10 @@ compose-materialmotion = "io.github.fornewid:material-motion-compose-core:2.0.1" compose-webview = "io.github.kevinnzou:compose-webview:0.33.6" compose-grid = "io.woong.compose.grid:grid:1.2.2" compose-stablemarker = "com.github.skydoves:compose-stable-marker:1.0.5" +reorderable = { module = "sh.calvin.reorderable:reorderable", version = "2.4.3" } palette-ktx = "androidx.palette:palette-ktx:1.0.0" materialKolor = "com.materialkolor:material-kolor:2.0.2" haze = "dev.chrisbanes.haze:haze:1.3.1" -reorderable = { module = "sh.calvin.reorderable:reorderable", version = "2.4.3" } swipe = "me.saket.swipe:swipe:1.3.0"