From 19b309db94fdcf5cbdcb074d56ac334797ad8a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C9=91rry=20Shiv=C9=91m?= Date: Tue, 7 May 2024 12:51:23 +0530 Subject: [PATCH] Improve navigation drawer UI (#118) Changes: * Redesign navigation drawer. * Improved search bar design. * Improved UI of transaction type selection in edit-transaction sheet. * Enabled edge-to-edge mode and removed deprecated systemui-controller lib. --------- Signed-off-by: starry-shivam --- .idea/deploymentTargetSelector.xml | 8 + app/build.gradle | 4 +- .../com/starry/greenstash/MainActivity.kt | 19 +- .../screens/home/composables/HomeAppBars.kt | 24 +- .../ui/screens/home/composables/HomeDrawer.kt | 204 +++++++++++-- .../ui/screens/home/composables/HomeScreen.kt | 20 +- .../info/composables/EditTransactionSheet.kt | 76 +++-- .../screens/input/composables/InputScreen.kt | 16 +- .../settings/composables/AboutScreen.kt | 24 +- .../settings/composables/GoalCardStyle.kt | 2 +- .../settings/composables/SettingsScreen.kt | 270 ++++++------------ .../com/starry/greenstash/ui/theme/Theme.kt | 30 ++ .../java/com/starry/greenstash/utils/Utils.kt | 73 +++-- .../configuration/WidgetConfigActivity.kt | 19 +- app/src/main/res/drawable/ic_nav_backups.xml | 5 +- app/src/main/res/drawable/ic_nav_privacy.xml | 5 + app/src/main/res/drawable/ic_nav_rating.xml | 14 + app/src/main/res/drawable/ic_nav_share.xml | 17 ++ .../main/res/drawable/ic_settings_about.xml | 9 - .../res/drawable/ic_settings_app_lock.xml | 9 - .../res/drawable/ic_settings_material_you.xml | 9 - app/src/main/res/drawable/ic_settings_osl.xml | 9 - .../res/drawable/ic_settings_sort_order.xml | 11 - .../main/res/drawable/ic_settings_theme.xml | 9 - app/src/main/res/values-es/strings.xml | 11 + app/src/main/res/values-ru/strings.xml | 11 + app/src/main/res/values-tr/strings.xml | 11 + app/src/main/res/values-zh-rCN/strings.xml | 10 + app/src/main/res/values-zh-rTW/strings.xml | 9 + app/src/main/res/values/strings.xml | 10 + 30 files changed, 577 insertions(+), 371 deletions(-) create mode 100644 app/src/main/res/drawable/ic_nav_privacy.xml create mode 100644 app/src/main/res/drawable/ic_nav_rating.xml create mode 100644 app/src/main/res/drawable/ic_nav_share.xml delete mode 100644 app/src/main/res/drawable/ic_settings_about.xml delete mode 100644 app/src/main/res/drawable/ic_settings_app_lock.xml delete mode 100644 app/src/main/res/drawable/ic_settings_material_you.xml delete mode 100644 app/src/main/res/drawable/ic_settings_osl.xml delete mode 100644 app/src/main/res/drawable/ic_settings_sort_order.xml delete mode 100644 app/src/main/res/drawable/ic_settings_theme.xml diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index b268ef36..495ef5f9 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,6 +4,14 @@ diff --git a/app/build.gradle b/app/build.gradle index 25f74eca..82726210 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,7 +17,7 @@ android { minSdk 24 targetSdk 34 versionCode 350 - versionName "3.5.0" + versionName "3.6.0-dev" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -94,8 +94,6 @@ dependencies { implementation "androidx.compose.animation:animation" implementation "androidx.compose.runtime:runtime-livedata" implementation "androidx.compose.material3:material3" - // Accompanist compose. - implementation "com.google.accompanist:accompanist-systemuicontroller:0.28.0" // Material theme for main activity. implementation 'com.google.android.material:material:1.11.0' // Android 12+ splash API. diff --git a/app/src/main/java/com/starry/greenstash/MainActivity.kt b/app/src/main/java/com/starry/greenstash/MainActivity.kt index 39e8cab8..69887670 100644 --- a/app/src/main/java/com/starry/greenstash/MainActivity.kt +++ b/app/src/main/java/com/starry/greenstash/MainActivity.kt @@ -27,6 +27,7 @@ package com.starry.greenstash import android.os.Bundle import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt @@ -43,11 +44,10 @@ import androidx.core.content.ContextCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.ViewModelProvider import androidx.navigation.compose.rememberNavController -import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.starry.greenstash.ui.navigation.NavGraph import com.starry.greenstash.ui.screens.other.AppLockedScreen import com.starry.greenstash.ui.screens.settings.SettingsViewModel -import com.starry.greenstash.ui.screens.settings.ThemeMode +import com.starry.greenstash.ui.theme.AdjustEdgeToEdge import com.starry.greenstash.ui.theme.GreenStashTheme import com.starry.greenstash.utils.Utils import com.starry.greenstash.utils.toToast @@ -75,6 +75,8 @@ class MainActivity : AppCompatActivity() { mainViewModel.isLoading.value } + enableEdgeToEdge() // enable edge to edge for the activity. + // refresh reminders mainViewModel.refreshReminders() @@ -137,15 +139,10 @@ class MainActivity : AppCompatActivity() { private fun setAppContents(showAppContents: State) { setContent { GreenStashTheme(settingsViewModel = settingsViewModel) { - val systemUiController = rememberSystemUiController() - systemUiController.setNavigationBarColor( - color = MaterialTheme.colorScheme.background, - darkIcons = settingsViewModel.getCurrentTheme() == ThemeMode.Light - ) - - systemUiController.setStatusBarColor( - color = MaterialTheme.colorScheme.surface, - darkIcons = settingsViewModel.getCurrentTheme() == ThemeMode.Light + // fix status bar icon color in dark mode. + AdjustEdgeToEdge( + activity = this, + themeState = settingsViewModel.getCurrentTheme() ) Surface( 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 fcaabd68..b6d63abe 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 @@ -27,9 +27,12 @@ package com.starry.greenstash.ui.screens.home.composables import androidx.compose.animation.Crossfade import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions @@ -42,9 +45,9 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -161,7 +164,10 @@ private fun SearchAppBar( onSearchClicked: (String) -> Unit, ) { Surface( - modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.surface + modifier = Modifier + .fillMaxWidth() + .windowInsetsPadding(insets = WindowInsets.statusBars), + color = MaterialTheme.colorScheme.surface ) { OutlinedTextField( modifier = Modifier @@ -182,7 +188,7 @@ private fun SearchAppBar( Icon( imageVector = Icons.Default.Search, contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) ) } }, @@ -197,7 +203,7 @@ private fun SearchAppBar( Icon( imageVector = Icons.Filled.Close, contentDescription = null, - tint = MaterialTheme.colorScheme.onSurface + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.5f) ) } }, @@ -207,11 +213,13 @@ private fun SearchAppBar( keyboardActions = KeyboardActions(onSearch = { onSearchClicked(text) }), - colors = TextFieldDefaults.colors( - focusedContainerColor = Color.Transparent, - unfocusedContainerColor = MaterialTheme.colorScheme.primaryContainer.copy(0.3f), - disabledContainerColor = Color.Transparent, + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer, cursorColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f), + focusedTextColor = MaterialTheme.colorScheme.onSurface, + focusedBorderColor = Color.Transparent, + unfocusedBorderColor = Color.Transparent ), shape = RoundedCornerShape(24.dp) ) 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 b75c5438..c39e77cb 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 @@ -25,14 +25,24 @@ package com.starry.greenstash.ui.screens.home.composables +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize 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.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.DrawerState +import androidx.compose.material3.DrawerValue import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -40,6 +50,7 @@ import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.NavigationDrawerItem import androidx.compose.material3.NavigationDrawerItemDefaults import androidx.compose.material3.Text +import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -47,49 +58,80 @@ 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.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 +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 coil.compose.AsyncImage import com.starry.greenstash.R import com.starry.greenstash.ui.navigation.DrawerScreens +import com.starry.greenstash.ui.screens.settings.ThemeMode +import com.starry.greenstash.ui.screens.settings.composables.AboutLinks 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) { +fun HomeDrawer(drawerState: DrawerState, navController: NavController, themeMode: ThemeMode) { val items = listOf(DrawerScreens.Home, DrawerScreens.Backups, DrawerScreens.Settings) val selectedItem = remember { mutableStateOf(items[0]) } val view = LocalView.current + val context = LocalContext.current val coroutineScope = rememberCoroutineScope() ModalDrawerSheet( - modifier = Modifier.width(280.dp), - drawerShape = RoundedCornerShape(4.dp) + modifier = Modifier.width(295.dp), + drawerShape = RoundedCornerShape(topEnd = 14.dp, bottomEnd = 14.dp), + drawerTonalElevation = 2.dp, ) { - Spacer(Modifier.height(14.dp)) - - Text( - text = Utils.getGreeting(), - modifier = Modifier.padding(start = 16.dp, top = 12.dp), - fontSize = 24.sp, - fontWeight = FontWeight.SemiBold, - fontFamily = greenstashFont, - color = MaterialTheme.colorScheme.onSurface - ) + Row( + modifier = Modifier + .height(140.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(modifier = Modifier.width(20.dp)) + Box( + modifier = Modifier + .size(60.dp) + .background( + color = if (themeMode == ThemeMode.Light) MaterialTheme.colorScheme.onSurface + else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.15f), + shape = CircleShape + ) + ) { + AsyncImage( + model = R.drawable.ic_launcher_foreground, + contentDescription = stringResource(id = R.string.app_name), + modifier = Modifier.fillMaxSize(), + ) + } + + Spacer(modifier = Modifier.width(18.dp)) + Text( + text = stringResource(id = R.string.app_name), + fontFamily = greenstashFont, + fontSize = 22.sp, + fontWeight = FontWeight.Bold + ) + } + HorizontalDivider( modifier = Modifier .fillMaxWidth() - .padding(top = 16.dp, bottom = 16.dp), + .padding(bottom = 16.dp), thickness = 0.5.dp, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f) + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f) ) items.forEach { item -> @@ -102,8 +144,7 @@ fun HomeDrawer(drawerState: DrawerState, navController: NavController) { }, label = { Text( - text = stringResource(id = item.nameResId), - fontFamily = greenstashFont + text = stringResource(id = item.nameResId), fontFamily = greenstashFont ) }, selected = item == selectedItem.value, @@ -122,17 +163,142 @@ fun HomeDrawer(drawerState: DrawerState, navController: NavController) { Spacer(modifier = Modifier.height(4.dp)) } + HorizontalDivider( + modifier = Modifier + .fillMaxWidth() + .padding(top = 14.dp, bottom = 14.dp), + thickness = 0.5.dp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f) + ) + + // Non-navigational items in the drawer ======================================== + + NavigationDrawerItem( + modifier = Modifier + .width(280.dp) + .padding(NavigationDrawerItemDefaults.ItemPadding), + selected = false, + onClick = { + view.weakHapticFeedback() + onRatingClick(context) + }, + icon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_nav_rating), + contentDescription = null + ) + }, + label = { + Text( + text = stringResource(id = R.string.drawer_rating), + fontFamily = greenstashFont + ) + }, + ) + + NavigationDrawerItem( + modifier = Modifier + .width(280.dp) + .padding(NavigationDrawerItemDefaults.ItemPadding), + selected = false, + onClick = { + view.weakHapticFeedback() + onShareClick(context) + }, + icon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_nav_share), + contentDescription = null + ) + }, + label = { + Text( + text = stringResource(id = R.string.drawer_share), + fontFamily = greenstashFont + ) + }, + ) + + NavigationDrawerItem( + modifier = Modifier + .width(280.dp) + .padding(NavigationDrawerItemDefaults.ItemPadding), + selected = false, + onClick = { + view.weakHapticFeedback() + onPrivacyClick(context) + }, + icon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_nav_privacy), + contentDescription = null + ) + }, + label = { + Text( + text = stringResource(id = R.string.drawer_privacy), + fontFamily = greenstashFont + ) + }, + ) + 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, + fontSize = 11.sp, fontFamily = greenstashFont, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f) + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.69f) ) } } +} + +private fun onRatingClick(context: Context) { + try { + context.startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse("market://details?id=${context.packageName}") + ) + ) + } catch (e: ActivityNotFoundException) { + Utils.openWebLink( + context = context, + url = "https://play.google.com/store/apps/details?id=${context.packageName}" + ) + } +} + +private fun onShareClick(context: Context) { + val shareMessage = + context.getString( + R.string.drawer_share_message, + "https://play.google.com/store/apps/details?id=${context.packageName}" + ).trimIndent() + val shareIntent = Intent(Intent.ACTION_SEND).apply { + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, shareMessage) + } + context.startActivity(Intent.createChooser(shareIntent, null)) +} + +private fun onPrivacyClick(context: Context) { + Utils.openWebLink( + context = context, + url = AboutLinks.PrivacyPolicy.url + ) +} + +@Preview +@Composable +private fun HomeDrawerPV() { + HomeDrawer( + drawerState = rememberDrawerState(initialValue = DrawerValue.Open), + navController = rememberNavController(), + themeMode = ThemeMode.Light + ) } \ No newline at end of file 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 f67940fa..5825fe04 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 @@ -77,10 +77,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -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 @@ -100,6 +98,7 @@ import com.airbnb.lottie.compose.rememberLottieComposition import com.psoffritti.taptargetcompose.TapTargetCoordinator import com.psoffritti.taptargetcompose.TapTargetStyle import com.psoffritti.taptargetcompose.TextDefinition +import com.starry.greenstash.MainActivity import com.starry.greenstash.R import com.starry.greenstash.database.core.GoalWithTransactions import com.starry.greenstash.ui.navigation.Screens @@ -108,6 +107,7 @@ import com.starry.greenstash.ui.screens.home.FilterSortType 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.getActivity import com.starry.greenstash.utils.isScrollingUp import com.starry.greenstash.utils.weakHapticFeedback import kotlinx.coroutines.delay @@ -118,19 +118,19 @@ import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @Composable fun HomeScreen(navController: NavController) { + val context = LocalContext.current val viewModel: HomeViewModel = hiltViewModel() - val allGoalState = viewModel.goalsList.observeAsState(emptyList()) + val settingsVM = (context.getActivity() as MainActivity).settingsViewModel + val allGoalState = viewModel.goalsList.observeAsState(emptyList()) val showFilterSheet = remember { mutableStateOf(false) } val filterSheetState = rememberModalBottomSheetState() - val drawerState = rememberDrawerState(DrawerValue.Closed) val searchWidgetState by viewModel.searchWidgetState val searchTextState by viewModel.searchTextState val lazyListState = rememberLazyListState() - val snackBarHostState = remember { SnackbarHostState() } val coroutineScope = rememberCoroutineScope() @@ -153,7 +153,11 @@ fun HomeScreen(navController: NavController) { drawerState = drawerState, gesturesEnabled = drawerState.isOpen, drawerContent = { - HomeDrawer(drawerState = drawerState, navController = navController) + HomeDrawer( + drawerState = drawerState, + navController = navController, + themeMode = settingsVM.getCurrentTheme() + ) }, ) { val showTapTargets = remember { mutableStateOf(false) } @@ -469,7 +473,7 @@ private fun FilterButton(text: String, isSelected: Boolean, onClick: () -> Unit) textColor = MaterialTheme.colorScheme.onSecondaryContainer } - val haptic = LocalHapticFeedback.current + val view = LocalView.current Card( modifier = Modifier @@ -478,7 +482,7 @@ private fun FilterButton(text: String, isSelected: Boolean, onClick: () -> Unit) colors = CardDefaults.cardColors(containerColor = buttonColor), shape = RoundedCornerShape(14.dp), onClick = { - haptic.performHapticFeedback(HapticFeedbackType.LongPress) + view.weakHapticFeedback() onClick() } ) { diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/EditTransactionSheet.kt b/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/EditTransactionSheet.kt index ce94b4f6..1f0e0a67 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/EditTransactionSheet.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/EditTransactionSheet.kt @@ -25,25 +25,26 @@ package com.starry.greenstash.ui.screens.info.composables -import androidx.compose.foundation.layout.Arrangement 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.selection.selectableGroup +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.OutlinedCard import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.RadioButton +import androidx.compose.material3.SegmentedButton +import androidx.compose.material3.SegmentedButtonDefaults +import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -52,7 +53,6 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember 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.LocalContext @@ -132,39 +132,59 @@ fun EditTransactionSheet( .fillMaxWidth() .padding(8.dp) ) { - OutlinedCard( + SingleChoiceSegmentedButtonRow( modifier = Modifier .fillMaxWidth() .padding(horizontal = 18.dp, vertical = 6.dp) ) { - Row( - modifier = Modifier - .fillMaxWidth() - .selectableGroup(), - horizontalArrangement = Arrangement.SpaceEvenly, - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - RadioButton( - selected = selectedTransactionType == TransactionType.Deposit.name, - onClick = { onTransactionTypeSelected(TransactionType.Deposit.name) }, - ) + SegmentedButton( + selected = selectedTransactionType == TransactionType.Deposit.name, + onClick = { onTransactionTypeSelected(TransactionType.Deposit.name) }, + shape = RoundedCornerShape(topStart = 14.dp, bottomStart = 14.dp), + label = { Text( - text = TransactionType.Deposit.name, - fontFamily = greenstashFont + text = TransactionType.Deposit.name, fontFamily = greenstashFont ) - } + }, + icon = { + if (selectedTransactionType == TransactionType.Deposit.name) { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + } + }, + colors = SegmentedButtonDefaults.colors( + activeContentColor = MaterialTheme.colorScheme.onPrimary, + activeContainerColor = MaterialTheme.colorScheme.primary, + ) + ) - Row(verticalAlignment = Alignment.CenterVertically) { - RadioButton( - selected = selectedTransactionType == TransactionType.Withdraw.name, - onClick = { onTransactionTypeSelected(TransactionType.Withdraw.name) }, - ) + SegmentedButton( + selected = selectedTransactionType == TransactionType.Withdraw.name, + onClick = { onTransactionTypeSelected(TransactionType.Withdraw.name) }, + shape = RoundedCornerShape(topEnd = 14.dp, bottomEnd = 14.dp), + label = { Text( text = TransactionType.Withdraw.name, fontFamily = greenstashFont ) - } - } + }, + icon = { + if (selectedTransactionType == TransactionType.Withdraw.name) { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + } + }, + colors = SegmentedButtonDefaults.colors( + activeContentColor = MaterialTheme.colorScheme.onPrimary, + activeContainerColor = MaterialTheme.colorScheme.primary, + ) + ) } DateTimeCard( 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 1594b255..5615665b 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 @@ -375,7 +375,7 @@ fun InputScreen(editGoalId: String?, navController: NavController) { GoalPriorityMenu( selectedPriority = viewModel.state.priority, - obPriorityChanged = { newValue -> + onPriorityChanged = { newValue -> viewModel.updatePriority(newValue) } ) @@ -630,7 +630,7 @@ private fun IconPickerCard( @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun GoalPriorityMenu(selectedPriority: String, obPriorityChanged: (String) -> Unit) { +private fun GoalPriorityMenu(selectedPriority: String, onPriorityChanged: (String) -> Unit) { Card( modifier = Modifier.fillMaxWidth(0.86f), colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.primaryContainer @@ -658,7 +658,7 @@ private fun GoalPriorityMenu(selectedPriority: String, obPriorityChanged: (Strin ) { SegmentedButton( selected = selectedPriority == GoalPriority.High.name, - onClick = { obPriorityChanged(GoalPriority.High.name) }, + onClick = { onPriorityChanged(GoalPriority.High.name) }, shape = RoundedCornerShape(topStart = 14.dp, bottomStart = 14.dp), label = { Text( @@ -677,12 +677,14 @@ private fun GoalPriorityMenu(selectedPriority: String, obPriorityChanged: (Strin colors = SegmentedButtonDefaults.colors( activeContentColor = MaterialTheme.colorScheme.onPrimary, activeContainerColor = MaterialTheme.colorScheme.primary, + inactiveContainerColor = MaterialTheme.colorScheme.primaryContainer, + inactiveContentColor = MaterialTheme.colorScheme.onPrimaryContainer ) ) SegmentedButton( selected = selectedPriority == GoalPriority.Normal.name, - onClick = { obPriorityChanged(GoalPriority.Normal.name) }, + onClick = { onPriorityChanged(GoalPriority.Normal.name) }, shape = RectangleShape, label = { Text( @@ -701,12 +703,14 @@ private fun GoalPriorityMenu(selectedPriority: String, obPriorityChanged: (Strin colors = SegmentedButtonDefaults.colors( activeContentColor = MaterialTheme.colorScheme.onPrimary, activeContainerColor = MaterialTheme.colorScheme.primary, + inactiveContainerColor = MaterialTheme.colorScheme.primaryContainer, + inactiveContentColor = MaterialTheme.colorScheme.onPrimaryContainer ) ) SegmentedButton( selected = selectedPriority == GoalPriority.Low.name, - onClick = { obPriorityChanged(GoalPriority.Low.name) }, + onClick = { onPriorityChanged(GoalPriority.Low.name) }, shape = RoundedCornerShape(topEnd = 14.dp, bottomEnd = 14.dp), label = { Text( @@ -725,6 +729,8 @@ private fun GoalPriorityMenu(selectedPriority: String, obPriorityChanged: (Strin colors = SegmentedButtonDefaults.colors( activeContentColor = MaterialTheme.colorScheme.onPrimary, activeContainerColor = MaterialTheme.colorScheme.primary, + inactiveContainerColor = MaterialTheme.colorScheme.primaryContainer, + inactiveContentColor = MaterialTheme.colorScheme.onPrimaryContainer ) ) } diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/AboutScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/AboutScreen.kt index 921a73f8..956833af 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/AboutScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/AboutScreen.kt @@ -25,10 +25,6 @@ package com.starry.greenstash.ui.screens.settings.composables -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent -import android.net.Uri import android.os.Build import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -60,6 +56,7 @@ import androidx.navigation.NavController import com.starry.greenstash.BuildConfig import com.starry.greenstash.R import com.starry.greenstash.ui.theme.greenstashFont +import com.starry.greenstash.utils.Utils sealed class AboutLinks(val url: String) { data object ReadMe : AboutLinks("https://github.com/Pool-Of-Tears/GreenStash") @@ -109,35 +106,35 @@ fun AboutScreen(navController: NavController) { SettingsItem(title = stringResource(id = R.string.about_readme_title), description = stringResource(id = R.string.about_readme_desc), icon = Icons.AutoMirrored.Filled.Notes, - onClick = { openWebLink(context, AboutLinks.ReadMe.url) } + onClick = { Utils.openWebLink(context, AboutLinks.ReadMe.url) } ) } item { SettingsItem(title = stringResource(id = R.string.about_privacy_title), description = stringResource(id = R.string.about_privacy_desc), icon = Icons.Filled.PrivacyTip, - onClick = { openWebLink(context, AboutLinks.PrivacyPolicy.url) } + onClick = { Utils.openWebLink(context, AboutLinks.PrivacyPolicy.url) } ) } item { SettingsItem(title = stringResource(id = R.string.about_gh_issue_title), description = stringResource(id = R.string.about_gh_issue_desc), icon = ImageVector.vectorResource(id = R.drawable.ic_about_gh_issue), - onClick = { openWebLink(context, AboutLinks.GithubIssues.url) } + onClick = { Utils.openWebLink(context, AboutLinks.GithubIssues.url) } ) } item { SettingsItem(title = stringResource(id = R.string.about_telegram_title), description = stringResource(id = R.string.about_telegram_desc), icon = ImageVector.vectorResource(id = R.drawable.ic_about_telegram), - onClick = { openWebLink(context, AboutLinks.Telegram.url) } + onClick = { Utils.openWebLink(context, AboutLinks.Telegram.url) } ) } item { SettingsItem(title = stringResource(id = R.string.about_support_title), description = stringResource(id = R.string.about_support_desc), icon = Icons.Filled.Favorite, - onClick = { openWebLink(context, AboutLinks.Sponsor.url) } + onClick = { Utils.openWebLink(context, AboutLinks.Sponsor.url) } ) } item { @@ -153,15 +150,6 @@ fun AboutScreen(navController: NavController) { } } -fun openWebLink(context: Context, url: String) { - val uri: Uri = Uri.parse(url) - val intent = Intent(Intent.ACTION_VIEW, uri) - try { - context.startActivity(intent) - } catch (exc: ActivityNotFoundException) { - exc.printStackTrace() - } -} fun getVersionReport(): String { val versionName = BuildConfig.VERSION_NAME 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 26cfd5b6..77e173f5 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 @@ -174,7 +174,7 @@ fun GoalCardStyle(navController: NavController) { savedAmount = "$1,000.00", daysLeftText = "12 days left", goalProgress = 0.8f, - goalIcon = ImageVector.vectorResource(id = R.drawable.ic_nav_backups), + goalIcon = ImageVector.vectorResource(id = R.drawable.ic_nav_rating), onDepositClicked = {}, onWithdrawClicked = {}, onInfoClicked = {}, 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 385bf297..4ccbf1b7 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 @@ -34,14 +34,17 @@ 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.lazy.LazyColumn import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectableGroup -import androidx.compose.foundation.shape.RoundedCornerShape 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.BrightnessMedium +import androidx.compose.material.icons.filled.Contrast +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.LocalPolice +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.Palette import androidx.compose.material.icons.filled.Style import androidx.compose.material3.AlertDialog import androidx.compose.material3.ButtonDefaults @@ -53,23 +56,17 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButtonDefaults import androidx.compose.material3.Scaffold -import androidx.compose.material3.SheetState -import androidx.compose.material3.Switch -import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -100,10 +97,6 @@ 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 kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.util.concurrent.Executor @@ -158,11 +151,9 @@ fun SettingsScreen(navController: NavController) { } } -@OptIn(ExperimentalMaterial3Api::class) @Composable private fun DisplaySettings(viewModel: SettingsViewModel, navController: NavController) { val context = LocalContext.current - val sheetState = rememberModalBottomSheetState() val showThemeSheet = remember { mutableStateOf(false) } // Theme related values. @@ -194,14 +185,25 @@ private fun DisplaySettings(viewModel: SettingsViewModel, navController: NavCont SettingsCategory(title = stringResource(id = R.string.display_settings_title)) SettingsItem(title = stringResource(id = R.string.theme_setting), description = themeValue, - icon = ImageVector.vectorResource(id = R.drawable.ic_settings_theme), + icon = Icons.Filled.BrightnessMedium, onClick = { showThemeSheet.value = true }) + SettingsItem( + title = stringResource(id = R.string.amoled_theme_setting), + description = stringResource(id = R.string.amoled_theme_desc), + icon = Icons.Filled.Contrast, + switchState = amoledThemeValue, + onCheckChange = { newValue -> + amoledThemeValue.value = newValue + viewModel.setAmoledTheme(newValue) + } + ) + SettingsItem(title = stringResource(id = R.string.material_you_setting), description = stringResource( id = R.string.material_you_setting_desc ), - icon = ImageVector.vectorResource(id = R.drawable.ic_settings_material_you), + icon = Icons.Filled.Palette, switchState = materialYouValue, onCheckChange = { newValue -> materialYouValue.value = newValue @@ -224,36 +226,24 @@ private fun DisplaySettings(viewModel: SettingsViewModel, navController: NavCont onClick = { navController.navigate(Screens.GoalCardStyle.route) }) if (showThemeSheet.value) { - ThemeBottomSheet( + ThemePickerDialog( themeValue = themeValue, - amoledThemeValue = amoledThemeValue.value, - sheetState = sheetState, - showThemeSheet = showThemeSheet, + showThemeDialog = showThemeSheet, onThemeChange = { newTheme -> viewModel.setTheme(newTheme) - }, - onAmoledThemeChange = { newValue -> - amoledThemeValue.value = newValue - viewModel.setAmoledTheme(newValue) } ) } } } -@OptIn(ExperimentalMaterial3Api::class) @Composable -private fun ThemeBottomSheet( +private fun ThemePickerDialog( themeValue: String, - amoledThemeValue: Boolean, - sheetState: SheetState, - showThemeSheet: MutableState, + showThemeDialog: MutableState, onThemeChange: (ThemeMode) -> Unit, - onAmoledThemeChange: (Boolean) -> Unit, ) { - val view = LocalView.current val context = LocalContext.current - val coroutineScope = rememberCoroutineScope() val themeRadioOptions = listOf( stringResource(id = R.string.theme_dialog_option1), stringResource(id = R.string.theme_dialog_option2), @@ -263,161 +253,75 @@ private fun ThemeBottomSheet( mutableStateOf(themeValue) } - ModalBottomSheet(sheetState = sheetState, onDismissRequest = { - coroutineScope.launch { - sheetState.hide() - delay(300) - showThemeSheet.value = false - } - }) { - Column( - modifier = Modifier.fillMaxWidth() - - ) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 14.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp) - ), - shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) + if (showThemeDialog.value) { + AlertDialog(onDismissRequest = { + showThemeDialog.value = false + }, title = { + Text( + text = stringResource(id = R.string.theme_dialog_title), + color = MaterialTheme.colorScheme.onSurface, + ) + }, text = { + Column( + modifier = Modifier.selectableGroup(), + verticalArrangement = Arrangement.Center, ) { - Column( - modifier = Modifier - .selectableGroup() - .padding(top = 6.dp), - verticalArrangement = Arrangement.Center, - ) { - themeRadioOptions.forEach { text -> - Row( - modifier = Modifier - .fillMaxWidth() - .height(46.dp) - .selectable( - selected = (text == selectedThemeOption), - onClick = { onThemeOptionSelected(text) }, - role = Role.RadioButton, - ) - .padding(start = 14.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - RadioButton( + themeRadioOptions.forEach { text -> + Row( + modifier = Modifier + .fillMaxWidth() + .height(46.dp) + .selectable( selected = (text == selectedThemeOption), - onClick = null, - colors = RadioButtonDefaults.colors( - selectedColor = MaterialTheme.colorScheme.primary, - unselectedColor = MaterialTheme.colorScheme.inversePrimary, - disabledSelectedColor = Color.Black, - disabledUnselectedColor = Color.Black - ), - ) - Text( - text = text, - modifier = Modifier.padding(start = 16.dp), - color = MaterialTheme.colorScheme.onSurface, - fontFamily = greenstashFont, - ) - } + onClick = { onThemeOptionSelected(text) }, + role = Role.RadioButton, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + RadioButton( + selected = (text == selectedThemeOption), + onClick = null, + colors = RadioButtonDefaults.colors( + selectedColor = MaterialTheme.colorScheme.primary, + unselectedColor = MaterialTheme.colorScheme.inversePrimary, + disabledSelectedColor = Color.Black, + disabledUnselectedColor = Color.Black + ), + ) + Text( + text = text, + modifier = Modifier.padding(start = 16.dp), + color = MaterialTheme.colorScheme.onSurface, + fontFamily = greenstashFont + ) } } } - - Card( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 14.dp, vertical = 4.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp) - ), - shape = RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(4.dp) - ) { - Text( - text = stringResource(id = R.string.amoled_theme_setting), - fontFamily = greenstashFont, - modifier = Modifier.padding(start = 14.dp, top = 10.dp) - ) - Spacer(modifier = Modifier.weight(1f)) - Switch( - checked = amoledThemeValue, - onCheckedChange = { - view.weakHapticFeedback() - onAmoledThemeChange(it) - }, - thumbContent = if (amoledThemeValue) { - { - Icon( - imageVector = Icons.Filled.Check, - contentDescription = null, - modifier = Modifier.size(SwitchDefaults.IconSize), - ) - } - } else { - null - }, - modifier = Modifier.padding(end = 14.dp) - ) - } - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp, bottom = 20.dp, end = 14.dp) - ) { - Spacer(modifier = Modifier.weight(1f)) - TextButton(onClick = { - coroutineScope.launch { - sheetState.hide() - delay(300) - showThemeSheet.value = false - } - }) { - Text( - stringResource(id = R.string.cancel), fontFamily = greenstashFont - ) - } - - FilledTonalButton( - onClick = { - coroutineScope.launch { - sheetState.hide() - delay(100) - showThemeSheet.value = false - - withContext(Dispatchers.Main) { - when (selectedThemeOption) { - context.getString(R.string.theme_dialog_option1) -> { - onThemeChange(ThemeMode.Light) - } - - context.getString(R.string.theme_dialog_option2) -> { - onThemeChange(ThemeMode.Dark) - } - - context.getString(R.string.theme_dialog_option3) -> { - onThemeChange(ThemeMode.Auto) - } - } - } + }, confirmButton = { + FilledTonalButton( + onClick = { + showThemeDialog.value = false + onThemeChange( + when (selectedThemeOption) { + context.getString(R.string.theme_dialog_option1) -> ThemeMode.Light + context.getString(R.string.theme_dialog_option2) -> ThemeMode.Dark + else -> ThemeMode.Auto } - }, colors = ButtonDefaults.filledTonalButtonColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer - ), shape = RoundedCornerShape(16.dp) - ) { - Text( - stringResource(id = R.string.theme_dialog_apply_button), - fontFamily = greenstashFont ) - } + }, colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Text(stringResource(id = R.string.theme_dialog_apply_button)) } - } + }, dismissButton = { + TextButton(onClick = { + showThemeDialog.value = false + }) { + Text(stringResource(id = R.string.cancel)) + } + }) } } @@ -564,7 +468,7 @@ private fun SecuritySettings(viewModel: SettingsViewModel) { SettingsCategory(title = stringResource(id = R.string.security_settings_title)) SettingsItem(title = stringResource(id = R.string.app_lock_setting), description = stringResource(id = R.string.app_lock_setting_desc), - icon = ImageVector.vectorResource(id = R.drawable.ic_settings_app_lock), + icon = Icons.Filled.Lock, switchState = appLockSwitch, onCheckChange = { newValue -> appLockSwitch.value = newValue @@ -620,11 +524,11 @@ private fun MiscSettings(navController: NavController) { SettingsCategory(title = stringResource(id = R.string.misc_setting_title)) SettingsItem(title = stringResource(id = R.string.license_setting), description = stringResource(id = R.string.license_setting_desc), - icon = ImageVector.vectorResource(id = R.drawable.ic_settings_osl), + icon = Icons.Filled.LocalPolice, onClick = { navController.navigate(Screens.OSLScreen.route) }) SettingsItem(title = stringResource(id = R.string.app_info_setting), description = stringResource(id = R.string.app_info_setting_desc), - icon = ImageVector.vectorResource(id = R.drawable.ic_settings_about), + icon = Icons.Filled.Info, onClick = { navController.navigate(Screens.AboutScreen.route) }) } Spacer(modifier = Modifier.height(2.dp)) // Last item padding. diff --git a/app/src/main/java/com/starry/greenstash/ui/theme/Theme.kt b/app/src/main/java/com/starry/greenstash/ui/theme/Theme.kt index 360ac07c..a3f0ab41 100644 --- a/app/src/main/java/com/starry/greenstash/ui/theme/Theme.kt +++ b/app/src/main/java/com/starry/greenstash/ui/theme/Theme.kt @@ -27,6 +27,9 @@ package com.starry.greenstash.ui.theme import android.content.Context import android.os.Build +import androidx.activity.SystemBarStyle +import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme @@ -35,6 +38,7 @@ import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -155,6 +159,32 @@ private fun getColorScheme( } } +/** + * Helper composable function to fix the status bar icons on dark theme + * when using edge-to-edge mode. + * @param activity: MainActivity to enable edge-to-edge status bar. + * @param themeState: ThemeMode to check the current theme. + */ +@Composable +fun AdjustEdgeToEdge(activity: AppCompatActivity, themeState: ThemeMode) { + LaunchedEffect(themeState) { + if (themeState == ThemeMode.Dark) { + activity.enableEdgeToEdge( + statusBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT), + navigationBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT) + ) + } else { + activity.enableEdgeToEdge() + } + } +} + +/** + * GreenStashTheme composable function to apply the theme to the app. + * @param darkTheme: Boolean to check if the theme is dark. + * @param settingsViewModel: SettingsViewModel to observe the theme settings. + * @param content: @Composable function to apply the theme to the content. + */ @Composable fun GreenStashTheme( darkTheme: Boolean = isSystemInDarkTheme(), diff --git a/app/src/main/java/com/starry/greenstash/utils/Utils.kt b/app/src/main/java/com/starry/greenstash/utils/Utils.kt index 175194d2..82b2e789 100644 --- a/app/src/main/java/com/starry/greenstash/utils/Utils.kt +++ b/app/src/main/java/com/starry/greenstash/utils/Utils.kt @@ -25,6 +25,10 @@ package com.starry.greenstash.utils +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.net.Uri import android.os.Build import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK @@ -33,17 +37,22 @@ import java.math.RoundingMode import java.text.DecimalFormat import java.text.DecimalFormatSymbols import java.text.NumberFormat -import java.text.SimpleDateFormat import java.time.LocalDateTime import java.time.ZoneId import java.util.Currency -import java.util.Date import java.util.Locale import java.util.TimeZone +/** + * A collection of utility functions. + */ object Utils { - /** Validate number from text field. */ + /** Get validated number from the text. + * + * @param text The text to validate + * @return The validated number + */ fun getValidatedNumber(text: String): String { val filteredChars = text.filterIndexed { index, c -> c.isDigit() || (c == '.' && index != 0 @@ -60,7 +69,11 @@ object Utils { } } - /** Round decimal (double) to 2 digits */ + /** Round the decimal number to two decimal places. + * + * @param number The number to round + * @return The rounded number + */ fun roundDecimal(number: Double): Double { val locale = DecimalFormatSymbols(Locale.US) val df = DecimalFormat("#.##", locale) @@ -68,7 +81,12 @@ object Utils { return df.format(number).toDouble() } - /** Convert double into currency format */ + /** Format currency based on the currency code. + * + * @param amount The amount to format + * @param currencyCode The currency code + * @return The formatted currency + */ fun formatCurrency(amount: Double, currencyCode: String): String { val nf = NumberFormat.getCurrencyInstance().apply { currency = Currency.getInstance(currencyCode) @@ -82,7 +100,19 @@ object Utils { } /** - * https://developer.android.com/reference/androidx/biometric/BiometricPrompt.PromptInfo.Builder#setAllowedAuthenticators(int) + * Get the authenticators based on the Android version. + * + * For Android 9 and 10, the authenticators are BIOMETRIC_WEAK and DEVICE_CREDENTIAL. + * + * For Android 11 and above, the authenticators are BIOMETRIC_STRONG and DEVICE_CREDENTIAL. + * + * For Android versions below 9, the authenticators are BIOMETRIC_STRONG and DEVICE_CREDENTIAL although they are not supported, + * they don't result in any error unlike in Android 9 and 10. + * + * See https://developer.android.com/reference/androidx/biometric/BiometricPrompt.PromptInfo.Builder#setAllowedAuthenticators(int) + * for more information. + * + * @return The authenticators based on the Android version. */ fun getAuthenticators() = if (Build.VERSION.SDK_INT == 28 || Build.VERSION.SDK_INT == 29) { BIOMETRIC_WEAK or DEVICE_CREDENTIAL @@ -90,18 +120,12 @@ object Utils { BIOMETRIC_STRONG or DEVICE_CREDENTIAL } - fun getGreeting(): String { - val currentTime = System.currentTimeMillis() - val simpleDateFormat = SimpleDateFormat("HH", Locale.US) - - return when (simpleDateFormat.format(Date(currentTime)).toInt()) { - in 0..11 -> "Good Morning!" - in 12..16 -> "Good Afternoon!" - in 17..20 -> "Good Evening!" - else -> "Good Night!" - } - } + /** Get the epoch time from the LocalDateTime. + * + * @param dateTime The LocalDateTime object + * @return The epoch time + */ fun getEpochTime(dateTime: LocalDateTime): Long { val timeZone = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ZoneId.systemDefault() @@ -111,4 +135,19 @@ object Utils { return dateTime.atZone(timeZone).toInstant().toEpochMilli() } + /** Open the web link in the browser. + * + * @param context The context + * @param url The URL to open + */ + fun openWebLink(context: Context, url: String) { + val uri: Uri = Uri.parse(url) + val intent = Intent(Intent.ACTION_VIEW, uri) + try { + context.startActivity(intent) + } catch (exc: ActivityNotFoundException) { + exc.printStackTrace() + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/widget/configuration/WidgetConfigActivity.kt b/app/src/main/java/com/starry/greenstash/widget/configuration/WidgetConfigActivity.kt index 533c0d01..bbddd703 100644 --- a/app/src/main/java/com/starry/greenstash/widget/configuration/WidgetConfigActivity.kt +++ b/app/src/main/java/com/starry/greenstash/widget/configuration/WidgetConfigActivity.kt @@ -29,6 +29,7 @@ import android.appwidget.AppWidgetManager import android.content.Intent import android.os.Bundle import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.background @@ -83,11 +84,10 @@ import com.airbnb.lottie.compose.LottieCompositionResult import com.airbnb.lottie.compose.LottieCompositionSpec import com.airbnb.lottie.compose.animateLottieCompositionAsState import com.airbnb.lottie.compose.rememberLottieComposition -import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.starry.greenstash.MainActivity import com.starry.greenstash.R import com.starry.greenstash.ui.screens.settings.SettingsViewModel -import com.starry.greenstash.ui.screens.settings.ThemeMode +import com.starry.greenstash.ui.theme.AdjustEdgeToEdge import com.starry.greenstash.ui.theme.GreenStashTheme import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.widget.GoalWidget @@ -103,21 +103,16 @@ class WidgetConfigActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + enableEdgeToEdge() // Enable edge-to-edge mode for the activity. settingsViewModel = ViewModelProvider(this)[SettingsViewModel::class.java] setContent { GreenStashTheme(settingsViewModel = settingsViewModel) { - val systemUiController = rememberSystemUiController() - systemUiController.setNavigationBarColor( - color = MaterialTheme.colorScheme.background, - darkIcons = settingsViewModel.getCurrentTheme() == ThemeMode.Light + // fix status bar icon color in dark mode. + AdjustEdgeToEdge( + activity = this, + themeState = settingsViewModel.getCurrentTheme() ) - - systemUiController.setStatusBarColor( - color = MaterialTheme.colorScheme.surfaceColorAtElevation(4.dp), - darkIcons = settingsViewModel.getCurrentTheme() == ThemeMode.Light - ) - Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background diff --git a/app/src/main/res/drawable/ic_nav_backups.xml b/app/src/main/res/drawable/ic_nav_backups.xml index 7c945841..65bc34fd 100644 --- a/app/src/main/res/drawable/ic_nav_backups.xml +++ b/app/src/main/res/drawable/ic_nav_backups.xml @@ -7,8 +7,9 @@ + android:pathData="M20.5348 3.46447C19.0704 2 16.7133 2 11.9993 2C7.28525 2 4.92823 2 3.46377 3.46447C2.70628 4.22195 2.3406 5.21824 2.16406 6.65598C2.69473 6.06532 3.33236 5.57328 4.04836 5.20846C4.82984 4.81027 5.66664 4.6488 6.59316 4.5731C7.48829 4.49997 8.58971 4.49998 9.93646 4.5H14.0621C15.4089 4.49998 16.5103 4.49997 17.4054 4.5731C18.332 4.6488 19.1688 4.81027 19.9502 5.20846C20.6662 5.57328 21.3039 6.06532 21.8345 6.65598C21.658 5.21824 21.2923 4.22195 20.5348 3.46447Z" /> + android:fillType="evenOdd" + android:pathData="M2 14C2 11.1997 2 9.79961 2.54497 8.73005C3.02433 7.78924 3.78924 7.02433 4.73005 6.54497C5.79961 6 7.19974 6 10 6H14C16.8003 6 18.2004 6 19.27 6.54497C20.2108 7.02433 20.9757 7.78924 21.455 8.73005C22 9.79961 22 11.1997 22 14C22 16.8003 22 18.2004 21.455 19.27C20.9757 20.2108 20.2108 20.9757 19.27 21.455C18.2004 22 16.8003 22 14 22H10C7.19974 22 5.79961 22 4.73005 21.455C3.78924 20.9757 3.02433 20.2108 2.54497 19.27C2 18.2004 2 16.8003 2 14ZM12.5303 10.4697C12.3897 10.329 12.1989 10.25 12 10.25C11.8011 10.25 11.6103 10.329 11.4697 10.4697L8.96967 12.9697C8.67678 13.2626 8.67678 13.7374 8.96967 14.0303C9.26256 14.3232 9.73744 14.3232 10.0303 14.0303L11.25 12.8107V17C11.25 17.4142 11.5858 17.75 12 17.75C12.4142 17.75 12.75 17.4142 12.75 17V12.8107L13.9697 14.0303C14.2626 14.3232 14.7374 14.3232 15.0303 14.0303C15.3232 13.7374 15.3232 13.2626 15.0303 12.9697L12.5303 10.4697Z" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_nav_privacy.xml b/app/src/main/res/drawable/ic_nav_privacy.xml new file mode 100644 index 00000000..ea64cfab --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_privacy.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_nav_rating.xml b/app/src/main/res/drawable/ic_nav_rating.xml new file mode 100644 index 00000000..7c945841 --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_rating.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_nav_share.xml b/app/src/main/res/drawable/ic_nav_share.xml new file mode 100644 index 00000000..1cfd748b --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_share.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings_about.xml b/app/src/main/res/drawable/ic_settings_about.xml deleted file mode 100644 index e3d03104..00000000 --- a/app/src/main/res/drawable/ic_settings_about.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings_app_lock.xml b/app/src/main/res/drawable/ic_settings_app_lock.xml deleted file mode 100644 index cfe42f4f..00000000 --- a/app/src/main/res/drawable/ic_settings_app_lock.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings_material_you.xml b/app/src/main/res/drawable/ic_settings_material_you.xml deleted file mode 100644 index 31bd991d..00000000 --- a/app/src/main/res/drawable/ic_settings_material_you.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_settings_osl.xml b/app/src/main/res/drawable/ic_settings_osl.xml deleted file mode 100644 index 52b259cc..00000000 --- a/app/src/main/res/drawable/ic_settings_osl.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings_sort_order.xml b/app/src/main/res/drawable/ic_settings_sort_order.xml deleted file mode 100644 index 84b8a695..00000000 --- a/app/src/main/res/drawable/ic_settings_sort_order.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings_theme.xml b/app/src/main/res/drawable/ic_settings_theme.xml deleted file mode 100644 index fab1abd7..00000000 --- a/app/src/main/res/drawable/ic_settings_theme.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - \ 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 f2cf6d6d..de44a5d0 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -11,7 +11,16 @@ Inicio Respaldo Ajustes + Calificarnos + Compartir + Privacidad Hecho con ❤️ en India. + + ¡Hola! Echa un vistazo a esta increíble aplicación llamada GreenStash. + Facilita la planificación y gestión de tus metas de ahorro, ayudándote a establecer el hábito de ahorrar dinero para cosas aún mejores. + Descárgala ahora desde la Play Store: %s + + Desbloquear GreenStash @@ -140,10 +149,12 @@ Configuración Pantalla Tema predeterminado + Cambiar tema Claro Oscuro Predeterminado del sistema Tema Oscuro + Activar tema AMOLED negro Aplicar Cambia el tema según tú fondo de pantalla (A12 en adelante) Esta característica es sólo para dispositivos con Android 12 en adelante. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 52d71229..16c69080 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -12,7 +12,16 @@ Главная Резервные копии Настройки + Оценить нас + Поделиться + Конфиденциальность Сделано с ❤️ в Индии. + + Привет! Посмотри это удивительное приложение под названием GreenStash. + Оно делает планирование и управление твоими финансовыми целями легким и приятным, помогая установить привычку экономить деньги для еще более лучших вещей. + Скачай его сейчас из Play Store: %s + + Разблокировать GreenStash @@ -141,10 +150,12 @@ Настройки Дисплей Тема по умолчанию + Изменить тему Светлая Тёмная Как в системе Чёрная тема + Включить черную тему AMOLED Применить Переключение темы в соответствии с вашими обоями (только для A12+) Эта функция доступна только для устройств под управлением Android 12 или более поздней версии. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index c6cd7735..e97e9151 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -11,7 +11,16 @@ Ana Sayfa Yedekler Ayarlar + Bizi Değerlendir + Paylaş + Gizlilik ❤️ Hindistan\'da yapıldı. + + Merhaba! GreenStash adlı bu harika uygulamaya göz at. + Tasarruf hedeflerinizi planlamanızı ve yönetmenizi kolaylaştırır, daha iyi şeyler için para biriktirme alışkanlığınızı kazanmanıza yardımcı olur. + Şimdi Play Store\'dan indirin: %s + + GreenStash\'in Kilidini Aç @@ -142,10 +151,12 @@ Ayarlar Görünüm Varsayılan Tema + Temayı Değiştir Açık Koyu Sistem Varsayılanı Siyah Tema + Siyah AMOLED temasını etkinleştir Uygula Material You Temayı duvar kağıdınıza göre değiştirin (Yalnızca A12+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index d173913a..951c6809 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -11,7 +11,15 @@ 主页 备份 设置 + 给我们评分 + 分享 + 隐私 用❤️制作于印度。 + + 嘿!看看这款名为GreenStash的神奇应用吧。它让你规划和管理储蓄目标变得轻松愉快,帮助你养成为更好的事物储蓄的习惯 + 立即从Play Store下载:%s + + 解锁GreenStash @@ -141,10 +149,12 @@ 设置 显示 默认主题 + 修改主题 明亮 暗色 系统默认 黑色主题 + 启用黑色AMOLED主题 应用 自适应 根据壁纸切换合适的主题(仅Android12+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index c9f8522e..130cb650 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -11,6 +11,9 @@ 首頁 備份 設定 + 給我們評分 + 分享 + 隱私 在印度製作,充滿 ❤️。 @@ -19,6 +22,10 @@ 驗證成功! 驗證失敗! 驗證錯誤:%s + + 嘿!看看這款名為GreenStash的神奇應用吧。它讓你規劃和管理儲蓄目標變得輕鬆愉快,幫助你養成為更好的事物儲蓄的習慣 + 立即從Play Store下載:%s + 您好!請選擇您的預設貨幣以開始。 @@ -141,10 +148,12 @@ 設定 顯示 預設主題 + 更改主題 亮色 暗色 系統預設 黑色主題 + 啟用黑色AMOLED主題 套用 Material You 根據您的桌布切換主題(僅限 Android 12+) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9370e618..3b707d31 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,7 +11,15 @@ Home Backups Settings + Rate Us + Share + Privacy Made with ❤️ in India. + + Hey! Check out this amazing app called GreenStash. + It makes planning and managing your savings goals a breeze, helping you establish the habit of saving money for better things. + Download it now from the Play Store: %s + Unlock GreenStash @@ -141,10 +149,12 @@ Settings Display Default Theme + Change Theme Light Dark System Default Black Theme + Enable black AMOLED theme Apply Material You Switch theme according to your wallpaper (A12+ Only)