Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drag-Drop reordering category #1427

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)) ([#1427](https://github.com/mihonapp/mihon/pull/1427))

### Changed
- Bump default user agent

Expand Down
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ dependencies {
implementation(libs.swipe)
implementation(libs.compose.webview)
implementation(libs.compose.grid)
implementation(libs.reorderable)

// Logging
implementation(libs.logcat)
Expand Down
60 changes: 39 additions & 21 deletions app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,26 @@ 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
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
Expand All @@ -34,8 +41,7 @@ fun CategoryScreen(
onClickSortAlphabetically: () -> Unit,
onClickRename: (Category) -> Unit,
onClickDelete: (Category) -> Unit,
onClickMoveUp: (Category) -> Unit,
onClickMoveDown: (Category) -> Unit,
moveTo: (Category, Int) -> Unit,
cuong-tran marked this conversation as resolved.
Show resolved Hide resolved
navigateUp: () -> Unit,
) {
val lazyListState = rememberLazyListState()
Expand Down Expand Up @@ -81,8 +87,7 @@ fun CategoryScreen(
PaddingValues(horizontal = MaterialTheme.padding.medium),
onClickRename = onClickRename,
onClickDelete = onClickDelete,
onMoveUp = onClickMoveUp,
onMoveDown = onClickMoveDown,
moveTo = moveTo,
cuong-tran marked this conversation as resolved.
Show resolved Hide resolved
)
}
}
Expand All @@ -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,
cuong-tran marked this conversation as resolved.
Show resolved Hide resolved
) {
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"
cuong-tran marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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.small),
cuong-tran marked this conversation as resolved.
Show resolved Hide resolved
verticalAlignment = Alignment.CenterVertically,
) {
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = null)
IconButton(
modifier = Modifier
.draggableHandle(),
onClick = {},
) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
cuong-tran marked this conversation as resolved.
Show resolved Hide resolved
Text(
text = category.name,
modifier = Modifier
.padding(start = MaterialTheme.padding.medium),
.weight(1f)
.padding(start = MaterialTheme.padding.small),
cuong-tran marked this conversation as resolved.
Show resolved Hide resolved
)
}
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
cuong-tran marked this conversation as resolved.
Show resolved Hide resolved
navigateUp = navigator::pop,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,9 @@ class CategoryScreenModel(
}
}

fun moveUp(category: Category) {
fun moveTo(category: Category, offset: Int) {
cuong-tran marked this conversation as resolved.
Show resolved Hide resolved
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 -> {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,14 @@ 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,
) {

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 {
cuong-tran marked this conversation as resolved.
Show resolved Hide resolved
mutex.withLock {
val categories = categoryRepository.getAll()
.filterNot(Category::isSystemCategory)
Expand All @@ -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(
Expand Down Expand Up @@ -81,9 +73,4 @@ class ReorderCategory(
data object Unchanged : Result
data class InternalError(val error: Throwable) : Result
}

private enum class MoveTo {
UP,
DOWN,
}
}
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down