Drag & Drop to reorder category quickly in Settings/Category screen (#508)

* drag to reorder Category list

* smaller padding

* cleanup code

* fix build
This commit is contained in:
Cuong-Tran 2024-12-31 11:26:40 +07:00 committed by GitHub
parent bc6a4959b2
commit 23d53a4560
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 60 additions and 84 deletions

View file

@ -293,6 +293,7 @@ dependencies {
implementation(libs.swipe)
implementation(libs.compose.webview)
implementation(libs.compose.grid)
implementation(libs.reorderable)
// KMK -->
implementation(libs.palette.ktx)

View file

@ -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
@ -19,6 +24,8 @@ import eu.kanade.presentation.components.AppBarActions
import eu.kanade.tachiyomi.ui.category.CategoryScreenState
import kotlinx.collections.immutable.ImmutableList
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
@ -35,8 +42,7 @@ fun CategoryScreen(
onClickSortAlphabetically: () -> Unit,
onClickRename: (Category) -> Unit,
onClickDelete: (Category) -> Unit,
onClickMoveUp: (Category) -> Unit,
onClickMoveDown: (Category) -> Unit,
changeOrder: (Category, Int) -> Unit,
// KMK -->
onClickHide: (Category) -> Unit,
// KMK <--
@ -84,8 +90,7 @@ fun CategoryScreen(
PaddingValues(horizontal = MaterialTheme.padding.medium),
onClickRename = onClickRename,
onClickDelete = onClickDelete,
onMoveUp = onClickMoveUp,
onMoveDown = onClickMoveDown,
changeOrder = changeOrder,
// KMK -->
onClickHide = onClickHide,
// KMK <--
@ -100,34 +105,47 @@ private fun CategoryContent(
paddingValues: PaddingValues,
onClickRename: (Category) -> Unit,
onClickDelete: (Category) -> Unit,
onMoveUp: (Category) -> Unit,
onMoveDown: (Category) -> Unit,
changeOrder: (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))
}
}
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) },
// KMK -->
onHide = { onClickHide(category) },
// KMK <--
)
items(
items = reorderableList,
key = { category -> category.key },
) { category ->
ReorderableItem(reorderableLazyColumnState, category.key) {
CategoryListItem(
modifier = Modifier.animateItem(),
category = category,
onRename = { onClickRename(category) },
onDelete = { onClickDelete(category) },
// KMK -->
onHide = { onClickHide(category) },
// KMK <--
)
}
}
}
}
private val Category.key get() = "category-$id"

View file

@ -2,14 +2,11 @@ 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.DragHandle
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Visibility
import androidx.compose.material.icons.outlined.VisibilityOff
@ -23,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextDecoration
import sh.calvin.reorderable.ReorderableCollectionItemScope
import tachiyomi.domain.category.model.Category
import tachiyomi.i18n.MR
import tachiyomi.i18n.kmk.KMR
@ -30,12 +28,8 @@ 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,
// KMK -->
@ -50,19 +44,19 @@ fun CategoryListItem(
modifier = Modifier
.fillMaxWidth()
.clickable { onRename() }
.padding(vertical = MaterialTheme.padding.small)
.padding(
start = MaterialTheme.padding.medium,
top = MaterialTheme.padding.medium,
start = MaterialTheme.padding.small,
end = MaterialTheme.padding.medium,
),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = Icons.AutoMirrored.Outlined.Label,
imageVector = Icons.Outlined.DragHandle,
contentDescription = null,
// KMK -->
tint = LocalContentColor.current.let { if (category.hidden) it.copy(alpha = 0.6f) else it },
// KMK <--
modifier = Modifier
.padding(MaterialTheme.padding.medium)
.draggableHandle(),
)
Text(
text = category.name,
@ -71,23 +65,8 @@ fun CategoryListItem(
textDecoration = TextDecoration.LineThrough.takeIf { category.hidden },
// KMK <--
modifier = Modifier
.padding(start = MaterialTheme.padding.medium),
.weight(1f),
)
}
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,

View file

@ -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,
changeOrder = screenModel::changeOrder,
// KMK -->
onClickHide = screenModel::hideCategory,
// KMK <--

View file

@ -89,18 +89,9 @@ class CategoryScreenModel(
}
}
fun moveUp(category: Category) {
fun changeOrder(category: Category, newOrder: 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.changeOrder(category, newOrder)) {
is ReorderCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
else -> {}
}

View file

@ -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 changeOrder(category: Category, newOrder: 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 + newOrder
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,
}
}

View file

@ -67,6 +67,7 @@ compose-stablemarker = "com.github.skydoves:compose-stable-marker:1.0.5"
palette-ktx = "androidx.palette:palette-ktx:1.0.0"
materialKolor = "com.materialkolor:material-kolor:2.0.0"
haze = "dev.chrisbanes.haze:haze:1.1.1"
reorderable = { module = "sh.calvin.reorderable:reorderable", version = "2.4.0" }
swipe = "me.saket.swipe:swipe:1.3.0"