[优化] 左滑触发合并操作,优化界面
This commit is contained in:
3 files changed
+184
-111
No files matched your search
@@ -212,6 +212,7 @@ class ReadRecordRepository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transaction
|
||||||
private suspend fun mergeSingleReadRecordInto(targetRecord: ReadRecord, sourceRecord: ReadRecord) {
|
private suspend fun mergeSingleReadRecordInto(targetRecord: ReadRecord, sourceRecord: ReadRecord) {
|
||||||
if (targetRecord == sourceRecord) return
|
if (targetRecord == sourceRecord) return
|
||||||
if (targetRecord.deviceId != sourceRecord.deviceId) return
|
if (targetRecord.deviceId != sourceRecord.deviceId) return
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package io.legado.app.ui.book.readRecord
|
|||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.basicMarquee
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -23,6 +24,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.automirrored.filled.List
|
import androidx.compose.material.icons.automirrored.filled.List
|
||||||
import androidx.compose.material.icons.filled.CalendarMonth
|
import androidx.compose.material.icons.filled.CalendarMonth
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material.icons.filled.Merge
|
||||||
import androidx.compose.material.icons.filled.Schedule
|
import androidx.compose.material.icons.filled.Schedule
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material.icons.filled.Timeline
|
import androidx.compose.material.icons.filled.Timeline
|
||||||
@@ -36,6 +38,8 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@@ -47,6 +51,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -56,8 +61,11 @@ import androidx.compose.ui.draw.rotate
|
|||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import cn.hutool.core.date.DateUtil
|
import cn.hutool.core.date.DateUtil
|
||||||
@@ -75,6 +83,7 @@ import io.legado.app.ui.widget.components.button.SmallTopBarButton
|
|||||||
import io.legado.app.ui.widget.components.swipe.SwipeAction
|
import io.legado.app.ui.widget.components.swipe.SwipeAction
|
||||||
import io.legado.app.ui.widget.components.swipe.SwipeActionContainer
|
import io.legado.app.ui.widget.components.swipe.SwipeActionContainer
|
||||||
import io.legado.app.utils.StringUtils.formatFriendlyDate
|
import io.legado.app.utils.StringUtils.formatFriendlyDate
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
@@ -87,6 +96,9 @@ fun ReadRecordScreen(
|
|||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
onBookClick: (String, String) -> Unit
|
onBookClick: (String, String) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val state by viewModel.uiState.collectAsState()
|
val state by viewModel.uiState.collectAsState()
|
||||||
val displayMode by viewModel.displayMode.collectAsState()
|
val displayMode by viewModel.displayMode.collectAsState()
|
||||||
var showSearch by remember { mutableStateOf(false) }
|
var showSearch by remember { mutableStateOf(false) }
|
||||||
@@ -96,7 +108,7 @@ fun ReadRecordScreen(
|
|||||||
|
|
||||||
var skipDeleteConfirm by remember { mutableStateOf(false) }
|
var skipDeleteConfirm by remember { mutableStateOf(false) }
|
||||||
var pendingDeleteAction by remember { mutableStateOf<(() -> Unit)?>(null) }
|
var pendingDeleteAction by remember { mutableStateOf<(() -> Unit)?>(null) }
|
||||||
var mergeTargetRecord by remember { mutableStateOf<ReadRecord?>(null) }
|
var mergeDialogData by remember { mutableStateOf<Pair<ReadRecord, List<ReadRecord>>?>(null) }
|
||||||
val onConfirmDelete: (() -> Unit) -> Unit = { action ->
|
val onConfirmDelete: (() -> Unit) -> Unit = { action ->
|
||||||
if (skipDeleteConfirm) {
|
if (skipDeleteConfirm) {
|
||||||
action()
|
action()
|
||||||
@@ -113,6 +125,7 @@ fun ReadRecordScreen(
|
|||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||||
topBar = {
|
topBar = {
|
||||||
Column {
|
Column {
|
||||||
GlassMediumFlexibleTopAppBar(
|
GlassMediumFlexibleTopAppBar(
|
||||||
@@ -228,7 +241,16 @@ fun ReadRecordScreen(
|
|||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onBookClick = onBookClick,
|
onBookClick = onBookClick,
|
||||||
onConfirmDelete = onConfirmDelete,
|
onConfirmDelete = onConfirmDelete,
|
||||||
onMergeClick = { mergeTargetRecord = it }
|
onMergeClick = { record ->
|
||||||
|
scope.launch {
|
||||||
|
val candidates = viewModel.getMergeCandidates(record)
|
||||||
|
if (candidates.isEmpty()) {
|
||||||
|
snackbarHostState.showSnackbar("没有可合并的同名记录")
|
||||||
|
} else {
|
||||||
|
mergeDialogData = record to candidates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,49 +311,40 @@ fun ReadRecordScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeTargetRecord?.let { targetRecord ->
|
mergeDialogData?.let { (targetRecord, candidates) ->
|
||||||
val candidates by produceState(
|
|
||||||
initialValue = emptyList<ReadRecord>(),
|
var selectedAuthors by remember(targetRecord, candidates) {
|
||||||
key1 = targetRecord.bookName,
|
|
||||||
key2 = targetRecord.bookAuthor
|
|
||||||
) {
|
|
||||||
value = viewModel.getMergeCandidates(targetRecord)
|
|
||||||
}
|
|
||||||
var selectedAuthors by remember(targetRecord.bookName, targetRecord.bookAuthor, candidates) {
|
|
||||||
mutableStateOf(candidates.map { it.bookAuthor }.toSet())
|
mutableStateOf(candidates.map { it.bookAuthor }.toSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { mergeTargetRecord = null },
|
onDismissRequest = { mergeDialogData = null },
|
||||||
title = { Text("合并阅读记录") },
|
title = { Text("合并阅读记录") },
|
||||||
text = {
|
text = {
|
||||||
if (candidates.isEmpty()) {
|
Column {
|
||||||
Text("没有可合并的同名记录")
|
Text("将以下作者的“${targetRecord.bookName}”合并到 ${targetRecord.bookAuthor.ifBlank { "未知作者" }}")
|
||||||
} else {
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Column {
|
|
||||||
Text("将以下作者的“${targetRecord.bookName}”合并到 ${targetRecord.bookAuthor.ifBlank { "未知作者" }}")
|
candidates.forEach { candidate ->
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
val author = candidate.bookAuthor.ifBlank { "未知作者" }
|
||||||
candidates.forEach { candidate ->
|
val isChecked = selectedAuthors.contains(candidate.bookAuthor)
|
||||||
val author = candidate.bookAuthor.ifBlank { "未知作者" }
|
Row(
|
||||||
val isChecked = selectedAuthors.contains(candidate.bookAuthor)
|
modifier = Modifier
|
||||||
Row(
|
.fillMaxWidth()
|
||||||
modifier = Modifier
|
.clickable {
|
||||||
.fillMaxWidth()
|
selectedAuthors =
|
||||||
.clickable {
|
if (isChecked) selectedAuthors - candidate.bookAuthor
|
||||||
selectedAuthors =
|
else selectedAuthors + candidate.bookAuthor
|
||||||
if (isChecked) selectedAuthors - candidate.bookAuthor
|
}
|
||||||
else selectedAuthors + candidate.bookAuthor
|
.padding(vertical = 6.dp),
|
||||||
}
|
verticalAlignment = Alignment.CenterVertically
|
||||||
.padding(vertical = 6.dp),
|
) {
|
||||||
verticalAlignment = Alignment.CenterVertically
|
Checkbox(checked = isChecked, onCheckedChange = null)
|
||||||
) {
|
Text(
|
||||||
Checkbox(checked = isChecked, onCheckedChange = null)
|
text = "$author(${formatDuring(candidate.readTime)})",
|
||||||
Text(
|
modifier = Modifier.padding(start = 8.dp),
|
||||||
text = "$author(${formatDuring(candidate.readTime)})",
|
style = MaterialTheme.typography.bodyMedium
|
||||||
modifier = Modifier.padding(start = 8.dp),
|
)
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -339,20 +352,18 @@ fun ReadRecordScreen(
|
|||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (candidates.isNotEmpty()) {
|
viewModel.mergeReadRecords(
|
||||||
viewModel.mergeReadRecords(
|
targetRecord,
|
||||||
targetRecord,
|
candidates.filter { selectedAuthors.contains(it.bookAuthor) }
|
||||||
candidates.filter { selectedAuthors.contains(it.bookAuthor) }
|
)
|
||||||
)
|
mergeDialogData = null
|
||||||
}
|
|
||||||
mergeTargetRecord = null
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text("合并")
|
Text("合并")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { mergeTargetRecord = null }) {
|
TextButton(onClick = { mergeDialogData = null }) {
|
||||||
Text("取消")
|
Text("取消")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,6 +420,7 @@ fun LazyListScope.renderListByMode(
|
|||||||
onConfirmDelete: (() -> Unit) -> Unit,
|
onConfirmDelete: (() -> Unit) -> Unit,
|
||||||
onMergeClick: (ReadRecord) -> Unit
|
onMergeClick: (ReadRecord) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
||||||
when (displayMode) {
|
when (displayMode) {
|
||||||
DisplayMode.AGGREGATE -> {
|
DisplayMode.AGGREGATE -> {
|
||||||
state.groupedRecords.forEach { (date, details) ->
|
state.groupedRecords.forEach { (date, details) ->
|
||||||
@@ -469,13 +481,19 @@ fun LazyListScope.renderListByMode(
|
|||||||
onSwipe = {
|
onSwipe = {
|
||||||
onConfirmDelete { viewModel.deleteReadRecord(record) }
|
onConfirmDelete { viewModel.deleteReadRecord(record) }
|
||||||
}
|
}
|
||||||
|
),
|
||||||
|
endAction = SwipeAction(
|
||||||
|
icon = Icons.Default.Merge,
|
||||||
|
background = MaterialTheme.colorScheme.primary,
|
||||||
|
onSwipe = {
|
||||||
|
onMergeClick(record)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
LatestReadItem(
|
LatestReadItem(
|
||||||
record = record,
|
record = record,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onClick = { onBookClick(record.bookName, record.bookAuthor) },
|
onClick = { onBookClick(record.bookName, record.bookAuthor) }
|
||||||
onMergeClick = { onMergeClick(record) }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -488,7 +506,6 @@ fun LatestReadItem(
|
|||||||
record: ReadRecord,
|
record: ReadRecord,
|
||||||
viewModel: ReadRecordViewModel,
|
viewModel: ReadRecordViewModel,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onMergeClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
var coverPath by remember { mutableStateOf<String?>(null) }
|
var coverPath by remember { mutableStateOf<String?>(null) }
|
||||||
@@ -512,29 +529,37 @@ fun LatestReadItem(
|
|||||||
Text(
|
Text(
|
||||||
text = record.bookName,
|
text = record.bookName,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
maxLines = 2
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = record.bookAuthor.ifBlank { "未知作者" },
|
text = record.bookAuthor.ifBlank { "未知作者" },
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = Color.Gray,
|
color = MaterialTheme.colorScheme.outline,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "总时长: ${formatDuring(record.readTime)}",
|
modifier = Modifier
|
||||||
style = MaterialTheme.typography.bodySmall,
|
.basicMarquee(
|
||||||
color = Color.Gray
|
iterations = Int.MAX_VALUE,
|
||||||
|
repeatDelayMillis = 2000,
|
||||||
|
initialDelayMillis = 1000
|
||||||
|
),
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.outline)) {
|
||||||
|
append(formatDuring(record.readTime))
|
||||||
|
append(" • ")
|
||||||
|
}
|
||||||
|
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.primary)) {
|
||||||
|
append("${DateUtil.format(Date(record.lastRead), "yyyy-MM-dd HH:mm")}")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
Text(
|
|
||||||
text = "最后阅读: ${DateUtil.format(Date(record.lastRead), "yyyy-MM-dd HH:mm")}",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
TextButton(onClick = onMergeClick) {
|
|
||||||
Text("合并")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -613,21 +638,21 @@ fun TimelineSessionItem(
|
|||||||
Text(
|
Text(
|
||||||
text = session.bookName,
|
text = session.bookName,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
maxLines = 2,
|
maxLines = 1,
|
||||||
minLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = session.bookAuthor.ifBlank { "未知作者" },
|
text = session.bookAuthor.ifBlank { "未知作者" },
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = Color.Gray,
|
color = MaterialTheme.colorScheme.outline,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = chapterTitle.orEmpty(),
|
text = chapterTitle.orEmpty(),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
color = Color.Gray,
|
color = MaterialTheme.colorScheme.outline,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
@@ -665,18 +690,18 @@ fun ReadRecordItem(
|
|||||||
Text(
|
Text(
|
||||||
text = detail.bookName,
|
text = detail.bookName,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
maxLines = 2
|
maxLines = 1
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = detail.bookAuthor.ifBlank { "未知作者" },
|
text = detail.bookAuthor.ifBlank { "未知作者" },
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = Color.Gray
|
color = MaterialTheme.colorScheme.outline,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "阅读时长: ${formatDuring(detail.readTime)}",
|
text = "阅读时长: ${formatDuring(detail.readTime)}",
|
||||||
style = MaterialTheme.typography.bodySmall,
|
color = MaterialTheme.colorScheme.outline,
|
||||||
color = Color.Gray
|
style = MaterialTheme.typography.labelSmall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+84
-37
@@ -31,6 +31,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
fun SwipeActionContainer(
|
fun SwipeActionContainer(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
startAction: SwipeAction? = null,
|
startAction: SwipeAction? = null,
|
||||||
|
endAction: SwipeAction? = null,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
@@ -39,8 +40,6 @@ fun SwipeActionContainer(
|
|||||||
totalDistance * 0.6f
|
totalDistance * 0.6f
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
val isThresholdReached =
|
|
||||||
dismissState.progress > 0.5f && dismissState.targetValue == SwipeToDismissBoxValue.StartToEnd
|
|
||||||
|
|
||||||
LaunchedEffect(dismissState.targetValue) {
|
LaunchedEffect(dismissState.targetValue) {
|
||||||
if (dismissState.targetValue == SwipeToDismissBoxValue.StartToEnd) {
|
if (dismissState.targetValue == SwipeToDismissBoxValue.StartToEnd) {
|
||||||
@@ -51,9 +50,18 @@ fun SwipeActionContainer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(dismissState.settledValue) {
|
LaunchedEffect(dismissState.settledValue) {
|
||||||
if (dismissState.settledValue == SwipeToDismissBoxValue.StartToEnd) {
|
when (dismissState.settledValue) {
|
||||||
startAction?.onSwipe?.invoke()
|
SwipeToDismissBoxValue.StartToEnd -> {
|
||||||
dismissState.reset()
|
startAction?.onSwipe?.invoke()
|
||||||
|
dismissState.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
SwipeToDismissBoxValue.EndToStart -> {
|
||||||
|
endAction?.onSwipe?.invoke()
|
||||||
|
dismissState.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,44 +69,34 @@ fun SwipeActionContainer(
|
|||||||
state = dismissState,
|
state = dismissState,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
enableDismissFromStartToEnd = startAction != null,
|
enableDismissFromStartToEnd = startAction != null,
|
||||||
enableDismissFromEndToStart = false,
|
enableDismissFromEndToStart = endAction != null,
|
||||||
backgroundContent = {
|
backgroundContent = {
|
||||||
val direction = dismissState.dismissDirection
|
val direction = dismissState.dismissDirection
|
||||||
val progress = dismissState.progress
|
val progress = dismissState.progress
|
||||||
|
|
||||||
if (direction == SwipeToDismissBoxValue.StartToEnd) {
|
when (direction) {
|
||||||
val backgroundColor by animateColorAsState(
|
|
||||||
targetValue = if (isThresholdReached) startAction!!.background
|
|
||||||
else MaterialTheme.colorScheme.surfaceVariant,
|
|
||||||
label = "bgColor"
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
SwipeToDismissBoxValue.StartToEnd -> {
|
||||||
modifier = Modifier
|
startAction?.let { action ->
|
||||||
.fillMaxSize()
|
SwipeBackground(
|
||||||
.background(backgroundColor)
|
action = action,
|
||||||
.padding(horizontal = 24.dp),
|
progress = progress,
|
||||||
contentAlignment = Alignment.CenterStart
|
alignStart = true
|
||||||
) {
|
)
|
||||||
val iconScale by animateFloatAsState(
|
}
|
||||||
targetValue = if (isThresholdReached) 1.3f else progress.coerceIn(0.5f, 1f),
|
|
||||||
label = "iconScale"
|
|
||||||
)
|
|
||||||
|
|
||||||
Icon(
|
|
||||||
imageVector = startAction!!.icon,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(24.dp)
|
|
||||||
.graphicsLayer {
|
|
||||||
scaleX = iconScale
|
|
||||||
scaleY = iconScale
|
|
||||||
},
|
|
||||||
tint = if (isThresholdReached)
|
|
||||||
contentColorFor(startAction.background)
|
|
||||||
else MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SwipeToDismissBoxValue.EndToStart -> {
|
||||||
|
endAction?.let { action ->
|
||||||
|
SwipeBackground(
|
||||||
|
action = action,
|
||||||
|
progress = progress,
|
||||||
|
alignStart = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
@@ -111,4 +109,53 @@ fun SwipeActionContainer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SwipeBackground(
|
||||||
|
action: SwipeAction,
|
||||||
|
progress: Float,
|
||||||
|
alignStart: Boolean
|
||||||
|
) {
|
||||||
|
val isThresholdReached = progress > 0.6f
|
||||||
|
|
||||||
|
val backgroundColor by animateColorAsState(
|
||||||
|
targetValue = if (isThresholdReached)
|
||||||
|
action.background
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
label = "bgColor"
|
||||||
|
)
|
||||||
|
|
||||||
|
val iconScale by animateFloatAsState(
|
||||||
|
targetValue = if (isThresholdReached) 1.3f
|
||||||
|
else progress.coerceIn(0.5f, 1f),
|
||||||
|
label = "iconScale"
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(backgroundColor)
|
||||||
|
.padding(horizontal = 24.dp),
|
||||||
|
contentAlignment = if (alignStart)
|
||||||
|
Alignment.CenterStart
|
||||||
|
else
|
||||||
|
Alignment.CenterEnd
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = action.icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.graphicsLayer {
|
||||||
|
scaleX = iconScale
|
||||||
|
scaleY = iconScale
|
||||||
|
},
|
||||||
|
tint = if (isThresholdReached)
|
||||||
|
contentColorFor(action.background)
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user