@@ -9,6 +9,7 @@ import androidx.compose.foundation.shape.CircleShape
99import androidx.compose.foundation.shape.RoundedCornerShape
1010import androidx.compose.material.icons.Icons
1111import androidx.compose.material.icons.filled.CheckCircle
12+ import androidx.compose.material.icons.filled.Delete
1213import androidx.compose.material.icons.outlined.LocalShipping
1314import androidx.compose.material3.*
1415import androidx.compose.runtime.*
@@ -32,6 +33,11 @@ import com.kaeru.app.tracking.utils.DateUtils
3233import com.kaeru.app.ui.components.TransitCalendarDialog
3334import kotlin.math.abs
3435import androidx.compose.runtime.saveable.rememberSaveable
36+ import androidx.compose.ui.draw.alpha
37+ import androidx.compose.ui.draw.scale
38+ import androidx.compose.ui.graphics.Color
39+ import com.kaeru.app.ui.components.AnimatedFilterChip
40+ import kotlinx.coroutines.launch
3541
3642@Composable
3743fun HistoryScreen (
@@ -59,6 +65,9 @@ fun HistoryScreen(
5965 TrackingFilter .ALL -> history
6066 }
6167 }
68+ val coroutineScope = rememberCoroutineScope()
69+ val snackbarHostState = remember { SnackbarHostState () }
70+ val isSwipeEnabled by viewModel.isSwipeToDeleteEnabled.collectAsState()
6271
6372 Scaffold (
6473 snackbarHost = { SnackbarHost (hostState = snackbarHostState) },
@@ -130,45 +139,83 @@ fun HistoryScreen(
130139 icon = Icons .Default .CheckCircle
131140 )
132141
133- FilterChip (
134- selected = currentFilter == TrackingFilter .ALL ,
135- onClick = { currentFilter = TrackingFilter .ALL },
136- label = { Text (" Todos" ) },
137- leadingIcon = if (currentFilter == TrackingFilter .ALL ) {
138- { Icon (painterResource(R .drawable.ic_package_outlined), null , modifier = Modifier .size(16 .dp)) }
139- } else null ,
140- shape = CircleShape
141- )
142+ AnimatedFilterChip (
143+ selected = currentFilter == TrackingFilter .ALL ,
144+ onClick = { currentFilter = TrackingFilter .ALL },
145+ label = stringResource(R .string.all_label),
146+ icon = Icons .Default .CheckCircle
147+ )
148+ }
142149 }
143150 }
144- }
145-
146- if (history.isEmpty()) {
147- item {
148- Box (
149- modifier = Modifier
150- .fillMaxWidth()
151- .height(300 .dp),
152- contentAlignment = Alignment .Center
153- ) {
154- val emptyText = when (currentFilter) {
155- TrackingFilter .IN_TRANSIT -> " Nenhuma encomenda em trânsito"
156- TrackingFilter .DELIVERED -> " Nenhuma encomenda entregue"
157- TrackingFilter .ALL -> stringResource(R .string.no_packages_saved)
151+ if (history.isEmpty()) {
152+ item {
153+ Box (
154+ modifier = Modifier
155+ .fillMaxWidth()
156+ .height(300 .dp),
157+ contentAlignment = Alignment .Center
158+ ) {
159+ val emptyText = when (currentFilter) {
160+ TrackingFilter .IN_TRANSIT -> stringResource(R .string.no_packages_saved)
161+ TrackingFilter .DELIVERED -> stringResource(R .string.no_packages_saved)
162+ TrackingFilter .ALL -> stringResource(R .string.no_packages_saved)
163+ }
164+ Text (
165+ text = emptyText,
166+ color = MaterialTheme .colorScheme.onSurfaceVariant
167+ )
158168 }
159- Text (
160- text = emptyText,
161- color = MaterialTheme .colorScheme.onSurfaceVariant
162- )
163169 }
164- }
165- } else {
166- items(filteredHistory, key = { it.code }) { item ->
167- Box (modifier = Modifier .animateItem()) {
168- HistoryCardNew (
169- item = item,
170- onClick = { onNavigateToResult(item.code) }
170+ } else {
171+ items(filteredHistory, key = { it.code }) { item ->
172+
173+ var hasFiredDelete by remember { mutableStateOf(false ) }
174+ val deletedOrder = stringResource(R .string.deleted_order)
175+ val undoAction = stringResource(R .string.undo_action)
176+
177+ val dismissState = rememberSwipeToDismissBoxState(
178+ positionalThreshold = { totalDistance -> totalDistance * 0.5f },
179+ confirmValueChange = { dismissValue ->
180+
181+ if (dismissValue == SwipeToDismissBoxValue .EndToStart ) {
182+
183+ if (! hasFiredDelete) {
184+ hasFiredDelete = true
185+
186+ viewModel.deleteTracking(item.code)
187+
188+ coroutineScope.launch {
189+ val result = snackbarHostState.showSnackbar(
190+ message = deletedOrder,
191+ actionLabel = undoAction,
192+ duration = SnackbarDuration .Short
193+ )
194+
195+ if (result == SnackbarResult .ActionPerformed ) {
196+ viewModel.restoreTracking(item)
197+ }
198+ }
199+ }
200+ false
201+ } else {
202+ false
203+ }
204+ }
171205 )
206+
207+ SwipeToDismissBox (
208+ state = dismissState,
209+ enableDismissFromEndToStart = isSwipeEnabled,
210+ enableDismissFromStartToEnd = false ,
211+ backgroundContent = { SwipeToDeleteIcon (dismissState) },
212+ modifier = Modifier .animateItem()
213+ ) {
214+ HistoryCardNew (
215+ item = item,
216+ onClick = { onNavigateToResult(item.code) }
217+ )
218+ }
172219 }
173220 }
174221 }
@@ -337,4 +384,36 @@ fun HistoryCardNew(
337384
338385enum class TrackingFilter {
339386 IN_TRANSIT , DELIVERED , ALL
387+ }
388+
389+ @OptIn(ExperimentalMaterial3Api ::class )
390+ @Composable
391+ fun SwipeToDeleteIcon (state : SwipeToDismissBoxState ) {
392+ val isDragging = state.dismissDirection == SwipeToDismissBoxValue .EndToStart
393+ val dragAmount = if (! isDragging) 0f else {
394+ if (state.targetValue == SwipeToDismissBoxValue .Settled ) {
395+ state.progress * 0.5f
396+ } else {
397+ 0.5f + (1f - state.progress) * 0.5f
398+ }
399+ }
400+ val iconAlpha = dragAmount.coerceIn(0f , 1f )
401+ val iconScale = (0.5f + (dragAmount * 0.7f )).coerceIn(0.5f , 1.2f )
402+
403+ Box (
404+ modifier = Modifier
405+ .fillMaxSize()
406+ .padding(horizontal = 24 .dp),
407+ contentAlignment = Alignment .CenterEnd
408+ ) {
409+ Icon (
410+ imageVector = Icons .Default .Delete ,
411+ contentDescription = null ,
412+ tint = MaterialTheme .colorScheme.error,
413+ modifier = Modifier
414+ .size(28 .dp)
415+ .scale(iconScale)
416+ .alpha(iconAlpha)
417+ )
418+ }
340419}
0 commit comments