Improve support for drag-and-drop category/feed reordering (mihonapp/mihon#1427)
This commit is contained in:
parent
0a6caab64b
commit
69adc4605e
15 changed files with 80 additions and 82 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<SourceFeedUI.SourceSavedSearch>()
|
||||
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ fun feedTab(
|
|||
FeedOrderScreen(
|
||||
state = state,
|
||||
onClickDelete = screenModel::openDeleteDialog,
|
||||
changeOrder = screenModel::changeOrder,
|
||||
onChangeOrder = screenModel::changeOrder,
|
||||
)
|
||||
} else {
|
||||
// KMK <--
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <--
|
||||
|
|
|
|||
|
|
@ -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 -> {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue