From 10e927c30140f6170c6d848aff242c00ff62e2e0 Mon Sep 17 00:00:00 2001 From: starry-shivam Date: Thu, 28 Mar 2024 22:30:54 +0530 Subject: [PATCH] Improve transaction swipe action & navigation drawer Signed-off-by: starry-shivam --- app/build.gradle | 2 - .../ui/screens/home/composables/HomeScreen.kt | 31 +++- .../info/composables/GoalInfoScreen.kt | 175 +++++++++++++++--- .../settings/composables/GoalCardStyle.kt | 4 +- app/src/main/res/values-es/strings.xml | 13 +- app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-zh-rCN/strings.xml | 7 +- app/src/main/res/values/strings.xml | 1 + 8 files changed, 196 insertions(+), 38 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8ae63929..187636c0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -121,8 +121,6 @@ dependencies { implementation 'com.maxkeppeler.sheets-compose-dialogs:core:1.3.0' implementation 'com.maxkeppeler.sheets-compose-dialogs:calendar:1.3.0' implementation 'com.maxkeppeler.sheets-compose-dialogs:date-time:1.3.0' - // Swipe actions. - implementation "me.saket.swipe:swipe:1.2.0" // Taptarget compose. implementation "com.pierfrancescosoffritti.taptargetcompose:core:1.1.0" // Lottie animations. diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt index 67751d36..7a74fd8d 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt @@ -188,7 +188,10 @@ fun HomeScreenContent( drawerState = drawerState, gesturesEnabled = drawerState.isOpen, drawerContent = { - ModalDrawerSheet(drawerShape = RoundedCornerShape(4.dp)) { + ModalDrawerSheet( + modifier = Modifier.width(280.dp), + drawerShape = RoundedCornerShape(4.dp) + ) { Spacer(Modifier.height(14.dp)) Text( @@ -196,6 +199,7 @@ fun HomeScreenContent( modifier = Modifier.padding(start = 16.dp, top = 12.dp), fontSize = 24.sp, fontWeight = FontWeight.SemiBold, + fontFamily = greenstashFont, color = MaterialTheme.colorScheme.onSurface ) @@ -229,14 +233,35 @@ fun HomeScreenContent( navController.navigate(item.route) } }, - modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding) + modifier = Modifier + .width(280.dp) + .padding(NavigationDrawerItemDefaults.ItemPadding) ) Spacer(modifier = Modifier.height(4.dp)) } + + Spacer(Modifier.weight(1f)) + + Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + Text( + text = stringResource(id = R.string.drawer_footer_text), + modifier = Modifier.padding(bottom = 18.dp), + fontSize = 12.sp, + fontFamily = greenstashFont, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f) + ) + } + } }) { + + val showTapTargets = remember { mutableStateOf(false) } + LaunchedEffect(key1 = viewModel.showOnboardingTapTargets.value) { + delay(300) // Delay to prevent flickering + showTapTargets.value = viewModel.showOnboardingTapTargets.value + } TapTargetCoordinator( - showTapTargets = viewModel.showOnboardingTapTargets.value, + showTapTargets = showTapTargets.value, onComplete = { viewModel.onboardingTapTargetsShown() } ) { Scaffold(modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/GoalInfoScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/GoalInfoScreen.kt index 4bc1916e..d6b6c80e 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/GoalInfoScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/GoalInfoScreen.kt @@ -26,6 +26,7 @@ package com.starry.greenstash.ui.screens.info.composables import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -43,35 +44,50 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AlertDialog import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.NotificationsActive import androidx.compose.material.icons.filled.NotificationsOff +import androidx.compose.material.icons.rounded.Delete +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold +import androidx.compose.material3.SwipeToDismissBox +import androidx.compose.material3.SwipeToDismissBoxValue import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar +import androidx.compose.material3.rememberSwipeToDismissBoxState import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -101,9 +117,11 @@ import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.ui.theme.greenstashNumberFont import com.starry.greenstash.utils.Utils import com.starry.greenstash.utils.getActivity +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import me.saket.swipe.SwipeAction -import me.saket.swipe.SwipeableActionsBox +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext @ExperimentalCoroutinesApi @@ -183,7 +201,7 @@ fun GoalInfoScreen(goalId: String, navController: NavController) { Spacer(modifier = Modifier.height(6.dp)) } if (goalData.transactions.isNotEmpty()) { - TransactionCard(goalData.transactions.reversed(), currencySymbol, viewModel) + TransactionItem(goalData.transactions.reversed(), currencySymbol, viewModel) } else { Column( modifier = Modifier.fillMaxWidth(), @@ -385,30 +403,146 @@ fun GoalNotesCard(notesText: String) { @ExperimentalAnimationApi @ExperimentalMaterial3Api @Composable -fun TransactionCard( +fun TransactionItem( transactions: List, currencySymbol: String, viewModel: InfoViewModel ) { val settingsVM = (LocalContext.current.getActivity() as MainActivity).settingsViewModel - transactions.forEach { - val deleteAction = SwipeAction( - icon = painterResource( - id = if (settingsVM.getCurrentTheme() == ThemeMode.Light) - R.drawable.ic_goal_delete else R.drawable.ic_goal_delete_white - ), - background = Color.Red, - onSwipe = { - viewModel.deleteTransaction(it) + transactions.forEach {transaction -> + val showEditSheet = remember { mutableStateOf(false) } + val showDeleteDialog = remember { mutableStateOf(false) } + + val coroutineScope = rememberCoroutineScope() + val swipeState = rememberSwipeToDismissBoxState( + confirmValueChange = { direction -> + when (direction) { + SwipeToDismissBoxValue.EndToStart -> { + coroutineScope.launch { + delay(180) // allow the swipe to settle. + withContext(Dispatchers.Main) { showEditSheet.value = true} + } + } + + SwipeToDismissBoxValue.StartToEnd -> { + coroutineScope.launch { + delay(180) // allow the swipe to settle. + withContext(Dispatchers.Main) { showDeleteDialog.value = true} + } + } + + SwipeToDismissBoxValue.Settled -> {} + } + false // Don't allow it to settle on dismissed state. } ) - SwipeableActionsBox( - endActions = listOf(deleteAction), - swipeThreshold = 85.dp, - content = { TransactionItem(it, currencySymbol) } + + val dismissDirection = swipeState.dismissDirection + + SwipeToDismissBox( + state = swipeState, + backgroundContent = { + val color by animateColorAsState( + when (dismissDirection) { + SwipeToDismissBoxValue.EndToStart -> MaterialTheme.colorScheme.primary + SwipeToDismissBoxValue.StartToEnd -> Color.Red.copy(alpha = 0.5f) + SwipeToDismissBoxValue.Settled -> Color.Transparent + }, label = "color" + ) + val alignment by remember(dismissDirection) { + derivedStateOf { + when (dismissDirection) { + SwipeToDismissBoxValue.EndToStart -> Alignment.CenterEnd + SwipeToDismissBoxValue.StartToEnd -> Alignment.CenterStart + SwipeToDismissBoxValue.Settled -> Alignment.Center + } + } + } + val icon by remember(dismissDirection) { + derivedStateOf { + when (dismissDirection) { + SwipeToDismissBoxValue.EndToStart -> R.drawable.ic_goal_edit + SwipeToDismissBoxValue.StartToEnd -> R.drawable.ic_goal_delete + // Placeholder icon, not used anywhere. + SwipeToDismissBoxValue.Settled -> R.drawable.ic_goal_info + } + } + } + + val scale by animateFloatAsState( + if (swipeState.dismissDirection != SwipeToDismissBoxValue.Settled) 1f else 0.75f, + label = "scale" + ) + + Box( + Modifier + .fillMaxSize() + .background(color) + .padding(horizontal = 20.dp), + contentAlignment = alignment + ) { + Icon( + imageVector = ImageVector.vectorResource(id = icon), + contentDescription = null, + modifier = Modifier.scale(scale) + ) + } + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 4.dp) + .clip(shape = RoundedCornerShape(8.dp)), + enableDismissFromStartToEnd = true, + enableDismissFromEndToStart = true, + content = { + TransactionCard(transaction = transaction, currencySymbol = currencySymbol) + } ) + + if (showEditSheet.value) { + ModalBottomSheet(onDismissRequest = { showEditSheet.value = false }) { + // TODO: Add edit transaction sheet + + } + } + + if (showDeleteDialog.value) { + AlertDialog(onDismissRequest = { + showDeleteDialog.value = false + }, title = { + Text( + text = stringResource(id = R.string.goal_delete_confirmation), + color = MaterialTheme.colorScheme.onSurface, + fontFamily = greenstashFont, + ) + }, confirmButton = { + FilledTonalButton( + onClick = { + showDeleteDialog.value = false + viewModel.deleteTransaction(transaction) + }, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer + ) + ) { + Text(stringResource(id = R.string.confirm), fontFamily = greenstashFont) + } + }, dismissButton = { + TextButton(onClick = { + showDeleteDialog.value = false + }) { + Text(stringResource(id = R.string.cancel), fontFamily = greenstashFont) + } + }, + icon = { + Icon(imageVector = Icons.Rounded.Delete, contentDescription = null) + } + ) + } } + } @ExperimentalCoroutinesApi @@ -418,11 +552,10 @@ fun TransactionCard( @ExperimentalFoundationApi @ExperimentalMaterialApi @Composable -fun TransactionItem(transaction: Transaction, currencySymbol: String) { +fun TransactionCard(transaction: Transaction, currencySymbol: String) { Card( modifier = Modifier - .fillMaxWidth() - .padding(start = 12.dp, end = 12.dp, top = 4.dp), + .fillMaxWidth(), shape = RoundedCornerShape(8.dp), colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation( diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/GoalCardStyle.kt b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/GoalCardStyle.kt index dc88539a..5896cfd4 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/GoalCardStyle.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/GoalCardStyle.kt @@ -120,8 +120,6 @@ fun GoalCardStyle(navController: NavController) { .fillMaxSize() .padding(paddingValues) ) { - - OutlinedCard( modifier = Modifier .fillMaxWidth() @@ -233,7 +231,7 @@ fun GoalCardStyle(navController: NavController) { delay(500) showCompactTip.value = true } else { - delay(400) + delay(600) showCompactTip.value = false } } diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 4950ceb4..6ec4276f 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -4,12 +4,13 @@ Confirmar Cancelar - Oops! something went wrong. + ¡Vaya! Algo salió mal. Inicio Respaldo Ajustes + Hecho con ❤️ en India. Desbloquear GreenStash @@ -83,7 +84,7 @@ %s días restantes. Sin fecha objetivo. Meta alcanzada. ¡Felicitaciones! - Goal Priority is %s + La prioridad del objetivo es %s Los recordatorios están activados Los recordatorios están desactivados Notas @@ -95,7 +96,7 @@ Selecciona ícono para tu objetivo. Selecciona un ícono Imagen de la meta - Select priority for this goal. + Selecciona la prioridad para este objetivo. Título del Ahorro ¡Oops! el título está vacío. Meta @@ -105,8 +106,8 @@ Agregar Nueva Meta Actualizar Meta ¡Buen trabajo! Meta agregada. - Meta actualizada.\ - Do you want to remove deadline from this goal? + Meta actualizada. + ¿Quieres quitar la fecha límite de este objetivo? Respaldar y recuperar datos @@ -114,7 +115,7 @@ Nota: La copia de seguridad no incluye la configuración de la app. Respaldar Restaurar - Backup restored successfully! + ¡La copia de seguridad se ha restaurado correctamente! Configuración diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 3d0a7110..30d1a68a 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -10,6 +10,7 @@ Ana Sayfa Yedekler Ayarlar + ❤️ Hindistan\'da yapıldı. GreenStash\'in Kilidini Aç diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 027736fc..37fc3b3f 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -4,12 +4,13 @@ 确认 取消 - Oops! something went wrong. + 糟糕!出了点问题。 主页 备份 设置 + 用❤️制作于印度。 解锁GreenStash @@ -115,7 +116,7 @@ 注意:备份不包括应用设置信息。 备份 恢复 - Backup restored successfully! + 备份成功恢复! 设置 @@ -162,7 +163,7 @@ Github问题 通过在Github上创建问题来提交bug报告或功能需求。 Telegram组 - Join us at telegram to share your ideas or just to chat! + 加入我们的 Telegram,分享您的想法或进行聊天! 版本 版本-%s diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3afdc3cf..b4489c57 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,6 +10,7 @@ Home Backups Settings + Made with ❤️ in India. Unlock GreenStash