scroll to next/previous group of errors

This commit is contained in:
Cuong-Tran 2024-10-24 01:55:33 +07:00
parent 58d59a555d
commit f1310ba879
No known key found for this signature in database
GPG Key ID: 733AA7624B9315C2
3 changed files with 80 additions and 6 deletions

View File

@ -24,6 +24,8 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.ZeroCornerSize import androidx.compose.foundation.shape.ZeroCornerSize
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowDownward
import androidx.compose.material.icons.outlined.ArrowUpward
import androidx.compose.material.icons.outlined.FindReplace import androidx.compose.material.icons.outlined.FindReplace
import androidx.compose.material.icons.outlined.FlipToBack import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.SelectAll import androidx.compose.material.icons.outlined.SelectAll
@ -36,9 +38,11 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.ripple import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -93,6 +97,21 @@ fun LibraryUpdateErrorScreen(
} }
} }
val headerIndexes = remember { mutableStateOf<List<Int>>(emptyList()) }
LaunchedEffect(state) {
headerIndexes.value = state.getHeaderIndexes()
}
val enableScrollToPrevious by remember {
derivedStateOf {
headerIndexes.value.any { it < listState.firstVisibleItemIndex }
}
}
val enableScrollToNext by remember {
derivedStateOf {
headerIndexes.value.any { it > listState.firstVisibleItemIndex }
}
}
BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) }) BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) })
Scaffold( Scaffold(
@ -128,6 +147,26 @@ fun LibraryUpdateErrorScreen(
listState.scrollToItem(state.items.size - 1) listState.scrollToItem(state.items.size - 1)
} }
}, },
enableScrollToPrevious = enableScrollToTop && enableScrollToPrevious,
enableScrollToNext = enableScrollToBottom && enableScrollToNext,
scrollToPrevious = {
scope.launch {
listState.scrollToItem(
state.getHeaderIndexes()
.filter { it < listState.firstVisibleItemIndex }
.maxOrNull() ?: 0,
)
}
},
scrollToNext = {
scope.launch {
listState.scrollToItem(
state.getHeaderIndexes()
.filter { it > listState.firstVisibleItemIndex }
.minOrNull() ?: 0,
)
}
},
) )
}, },
) { contentPadding -> ) { contentPadding ->
@ -166,6 +205,10 @@ private fun LibraryUpdateErrorsBottomBar(
enableScrollToBottom: Boolean, enableScrollToBottom: Boolean,
scrollToTop: () -> Unit, scrollToTop: () -> Unit,
scrollToBottom: () -> Unit, scrollToBottom: () -> Unit,
enableScrollToPrevious: Boolean,
enableScrollToNext: Boolean,
scrollToPrevious: () -> Unit,
scrollToNext: () -> Unit,
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
Surface( Surface(
@ -177,11 +220,11 @@ private fun LibraryUpdateErrorsBottomBar(
color = MaterialTheme.colorScheme.surfaceContainerHigh, color = MaterialTheme.colorScheme.surfaceContainerHigh,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val confirm = remember { mutableStateListOf(false, false, false) } val confirm = remember { mutableStateListOf(false, false, false, false, false) }
var resetJob: Job? = remember { null } var resetJob: Job? = remember { null }
val onLongClickItem: (Int) -> Unit = { toConfirmIndex -> val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
haptic.performHapticFeedback(HapticFeedbackType.LongPress) haptic.performHapticFeedback(HapticFeedbackType.LongPress)
(0 until 3).forEach { i -> confirm[i] = i == toConfirmIndex } (0 until 5).forEach { i -> confirm[i] = i == toConfirmIndex }
resetJob?.cancel() resetJob?.cancel()
resetJob = scope.launch { resetJob = scope.launch {
delay(1.seconds) delay(1.seconds)
@ -210,10 +253,22 @@ private fun LibraryUpdateErrorsBottomBar(
enabled = enableScrollToTop, enabled = enableScrollToTop,
) )
Button( Button(
title = stringResource(MR.strings.migrate), title = stringResource(MR.strings.action_scroll_to_previous),
icon = Icons.Outlined.FindReplace, icon = Icons.Outlined.ArrowUpward,
toConfirm = confirm[1], toConfirm = confirm[1],
onLongClick = { onLongClickItem(1) }, onLongClick = { onLongClickItem(1) },
onClick = if (enableScrollToPrevious) {
scrollToPrevious
} else {
{}
},
enabled = enableScrollToPrevious,
)
Button(
title = stringResource(MR.strings.migrate),
icon = Icons.Outlined.FindReplace,
toConfirm = confirm[2],
onLongClick = { onLongClickItem(2) },
onClick = if (selected.isNotEmpty()) { onClick = if (selected.isNotEmpty()) {
onMultiMigrateClicked onMultiMigrateClicked
} else { } else {
@ -221,11 +276,23 @@ private fun LibraryUpdateErrorsBottomBar(
}, },
enabled = selected.isNotEmpty(), enabled = selected.isNotEmpty(),
) )
Button(
title = stringResource(MR.strings.action_scroll_to_next),
icon = Icons.Outlined.ArrowDownward,
toConfirm = confirm[3],
onLongClick = { onLongClickItem(3) },
onClick = if (enableScrollToNext) {
scrollToNext
} else {
{}
},
enabled = enableScrollToNext,
)
Button( Button(
title = stringResource(MR.strings.action_scroll_to_bottom), title = stringResource(MR.strings.action_scroll_to_bottom),
icon = Icons.Outlined.VerticalAlignBottom, icon = Icons.Outlined.VerticalAlignBottom,
toConfirm = confirm[2], toConfirm = confirm[4],
onLongClick = { onLongClickItem(2) }, onLongClick = { onLongClickItem(4) },
onClick = if (enableScrollToBottom) { onClick = if (enableScrollToBottom) {
scrollToBottom scrollToBottom
} else { } else {

View File

@ -159,6 +159,11 @@ data class LibraryUpdateErrorScreenState(
} }
return uiModels return uiModels
} }
fun getHeaderIndexes(): List<Int> = getUiModel()
.withIndex()
.filter { it.value is LibraryUpdateErrorUiModel.Header }
.map { it.index }
} }
@Immutable @Immutable

View File

@ -579,6 +579,8 @@
<string name="info_empty_library_update_errors">You have no library update errors.</string> <string name="info_empty_library_update_errors">You have no library update errors.</string>
<string name="action_scroll_to_top">Scroll to top</string> <string name="action_scroll_to_top">Scroll to top</string>
<string name="action_scroll_to_bottom">Scroll to bottom</string> <string name="action_scroll_to_bottom">Scroll to bottom</string>
<string name="action_scroll_to_previous">Scroll to previous</string>
<string name="action_scroll_to_next">Scroll to next</string>
<!-- Advanced section --> <!-- Advanced section -->
<string name="label_network">Networking</string> <string name="label_network">Networking</string>