diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 608684fc..0c0c3383 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -3,20 +3,7 @@ - - - - - - - - - - - - - - + diff --git a/app/src/main/java/com/starry/greenstash/MainActivity.kt b/app/src/main/java/com/starry/greenstash/MainActivity.kt index a3586b81..39e8cab8 100644 --- a/app/src/main/java/com/starry/greenstash/MainActivity.kt +++ b/app/src/main/java/com/starry/greenstash/MainActivity.kt @@ -152,6 +152,9 @@ class MainActivity : AppCompatActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { + val navController = rememberNavController() + val startDestination by mainViewModel.startDestination + Crossfade( targetState = showAppContents, label = "AppLockCrossFade", @@ -159,9 +162,7 @@ class MainActivity : AppCompatActivity() { ) { showAppContents -> // show app contents only if user has authenticated. if (showAppContents.value) { - val navController = rememberNavController() - val screen by mainViewModel.startDestination - NavGraph(navController = navController, screen) + NavGraph(navController = navController, startDestination) } else { // show app locked screen if user has not authenticated. AppLockedScreen(onAuthRequest = { diff --git a/app/src/main/java/com/starry/greenstash/ui/common/DateTimeCard.kt b/app/src/main/java/com/starry/greenstash/ui/common/DateTimeCard.kt index 5d73b94b..0f8d28aa 100644 --- a/app/src/main/java/com/starry/greenstash/ui/common/DateTimeCard.kt +++ b/app/src/main/java/com/starry/greenstash/ui/common/DateTimeCard.kt @@ -39,6 +39,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview @@ -46,6 +47,7 @@ import androidx.compose.ui.unit.dp import com.starry.greenstash.R import com.starry.greenstash.ui.screens.settings.DateStyle import com.starry.greenstash.ui.theme.greenstashFont +import com.starry.greenstash.utils.weakHapticFeedback import java.time.LocalDateTime import java.time.format.DateTimeFormatter @@ -55,11 +57,15 @@ fun DateTimeCard( dateTimeStyle: () -> DateStyle, onClick: () -> Unit ) { + val view = LocalView.current Card( modifier = Modifier .fillMaxWidth() .padding(horizontal = 18.dp, vertical = 8.dp) - .clickable { onClick() } + .clickable { + view.weakHapticFeedback() + onClick() + } ) { Row( modifier = Modifier diff --git a/app/src/main/java/com/starry/greenstash/ui/common/TipCard.kt b/app/src/main/java/com/starry/greenstash/ui/common/TipCard.kt new file mode 100644 index 00000000..a117431e --- /dev/null +++ b/app/src/main/java/com/starry/greenstash/ui/common/TipCard.kt @@ -0,0 +1,125 @@ +/** + * MIT License + * + * Copyright (c) [2022 - Present] Stɑrry Shivɑm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +package com.starry.greenstash.ui.common + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Lightbulb +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.starry.greenstash.ui.theme.greenstashFont + +@Composable +fun TipCard( + modifier: Modifier = Modifier, + icon: ImageVector = Icons.Filled.Lightbulb, + description: String, + showTipCard: Boolean, + onDismissRequest: () -> Unit +) { + Column( + modifier = Modifier + .fillMaxWidth() + .animateContentSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + AnimatedVisibility( + visible = showTipCard, + enter = expandVertically(), + exit = shrinkVertically() + ) { + Card( + modifier = modifier + .fillMaxWidth() + .padding(bottom = 10.dp), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) + ) { + Column( + modifier = Modifier.padding(16.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(32.dp) + ) + Spacer(modifier = Modifier.width(16.dp)) + Text( + text = description, + style = MaterialTheme.typography.titleSmall, + fontFamily = greenstashFont, + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Button( + onClick = { onDismissRequest() }, + modifier = Modifier.align(Alignment.End) + ) { + Text(text = "OK") + } + } + } + } + } +} + +@Preview +@Composable +private fun TipCardPV() { + TipCard( + description = "This is a tip", + showTipCard = true, + onDismissRequest = {} + ) +} diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/backups/composables/BackupScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/backups/composables/BackupScreen.kt index c408112c..841dcfd5 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/backups/composables/BackupScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/backups/composables/BackupScreen.kt @@ -64,6 +64,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -73,6 +74,7 @@ import coil.compose.AsyncImage import com.starry.greenstash.R import com.starry.greenstash.ui.screens.backups.BackupViewModel import com.starry.greenstash.ui.theme.greenstashFont +import com.starry.greenstash.utils.weakHapticFeedback import kotlinx.coroutines.launch import java.io.InputStreamReader import java.io.Reader @@ -82,6 +84,7 @@ import java.nio.charset.StandardCharsets @OptIn(ExperimentalMaterial3Api::class) @Composable fun BackupScreen(navController: NavController) { + val view = LocalView.current val context = LocalContext.current val viewModel = hiltViewModel() @@ -102,7 +105,10 @@ fun BackupScreen(navController: NavController) { fontFamily = greenstashFont ) }, navigationIcon = { - IconButton(onClick = { navController.navigateUp() }) { + IconButton(onClick = { + view.weakHapticFeedback() + navController.navigateUp() + }) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/dwscreen/composables/DWScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/dwscreen/composables/DWScreen.kt index b754a15e..a079230f 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/dwscreen/composables/DWScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/dwscreen/composables/DWScreen.kt @@ -60,6 +60,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight @@ -88,6 +89,7 @@ import com.starry.greenstash.ui.screens.dwscreen.DWViewModel import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.utils.Utils import com.starry.greenstash.utils.validateAmount +import com.starry.greenstash.utils.weakHapticFeedback import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -99,6 +101,7 @@ import java.time.LocalDateTime @OptIn(ExperimentalMaterial3Api::class) @Composable fun DWScreen(goalId: String, transactionTypeName: String, navController: NavController) { + val view = LocalView.current val context = LocalContext.current val viewModel: DWViewModel = hiltViewModel() @@ -141,7 +144,10 @@ fun DWScreen(goalId: String, transactionTypeName: String, navController: NavCont fontFamily = greenstashFont ) }, navigationIcon = { - IconButton(onClick = { navController.navigateUp() }) { + IconButton(onClick = { + view.weakHapticFeedback() + navController.navigateUp() + }) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt index 4519916e..cba270d3 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt @@ -36,6 +36,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.starry.greenstash.MainActivity @@ -50,6 +51,8 @@ import com.starry.greenstash.utils.GoalTextUtils import com.starry.greenstash.utils.ImageUtils import com.starry.greenstash.utils.Utils import com.starry.greenstash.utils.getActivity +import com.starry.greenstash.utils.strongHapticFeedback +import com.starry.greenstash.utils.weakHapticFeedback import kotlinx.coroutines.launch @@ -71,6 +74,7 @@ fun GoalLazyColumnItem( } val openDeleteDialog = remember { mutableStateOf(false) } + val localView = LocalView.current when (goalCardStyle) { GoalCardStyle.Classic -> { @@ -90,6 +94,7 @@ fun GoalLazyColumnItem( goalProgress = progressPercent.toFloat() / 100, goalImage = item.goal.goalImage, onDepositClicked = { + localView.weakHapticFeedback() if (item.getCurrentlySavedAmount() >= item.goal.targetAmount) { coroutineScope.launch { snackBarHostState.showSnackbar(context.getString(R.string.goal_already_achieved)) @@ -104,6 +109,7 @@ fun GoalLazyColumnItem( } }, onWithdrawClicked = { + localView.weakHapticFeedback() if (item.getCurrentlySavedAmount() == 0f.toDouble()) { coroutineScope.launch { snackBarHostState.showSnackbar(context.getString(R.string.withdraw_button_error)) @@ -118,6 +124,7 @@ fun GoalLazyColumnItem( } }, onInfoClicked = { + localView.weakHapticFeedback() navController.navigate( Screens.GoalInfoScreen.withGoalId( goalId = item.goal.goalId.toString() @@ -125,13 +132,17 @@ fun GoalLazyColumnItem( ) }, onEditClicked = { + localView.weakHapticFeedback() navController.navigate( Screens.InputScreen.withGoalToEdit( goalId = item.goal.goalId.toString() ) ) }, - onDeleteClicked = { openDeleteDialog.value = true } + onDeleteClicked = { + localView.strongHapticFeedback() + openDeleteDialog.value = true + } ) } @@ -162,6 +173,7 @@ fun GoalLazyColumnItem( goalProgress = progressPercent.toFloat() / 100, goalIcon = goalIcon, onDepositClicked = { + localView.weakHapticFeedback() if (item.getCurrentlySavedAmount() >= item.goal.targetAmount) { coroutineScope.launch { snackBarHostState.showSnackbar(context.getString(R.string.goal_already_achieved)) @@ -176,6 +188,7 @@ fun GoalLazyColumnItem( } }, onWithdrawClicked = { + localView.weakHapticFeedback() if (item.getCurrentlySavedAmount() == 0f.toDouble()) { coroutineScope.launch { snackBarHostState.showSnackbar(context.getString(R.string.withdraw_button_error)) @@ -190,6 +203,7 @@ fun GoalLazyColumnItem( } }, onInfoClicked = { + localView.weakHapticFeedback() navController.navigate( Screens.GoalInfoScreen.withGoalId( goalId = item.goal.goalId.toString() @@ -197,6 +211,7 @@ fun GoalLazyColumnItem( ) }, onEditClicked = { + localView.weakHapticFeedback() navController.navigate( Screens.InputScreen.withGoalToEdit( goalId = item.goal.goalId.toString() @@ -204,6 +219,7 @@ fun GoalLazyColumnItem( ) }, onDeleteClicked = { + localView.strongHapticFeedback() openDeleteDialog.value = true } ) diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeAppBars.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeAppBars.kt index 039e99f3..fcaabd68 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeAppBars.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeAppBars.kt @@ -50,6 +50,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.input.ImeAction @@ -58,6 +59,7 @@ import androidx.compose.ui.unit.dp import com.starry.greenstash.R import com.starry.greenstash.ui.screens.home.SearchWidgetState import com.starry.greenstash.ui.theme.greenstashFont +import com.starry.greenstash.utils.weakHapticFeedback @Composable @@ -73,7 +75,8 @@ fun HomeAppBar( ) { Crossfade( targetState = searchWidgetState, - animationSpec = tween(durationMillis = 300), label = "searchbar cross-fade" + animationSpec = tween(durationMillis = 300), + label = "searchbar cross-fade" ) { when (it) { SearchWidgetState.CLOSED -> { @@ -104,6 +107,7 @@ private fun DefaultAppBar( onFilterClicked: () -> Unit, onSearchClicked: () -> Unit, ) { + val view = LocalView.current TopAppBar( title = { Text( @@ -114,7 +118,10 @@ private fun DefaultAppBar( ) }, navigationIcon = { - IconButton(onClick = { onMenuClicked() }) { + IconButton(onClick = { + view.weakHapticFeedback() + onMenuClicked() + }) { Icon( imageVector = Icons.Filled.Menu, contentDescription = stringResource(id = R.string.menu_button_desc) @@ -122,14 +129,20 @@ private fun DefaultAppBar( } }, actions = { - IconButton(onClick = { onFilterClicked() }) { + IconButton(onClick = { + view.weakHapticFeedback() + onFilterClicked() + }) { Icon( imageVector = ImageVector.vectorResource(id = R.drawable.ic_menu_filter), contentDescription = stringResource(id = R.string.filter_button_desc), modifier = Modifier.size(22.dp) ) } - IconButton(onClick = { onSearchClicked() }) { + IconButton(onClick = { + view.weakHapticFeedback() + onSearchClicked() + }) { Icon( imageVector = Icons.Filled.Search, contentDescription = stringResource(id = R.string.search_button_desc) @@ -148,8 +161,7 @@ private fun SearchAppBar( onSearchClicked: (String) -> Unit, ) { Surface( - modifier = Modifier.fillMaxWidth(), - color = MaterialTheme.colorScheme.surface + modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.surface ) { OutlinedTextField( modifier = Modifier @@ -166,9 +178,7 @@ private fun SearchAppBar( }, singleLine = true, leadingIcon = { - IconButton( - onClick = {} - ) { + IconButton(onClick = {}) { Icon( imageVector = Icons.Default.Search, contentDescription = null, @@ -177,15 +187,13 @@ private fun SearchAppBar( } }, trailingIcon = { - IconButton( - onClick = { - if (text.isNotEmpty()) { - onTextChange("") - } else { - onCloseClicked() - } + IconButton(onClick = { + if (text.isNotEmpty()) { + onTextChange("") + } else { + onCloseClicked() } - ) { + }) { Icon( imageVector = Icons.Filled.Close, contentDescription = null, @@ -196,17 +204,16 @@ private fun SearchAppBar( keyboardOptions = KeyboardOptions( imeAction = ImeAction.Search ), - keyboardActions = KeyboardActions( - onSearch = { - onSearchClicked(text) - } - ), + keyboardActions = KeyboardActions(onSearch = { + onSearchClicked(text) + }), colors = TextFieldDefaults.colors( focusedContainerColor = Color.Transparent, unfocusedContainerColor = MaterialTheme.colorScheme.primaryContainer.copy(0.3f), disabledContainerColor = Color.Transparent, cursorColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f), ), - shape = RoundedCornerShape(24.dp)) + shape = RoundedCornerShape(24.dp) + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDialogs.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDialogs.kt index beab8d83..fb71eedb 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDialogs.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDialogs.kt @@ -36,8 +36,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState -import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import com.starry.greenstash.R import com.starry.greenstash.ui.theme.greenstashFont @@ -48,10 +46,8 @@ fun HomeDialogs( openDeleteDialog: MutableState, onDeleteConfirmed: () -> Unit, ) { - val haptic = LocalHapticFeedback.current - if (openDeleteDialog.value) { - haptic.performHapticFeedback(HapticFeedbackType.LongPress) + AlertDialog(onDismissRequest = { openDeleteDialog.value = false }, title = { diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDrawer.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDrawer.kt index 87bd5772..b75c5438 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDrawer.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDrawer.kt @@ -47,6 +47,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight @@ -57,12 +58,15 @@ import com.starry.greenstash.R import com.starry.greenstash.ui.navigation.DrawerScreens import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.utils.Utils +import com.starry.greenstash.utils.weakHapticFeedback import kotlinx.coroutines.launch @Composable fun HomeDrawer(drawerState: DrawerState, navController: NavController) { val items = listOf(DrawerScreens.Home, DrawerScreens.Backups, DrawerScreens.Settings) val selectedItem = remember { mutableStateOf(items[0]) } + + val view = LocalView.current val coroutineScope = rememberCoroutineScope() ModalDrawerSheet( @@ -104,6 +108,7 @@ fun HomeDrawer(drawerState: DrawerState, navController: NavController) { }, selected = item == selectedItem.value, onClick = { + view.weakHapticFeedback() selectedItem.value = item coroutineScope.launch { drawerState.close() 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 24fe001d..1b8f09f2 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 @@ -81,6 +81,7 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -108,6 +109,7 @@ import com.starry.greenstash.ui.screens.home.HomeViewModel import com.starry.greenstash.ui.screens.home.SearchWidgetState import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.utils.isScrollingUp +import com.starry.greenstash.utils.weakHapticFeedback import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.Locale @@ -386,6 +388,8 @@ private fun HomeExtendedFAB( ) { val isFabVisible = lazyListState.isScrollingUp() val density = LocalDensity.current + val view = LocalView.current + AnimatedVisibility( visible = isFabVisible, enter = slideInVertically { @@ -399,7 +403,10 @@ private fun HomeExtendedFAB( ) { ExtendedFloatingActionButton( modifier = modifier.padding(end = 10.dp, bottom = 12.dp), - onClick = { navController.navigate(Screens.InputScreen.route) }, + onClick = { + view.weakHapticFeedback() + navController.navigate(Screens.InputScreen.route) + }, elevation = FloatingActionButtonDefaults.elevation(8.dp) ) { Row { @@ -443,7 +450,7 @@ private fun FilterSheetContent(viewModel: HomeViewModel) { } } } - + Spacer(modifier = Modifier.height(16.dp)) } } 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 fdab54db..282481a6 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 @@ -55,10 +55,8 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.surfaceColorAtElevation @@ -66,12 +64,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -92,20 +92,24 @@ import com.starry.greenstash.database.goal.GoalPriority.High import com.starry.greenstash.database.goal.GoalPriority.Low import com.starry.greenstash.database.goal.GoalPriority.Normal import com.starry.greenstash.ui.common.ExpandableTextCard +import com.starry.greenstash.ui.common.TipCard import com.starry.greenstash.ui.screens.info.InfoViewModel import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.ui.theme.greenstashNumberFont import com.starry.greenstash.utils.GoalTextUtils import com.starry.greenstash.utils.Utils +import com.starry.greenstash.utils.weakHapticFeedback +import kotlinx.coroutines.delay @OptIn(ExperimentalMaterial3Api::class) @Composable fun GoalInfoScreen(goalId: String, navController: NavController) { + val view = LocalView.current + val context = LocalContext.current val viewModel: InfoViewModel = hiltViewModel() val state = viewModel.state - val context = LocalContext.current val snackBarHostState = remember { SnackbarHostState() } @@ -125,7 +129,10 @@ fun GoalInfoScreen(goalId: String, navController: NavController) { fontFamily = greenstashFont ) }, navigationIcon = { - IconButton(onClick = { navController.navigateUp() }) { + IconButton(onClick = { + view.weakHapticFeedback() + navController.navigateUp() + }) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null @@ -183,30 +190,32 @@ fun GoalInfoScreen(goalId: String, navController: NavController) { Spacer(modifier = Modifier.height(6.dp)) } if (goalData.transactions.isNotEmpty()) { - TransactionItems( - goalData.getOrderedTransactions(), - currencySymbol, - viewModel - ) // Show tooltip for swipe functionality. + val showTransactionSwipeTip = remember { mutableStateOf(false) } LaunchedEffect(key1 = true) { if (viewModel.shouldShowTransactionTip()) { - val result = snackBarHostState.showSnackbar( - message = context.getString(R.string.info_transaction_onboarding_tip), - actionLabel = context.getString(R.string.ok), - duration = SnackbarDuration.Indefinite - ) - - when (result) { - SnackbarResult.ActionPerformed -> { - viewModel.transactionTipDismissed() - } - - SnackbarResult.Dismissed -> {} - } + delay(800) // Don't show immediately. + showTransactionSwipeTip.value = true } } + TipCard( + modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp), + description = stringResource(id = R.string.info_transaction_swipe_tip), + showTipCard = showTransactionSwipeTip.value, + onDismissRequest = { + showTransactionSwipeTip.value = false + viewModel.transactionTipDismissed() + } + ) + + // Show transaction items. + TransactionItems( + goalData.getOrderedTransactions(), + currencySymbol, + viewModel + ) + } else { Column( modifier = Modifier.fillMaxWidth(), diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/TransactionItems.kt b/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/TransactionItems.kt index 50ff0c7d..5d0421bb 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/TransactionItems.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/TransactionItems.kt @@ -73,6 +73,7 @@ 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.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight @@ -87,6 +88,8 @@ import com.starry.greenstash.ui.screens.info.InfoViewModel import com.starry.greenstash.ui.screens.settings.ThemeMode import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.utils.getActivity +import com.starry.greenstash.utils.strongHapticFeedback +import com.starry.greenstash.utils.weakHapticFeedback import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -190,6 +193,7 @@ private fun TransactionSwipeContainer( showEditSheet: MutableState, showDeleteDialog: MutableState ) { + val view = LocalView.current val coroutineScope = rememberCoroutineScope() val swipeState = rememberSwipeToDismissBoxState( confirmValueChange = { direction -> @@ -197,14 +201,20 @@ private fun TransactionSwipeContainer( SwipeToDismissBoxValue.EndToStart -> { coroutineScope.launch { delay(180) // allow the swipe to settle. - withContext(Dispatchers.Main) { showEditSheet.value = true } + withContext(Dispatchers.Main) { + view.weakHapticFeedback() + showEditSheet.value = true + } } } SwipeToDismissBoxValue.StartToEnd -> { coroutineScope.launch { delay(180) // allow the swipe to settle. - withContext(Dispatchers.Main) { showDeleteDialog.value = true } + withContext(Dispatchers.Main) { + view.strongHapticFeedback() + showDeleteDialog.value = true + } } } diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/input/composables/InputScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/input/composables/InputScreen.kt index 00ce47b1..31fc67d5 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/input/composables/InputScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/input/composables/InputScreen.kt @@ -36,10 +36,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.expandVertically -import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -66,7 +62,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Image -import androidx.compose.material.icons.filled.Lightbulb import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Card @@ -104,6 +99,7 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight @@ -139,6 +135,7 @@ import com.starry.greenstash.MainActivity import com.starry.greenstash.R import com.starry.greenstash.database.goal.GoalPriority import com.starry.greenstash.ui.common.SelectableChipGroup +import com.starry.greenstash.ui.common.TipCard import com.starry.greenstash.ui.navigation.DrawerScreens import com.starry.greenstash.ui.screens.input.InputViewModel import com.starry.greenstash.ui.theme.greenstashFont @@ -148,6 +145,7 @@ import com.starry.greenstash.utils.getActivity import com.starry.greenstash.utils.hasNotificationPermission import com.starry.greenstash.utils.toToast import com.starry.greenstash.utils.validateAmount +import com.starry.greenstash.utils.weakHapticFeedback import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -158,7 +156,9 @@ import java.time.format.DateTimeFormatter @OptIn(ExperimentalMaterial3Api::class) @Composable fun InputScreen(editGoalId: String?, navController: NavController) { + val view = LocalView.current val context = LocalContext.current + val viewModel: InputViewModel = hiltViewModel() val coroutineScope = rememberCoroutineScope() val snackBarHostState = remember { SnackbarHostState() } @@ -290,7 +290,10 @@ fun InputScreen(editGoalId: String?, navController: NavController) { fontFamily = greenstashFont ) }, navigationIcon = { - IconButton(onClick = { navController.navigateUp() }) { + IconButton(onClick = { + view.weakHapticFeedback() + navController.navigateUp() + }) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null @@ -418,8 +421,8 @@ fun InputScreen(editGoalId: String?, navController: NavController) { } } - InputTipCard( - icon = Icons.Filled.Lightbulb, + TipCard( + modifier = Modifier.fillMaxWidth(0.86f), description = stringResource(id = R.string.input_remove_deadline_tip), showTipCard = showRemoveDeadlineTip.value, onDismissRequest = { @@ -478,64 +481,6 @@ fun InputScreen(editGoalId: String?, navController: NavController) { } } -@Composable -private fun InputTipCard( - icon: ImageVector, - description: String, - showTipCard: Boolean, - onDismissRequest: () -> Unit -) { - Column( - modifier = Modifier - .fillMaxWidth() - .animateContentSize(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - AnimatedVisibility( - visible = showTipCard, - enter = expandVertically(), - exit = shrinkVertically() - ) { - Card( - modifier = Modifier - .fillMaxWidth(0.86f) - .padding(bottom = 10.dp), - shape = RoundedCornerShape(16.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer - ) - ) { - Column( - modifier = Modifier.padding(16.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = icon, - contentDescription = null, - modifier = Modifier.size(32.dp) - ) - Spacer(modifier = Modifier.width(16.dp)) - Text( - text = description, - style = MaterialTheme.typography.titleSmall, - fontFamily = greenstashFont, - ) - } - Spacer(modifier = Modifier.height(16.dp)) - Button( - onClick = { onDismissRequest() }, - modifier = Modifier.align(Alignment.End) - ) { - Text(text = "OK") - } - } - } - } - } -} - @Composable private fun GoalImagePicker( goalImage: Any?, @@ -544,6 +489,8 @@ private fun GoalImagePicker( @SuppressLint("ModifierParameter") fabModifier: Modifier ) { val context = LocalContext.current + val view = LocalView.current + Box( modifier = Modifier .fillMaxWidth() @@ -581,6 +528,7 @@ private fun GoalImagePicker( .align(Alignment.BottomEnd) .padding(end = 24.dp), onClick = { + view.weakHapticFeedback() photoPicker.launch( PickVisualMediaRequest( ActivityResultContracts.PickVisualMedia.ImageOnly @@ -628,8 +576,12 @@ private fun IconPickerCard( // To be used for onboarding tap target. modifier: Modifier = Modifier, ) { + val view = LocalView.current Card( - onClick = onClick, + onClick = { + view.weakHapticFeedback() + onClick() + }, modifier = Modifier.fillMaxWidth(0.86f), colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.primaryContainer @@ -727,7 +679,7 @@ private fun GoalReminderMenu( // To be used for onboarding tap target. modifier: Modifier = Modifier ) { - val haptic = LocalHapticFeedback.current + val view = LocalView.current var hasNotificationPermission by remember { mutableStateOf(context.hasNotificationPermission()) } val launcher = rememberLauncherForActivityResult( @@ -782,7 +734,7 @@ private fun GoalReminderMenu( Switch( checked = viewModel.state.reminder, onCheckedChange = { newValue -> - haptic.performHapticFeedback(HapticFeedbackType.LongPress) + view.weakHapticFeedback() viewModel.state = viewModel.state.copy(reminder = newValue) // Ask for notification permission if android ver > 13. if (newValue && 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 d6c71282..26cfd5b6 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 @@ -54,12 +54,13 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedCard import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -70,11 +71,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.starry.greenstash.MainActivity @@ -84,6 +87,7 @@ import com.starry.greenstash.ui.screens.home.composables.GoalItemClassic import com.starry.greenstash.ui.screens.home.composables.GoalItemCompact import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.utils.getActivity +import com.starry.greenstash.utils.weakHapticFeedback import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay @@ -91,12 +95,15 @@ import kotlinx.coroutines.delay @OptIn(ExperimentalMaterial3Api::class) @Composable fun GoalCardStyle(navController: NavController) { + val view = LocalView.current val context = navController.context + val settingsVM = (context.getActivity() as MainActivity).settingsViewModel val currentStyle = settingsVM.goalCardStyle.observeAsState().value!! + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() Scaffold(modifier = Modifier.fillMaxSize(), topBar = { - TopAppBar( + LargeTopAppBar( modifier = Modifier.fillMaxWidth(), title = { Text( @@ -106,12 +113,20 @@ fun GoalCardStyle(navController: NavController) { fontFamily = greenstashFont, ) }, navigationIcon = { - IconButton(onClick = { navController.navigateUp() }) { + IconButton(onClick = { + view.weakHapticFeedback() + navController.navigateUp() + }) { Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = null ) } - }) + }, scrollBehavior = scrollBehavior, colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + scrolledContainerColor = MaterialTheme.colorScheme.surface, + ) + ) }) { paddingValues -> Column( modifier = Modifier @@ -127,9 +142,10 @@ fun GoalCardStyle(navController: NavController) { ) { Text( text = "Preview", - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(start = 16.dp, top = 16.dp), - fontFamily = greenstashFont + modifier = Modifier.padding(start = 16.dp, top = 14.dp), + fontFamily = greenstashFont, + fontSize = 17.sp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f) ) AnimatedContent( @@ -227,10 +243,10 @@ fun GoalCardStyle(navController: NavController) { val showCompactTip = remember { mutableStateOf(false) } LaunchedEffect(key1 = currentStyle) { if (currentStyle == GoalCardStyle.Compact) { - delay(500) + delay(600) showCompactTip.value = true } else { - delay(600) + delay(700) showCompactTip.value = false } } diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsItem.kt b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsItem.kt index 97ff6a2d..1f692059 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsItem.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsItem.kt @@ -43,10 +43,10 @@ import androidx.compose.runtime.MutableState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp import com.starry.greenstash.ui.theme.greenstashFont +import com.starry.greenstash.utils.weakHapticFeedback @Composable fun SettingsItem(title: String, description: String, icon: ImageVector, onClick: () -> Unit) { @@ -96,7 +96,7 @@ fun SettingsItem( switchState: MutableState, onCheckChange: (Boolean) -> Unit, ) { - val haptic = LocalHapticFeedback.current + val view = LocalView.current Row( modifier = Modifier .fillMaxWidth() @@ -133,7 +133,7 @@ fun SettingsItem( Switch( checked = switchState.value, onCheckedChange = { - haptic.performHapticFeedback(HapticFeedbackType.LongPress) + view.weakHapticFeedback() onCheckChange(it) }, thumbContent = if (switchState.value) { diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsScreen.kt index 9bdf8803..7d26a58f 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsScreen.kt @@ -66,6 +66,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.semantics.Role @@ -88,12 +89,14 @@ import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.utils.Utils import com.starry.greenstash.utils.getActivity import com.starry.greenstash.utils.toToast +import com.starry.greenstash.utils.weakHapticFeedback import java.util.concurrent.Executor @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen(navController: NavController) { + val view = LocalView.current val context = LocalContext.current val viewModel = (context.getActivity() as MainActivity).settingsViewModel val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -109,7 +112,10 @@ fun SettingsScreen(navController: NavController) { fontFamily = greenstashFont ) }, navigationIcon = { - IconButton(onClick = { navController.navigateUp() }) { + IconButton(onClick = { + view.weakHapticFeedback() + navController.navigateUp() + }) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null diff --git a/app/src/main/java/com/starry/greenstash/utils/Extensions.kt b/app/src/main/java/com/starry/greenstash/utils/Extensions.kt index 7311b9a3..172944d5 100644 --- a/app/src/main/java/com/starry/greenstash/utils/Extensions.kt +++ b/app/src/main/java/com/starry/greenstash/utils/Extensions.kt @@ -30,6 +30,8 @@ import android.content.Context import android.content.ContextWrapper import android.content.pm.PackageManager import android.os.Build +import android.view.HapticFeedbackConstants +import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.lazy.LazyListState @@ -43,12 +45,20 @@ import androidx.core.content.ContextCompat import java.io.File import java.io.PrintWriter +/** + * Gets the activity from the context. + * @return the activity if the context is an instance of [AppCompatActivity], null otherwise. + */ fun Context.getActivity(): AppCompatActivity? = when (this) { is AppCompatActivity -> this is ContextWrapper -> baseContext.getActivity() else -> null } +/** + * Checks if the app has the notification permission. + * @return true if the app has the notification permission, false otherwise. + */ fun Context.hasNotificationPermission() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { ContextCompat.checkSelfPermission( @@ -58,6 +68,10 @@ fun Context.hasNotificationPermission() = true } +/** + * Checks if the lazy list is scrolling up. + * @return true if the list is scrolling up, false otherwise. + */ @Composable fun LazyListState.isScrollingUp(): Boolean { var previousIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) } @@ -83,11 +97,18 @@ fun String.toToast(context: Context, length: Int = Toast.LENGTH_SHORT) { Toast.makeText(context, this, length).show() } +/** + * Validates the amount. + * @return true if the amount is valid, false otherwise. + */ fun String.validateAmount() = this.isNotEmpty() && this.isNotBlank() && !this.matches("[0.]+".toRegex()) && !this.endsWith(".") +/** + * Clears the text content of the file. + */ fun File.clearText() { PrintWriter(this).also { it.print("") @@ -95,7 +116,24 @@ fun File.clearText() { } } +/** + * Updates the text content of the file. + */ fun File.updateText(content: String) { clearText() appendText(content) +} + +/** + * Performs a slight haptic feedback. + */ +fun View.weakHapticFeedback() { + this.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) +} + +/** + * Performs a strong haptic feedback. + */ +fun View.strongHapticFeedback() { + this.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) } \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e898ceea..ce83a1b4 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -100,7 +100,7 @@ ¡Copiado correctamente! Sin transacciones aún. Actualizar Transacción - Consejo: Desliza las transacciones hacia la izquierda o hacia la derecha para editarlas o eliminarlas. + Consejo: Desliza las transacciones hacia la izquierda o hacia la derecha para editarlas o eliminarlas. ¡Es momento de ahorrar! diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index b98c22a7..7c1462d1 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -100,7 +100,7 @@ Başarıyla Kopyalandı! Henüz İşlem Yok. İşlemi Güncelle - İpucu: İşlemleri düzenlemek veya silmek için sola veya sağa kaydırın. + İpucu: İşlemleri düzenlemek veya silmek için sola veya sağa kaydırın. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 34329c42..2b66a545 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -100,7 +100,7 @@ 复制成功! 尚无收支。 更新交易 - 提示:左右滑动交易以编辑或删除它们。 + 提示:左右滑动交易以编辑或删除它们。 省钱时间到! diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 80f4eeea..c2afc839 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -100,7 +100,7 @@ 成功複製! 尚無交易。 更新交易 - 提示:向左或向右滑動交易以編輯或刪除它們。 + 提示:向左或向右滑動交易以編輯或刪除它們。 該存錢的時候了! diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7e2261fa..9ef12fc1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -100,7 +100,7 @@ Copied Successfully! No Transactions Yet. Update Transaction - Tip: Swipe transactions left or right to edit or delete them. + Tip! Swipe transactions left or right to edit or delete them. It\'s time to save! @@ -127,7 +127,7 @@ Great! Saving goal set. Saving goal updated. Do you want to remove deadline from this goal? - Tip: You can remove the deadline from existing goals by long-pressing on the deadline field. + Tip! You can remove the deadline from existing goals by long-pressing on the deadline field. Backup & Restore