From d420f98d315d6e7acfd14902546210ae01279103 Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Fri, 1 Nov 2024 02:05:01 +0700 Subject: [PATCH 1/6] drag to reorder Category list --- app/build.gradle.kts | 1 + .../presentation/category/CategoryScreen.kt | 60 ++++++++++++------- .../category/components/CategoryListItem.kt | 42 ++++--------- .../tachiyomi/ui/category/CategoryScreen.kt | 3 +- .../ui/category/CategoryScreenModel.kt | 13 +--- .../category/interactor/ReorderCategory.kt | 19 +----- gradle/libs.versions.toml | 1 + 7 files changed, 59 insertions(+), 80 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 20814d2cb..c939ca768 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -260,6 +260,7 @@ dependencies { implementation(libs.swipe) implementation(libs.compose.webview) implementation(libs.compose.grid) + implementation(libs.reorderable) // Logging implementation(libs.logcat) 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 df553a203..eb3259143 100644 --- a/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt @@ -5,12 +5,17 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.items 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.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.category.components.CategoryFloatingActionButton import eu.kanade.presentation.category.components.CategoryListItem @@ -18,6 +23,8 @@ import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBarActions import eu.kanade.tachiyomi.ui.category.CategoryScreenState import kotlinx.collections.immutable.persistentListOf +import sh.calvin.reorderable.ReorderableItem +import sh.calvin.reorderable.rememberReorderableLazyListState import tachiyomi.domain.category.model.Category import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.Scaffold @@ -34,8 +41,7 @@ fun CategoryScreen( onClickSortAlphabetically: () -> Unit, onClickRename: (Category) -> Unit, onClickDelete: (Category) -> Unit, - onClickMoveUp: (Category) -> Unit, - onClickMoveDown: (Category) -> Unit, + moveTo: (Category, Int) -> Unit, navigateUp: () -> Unit, ) { val lazyListState = rememberLazyListState() @@ -81,8 +87,7 @@ fun CategoryScreen( PaddingValues(horizontal = MaterialTheme.padding.medium), onClickRename = onClickRename, onClickDelete = onClickDelete, - onMoveUp = onClickMoveUp, - onMoveDown = onClickMoveDown, + moveTo = moveTo, ) } } @@ -94,28 +99,41 @@ private fun CategoryContent( paddingValues: PaddingValues, onClickRename: (Category) -> Unit, onClickDelete: (Category) -> Unit, - onMoveUp: (Category) -> Unit, - onMoveDown: (Category) -> Unit, + moveTo: (Category, Int) -> Unit, ) { + var reorderableList by remember { mutableStateOf(categories.toList()) } + val reorderableLazyColumnState = rememberReorderableLazyListState(lazyListState) { from, to -> + reorderableList = reorderableList.toMutableList().apply { + moveTo(reorderableList[from.index], to.index - from.index) + add(to.index, removeAt(from.index)) + } + } + + LaunchedEffect(categories) { + if (!reorderableLazyColumnState.isAnyItemDragging) { + reorderableList = categories.toList() + } + } + LazyColumn( state = lazyListState, contentPadding = paddingValues, verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), ) { - itemsIndexed( - items = categories, - key = { _, category -> "category-${category.id}" }, - ) { index, category -> - CategoryListItem( - modifier = Modifier.animateItem(), - category = category, - canMoveUp = index != 0, - canMoveDown = index != categories.lastIndex, - onMoveUp = onMoveUp, - onMoveDown = onMoveDown, - onRename = { onClickRename(category) }, - onDelete = { onClickDelete(category) }, - ) + items( + items = reorderableList, + key = { category -> category.key() }, + ) { category -> + ReorderableItem(reorderableLazyColumnState, category.key()) { + CategoryListItem( + modifier = Modifier.animateItem(), + category = category, + onRename = { onClickRename(category) }, + onDelete = { onClickDelete(category) }, + ) + } } } } + +fun Category.key() = "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 5c387e542..9ab4acc55 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 @@ -2,15 +2,12 @@ package eu.kanade.presentation.category.components import androidx.compose.foundation.clickable 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.automirrored.outlined.Label -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.Edit +import androidx.compose.material.icons.rounded.DragHandle import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -19,18 +16,15 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import sh.calvin.reorderable.ReorderableCollectionItemScope import tachiyomi.domain.category.model.Category import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.i18n.stringResource @Composable -fun CategoryListItem( +fun ReorderableCollectionItemScope.CategoryListItem( category: Category, - canMoveUp: Boolean, - canMoveDown: Boolean, - onMoveUp: (Category) -> Unit, - onMoveDown: (Category) -> Unit, onRename: () -> Unit, onDelete: () -> Unit, modifier: Modifier = Modifier, @@ -42,34 +36,22 @@ fun CategoryListItem( modifier = Modifier .fillMaxWidth() .clickable { onRename() } - .padding( - start = MaterialTheme.padding.medium, - top = MaterialTheme.padding.medium, - end = MaterialTheme.padding.medium, - ), + .padding(MaterialTheme.padding.medium), verticalAlignment = Alignment.CenterVertically, ) { - Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = null) + IconButton( + modifier = Modifier + .draggableHandle(), + onClick = {}, + ) { + Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder") + } Text( text = category.name, modifier = Modifier + .weight(1f) .padding(start = MaterialTheme.padding.medium), ) - } - Row { - IconButton( - onClick = { onMoveUp(category) }, - enabled = canMoveUp, - ) { - Icon(imageVector = Icons.Outlined.ArrowDropUp, contentDescription = null) - } - IconButton( - onClick = { onMoveDown(category) }, - enabled = canMoveDown, - ) { - Icon(imageVector = Icons.Outlined.ArrowDropDown, contentDescription = null) - } - Spacer(modifier = Modifier.weight(1f)) IconButton(onClick = onRename) { Icon( imageVector = Icons.Outlined.Edit, 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 20ebdba0f..abaabfc3c 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,8 +43,7 @@ class CategoryScreen : Screen() { onClickSortAlphabetically = { screenModel.showDialog(CategoryDialog.SortAlphabetically) }, onClickRename = { screenModel.showDialog(CategoryDialog.Rename(it)) }, onClickDelete = { screenModel.showDialog(CategoryDialog.Delete(it)) }, - onClickMoveUp = screenModel::moveUp, - onClickMoveDown = screenModel::moveDown, + moveTo = screenModel::moveTo, navigateUp = navigator::pop, ) 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 2fb78342f..5aed5fddb 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 @@ -74,18 +74,9 @@ class CategoryScreenModel( } } - fun moveUp(category: Category) { + fun moveTo(category: Category, offset: Int) { screenModelScope.launch { - when (reorderCategory.moveUp(category)) { - is ReorderCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError) - else -> {} - } - } - } - - fun moveDown(category: Category) { - screenModelScope.launch { - when (reorderCategory.moveDown(category)) { + when (reorderCategory.await(category, offset)) { 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 a64824801..e89ad08dd 100644 --- a/domain/src/main/java/tachiyomi/domain/category/interactor/ReorderCategory.kt +++ b/domain/src/main/java/tachiyomi/domain/category/interactor/ReorderCategory.kt @@ -8,7 +8,6 @@ import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.CategoryUpdate import tachiyomi.domain.category.repository.CategoryRepository -import java.util.Collections class ReorderCategory( private val categoryRepository: CategoryRepository, @@ -16,11 +15,7 @@ class ReorderCategory( private val mutex = Mutex() - suspend fun moveUp(category: Category): Result = await(category, MoveTo.UP) - - suspend fun moveDown(category: Category): Result = await(category, MoveTo.DOWN) - - private suspend fun await(category: Category, moveTo: MoveTo) = withNonCancellableContext { + suspend fun await(category: Category, offset: Int) = withNonCancellableContext { mutex.withLock { val categories = categoryRepository.getAll() .filterNot(Category::isSystemCategory) @@ -31,13 +26,10 @@ class ReorderCategory( return@withNonCancellableContext Result.Unchanged } - val newPosition = when (moveTo) { - MoveTo.UP -> currentIndex - 1 - MoveTo.DOWN -> currentIndex + 1 - }.toInt() + val newPosition = currentIndex + offset try { - Collections.swap(categories, currentIndex, newPosition) + categories.add(newPosition, categories.removeAt(currentIndex)) val updates = categories.mapIndexed { index, category -> CategoryUpdate( @@ -81,9 +73,4 @@ class ReorderCategory( data object Unchanged : Result data class InternalError(val error: Throwable) : Result } - - private enum class MoveTo { - UP, - DOWN, - } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 07679239d..1e65b2444 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -65,6 +65,7 @@ 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.0" } swipe = "me.saket.swipe:swipe:1.3.0" From b99c4f093f5a5a42d339de229279345c68cbbd84 Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Fri, 1 Nov 2024 02:13:34 +0700 Subject: [PATCH 2/6] smaller padding --- .../presentation/category/components/CategoryListItem.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 9ab4acc55..da0cdf461 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 @@ -36,7 +36,7 @@ fun ReorderableCollectionItemScope.CategoryListItem( modifier = Modifier .fillMaxWidth() .clickable { onRename() } - .padding(MaterialTheme.padding.medium), + .padding(MaterialTheme.padding.small), verticalAlignment = Alignment.CenterVertically, ) { IconButton( @@ -50,7 +50,7 @@ fun ReorderableCollectionItemScope.CategoryListItem( text = category.name, modifier = Modifier .weight(1f) - .padding(start = MaterialTheme.padding.medium), + .padding(start = MaterialTheme.padding.small), ) IconButton(onClick = onRename) { Icon( From 007ea5c4bb699978668abdcceea135d3967c1510 Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Fri, 1 Nov 2024 22:45:14 +0700 Subject: [PATCH 3/6] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3e653d9a..e4577438d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co - `Other` - for technical stuff. ## [Unreleased] +### Added +- Drag & Drop to reorder Category in Settings ([@cuong-tran](https://github.com/cuong-tran)) + ### Changed - Bump default user agent From 8a396e6ceeb6c81254c346abc712aadf53bd3141 Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Sat, 2 Nov 2024 01:01:32 +0700 Subject: [PATCH 4/6] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4577438d..3def0d418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co ## [Unreleased] ### Added -- Drag & Drop to reorder Category in Settings ([@cuong-tran](https://github.com/cuong-tran)) +- Drag & Drop to reorder Category in Settings ([@cuong-tran](https://github.com/cuong-tran)) ([#1427](https://github.com/mihonapp/mihon/pull/1427)) ### Changed - Bump default user agent From 82ece10ba097224eec07e4de7b39bfa86ed9e016 Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Thu, 7 Nov 2024 23:31:08 +0700 Subject: [PATCH 5/6] remove changelog update --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3def0d418..e3e653d9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,6 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co - `Other` - for technical stuff. ## [Unreleased] -### Added -- Drag & Drop to reorder Category in Settings ([@cuong-tran](https://github.com/cuong-tran)) ([#1427](https://github.com/mihonapp/mihon/pull/1427)) - ### Changed - Bump default user agent From a5cc1a25860c5e569ffb0f43ab617f0a93c37c7b Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Thu, 7 Nov 2024 23:46:44 +0700 Subject: [PATCH 6/6] cleanup code --- .../presentation/category/CategoryScreen.kt | 14 ++++++------- .../category/components/CategoryListItem.kt | 21 +++++++++++-------- .../tachiyomi/ui/category/CategoryScreen.kt | 2 +- .../ui/category/CategoryScreenModel.kt | 4 ++-- .../category/interactor/ReorderCategory.kt | 4 ++-- 5 files changed, 24 insertions(+), 21 deletions(-) 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 eb3259143..e644678e5 100644 --- a/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt @@ -41,7 +41,7 @@ fun CategoryScreen( onClickSortAlphabetically: () -> Unit, onClickRename: (Category) -> Unit, onClickDelete: (Category) -> Unit, - moveTo: (Category, Int) -> Unit, + changeOrder: (Category, Int) -> Unit, navigateUp: () -> Unit, ) { val lazyListState = rememberLazyListState() @@ -87,7 +87,7 @@ fun CategoryScreen( PaddingValues(horizontal = MaterialTheme.padding.medium), onClickRename = onClickRename, onClickDelete = onClickDelete, - moveTo = moveTo, + changeOrder = changeOrder, ) } } @@ -99,12 +99,12 @@ private fun CategoryContent( paddingValues: PaddingValues, onClickRename: (Category) -> Unit, onClickDelete: (Category) -> Unit, - moveTo: (Category, Int) -> Unit, + changeOrder: (Category, Int) -> Unit, ) { var reorderableList by remember { mutableStateOf(categories.toList()) } val reorderableLazyColumnState = rememberReorderableLazyListState(lazyListState) { from, to -> reorderableList = reorderableList.toMutableList().apply { - moveTo(reorderableList[from.index], to.index - from.index) + changeOrder(reorderableList[from.index], to.index - from.index) add(to.index, removeAt(from.index)) } } @@ -122,9 +122,9 @@ private fun CategoryContent( ) { items( items = reorderableList, - key = { category -> category.key() }, + key = { category -> category.key }, ) { category -> - ReorderableItem(reorderableLazyColumnState, category.key()) { + ReorderableItem(reorderableLazyColumnState, category.key) { CategoryListItem( modifier = Modifier.animateItem(), category = category, @@ -136,4 +136,4 @@ private fun CategoryContent( } } -fun Category.key() = "category-$id" +private val Category.key 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 da0cdf461..af4265352 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 @@ -6,8 +6,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.DragHandle import androidx.compose.material.icons.outlined.Edit -import androidx.compose.material.icons.rounded.DragHandle import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -36,21 +36,24 @@ fun ReorderableCollectionItemScope.CategoryListItem( modifier = Modifier .fillMaxWidth() .clickable { onRename() } - .padding(MaterialTheme.padding.small), + .padding(vertical = MaterialTheme.padding.small) + .padding( + start = MaterialTheme.padding.small, + end = MaterialTheme.padding.medium, + ), verticalAlignment = Alignment.CenterVertically, ) { - IconButton( + Icon( + imageVector = Icons.Outlined.DragHandle, + contentDescription = null, modifier = Modifier + .padding(MaterialTheme.padding.medium) .draggableHandle(), - onClick = {}, - ) { - Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder") - } + ) Text( text = category.name, modifier = Modifier - .weight(1f) - .padding(start = MaterialTheme.padding.small), + .weight(1f), ) IconButton(onClick = onRename) { Icon( 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 abaabfc3c..1aa79d736 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)) }, - moveTo = screenModel::moveTo, + changeOrder = screenModel::changeOrder, navigateUp = navigator::pop, ) 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 5aed5fddb..991893a48 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 @@ -74,9 +74,9 @@ class CategoryScreenModel( } } - fun moveTo(category: Category, offset: Int) { + fun changeOrder(category: Category, newOrder: Int) { screenModelScope.launch { - when (reorderCategory.await(category, offset)) { + when (reorderCategory.changeOrder(category, newOrder)) { 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 e89ad08dd..b0f299953 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 await(category: Category, offset: Int) = withNonCancellableContext { + suspend fun changeOrder(category: Category, newOrder: Int) = withNonCancellableContext { mutex.withLock { val categories = categoryRepository.getAll() .filterNot(Category::isSystemCategory) @@ -26,7 +26,7 @@ class ReorderCategory( return@withNonCancellableContext Result.Unchanged } - val newPosition = currentIndex + offset + val newPosition = currentIndex + newOrder try { categories.add(newPosition, categories.removeAt(currentIndex))