From 70e530f02cc098f81b0734f97c1cf6f242fa1c78 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Sat, 23 Nov 2024 19:14:12 +0200 Subject: [PATCH] feat: Improve text fields and FABs Improved the keyboard actions for text fields, making them more user-friendly. Started working on entry animations for FABs to provide a better user experience. Added some small code improvements to enhance overall code quality. --- CHANGELOG.md | 5 + app/build.gradle.kts | 2 +- .../ui/components/FloatingActionButton.kt | 63 ++++++++++ .../dialogs/AddNewCartAlertDialog.kt | 34 ++++-- .../dialogs/AddNewCartItemAlertDialog.kt | 111 +++++++++++------- .../dialogs/DeleteCartAlertDialog.kt | 6 +- .../dialogs/DeleteCartItemAlertDialog.kt | 6 +- .../ui/screens/help/HelpScreen.kt | 32 +++-- .../ui/screens/home/HomeViewModel.kt | 1 + .../ui/screens/main/MainScreen.kt | 25 ++-- .../settings/about/AboutSettingsComposable.kt | 2 +- .../settings/cart/CartSettingsComposable.kt | 4 +- .../ui/viewmodel/BaseViewModel.kt | 74 +++++++----- .../utils/OpenSourceLicensesUtils.kt | 8 +- 14 files changed, 248 insertions(+), 125 deletions(-) create mode 100644 app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/FloatingActionButton.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d93918..a6a6845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Version 1.1.2: + +- **Minor**: Text fields now offer a more intuitive keyboard experience. +- **Minor**: Floating action buttons (FABs) have subtle entry animations for a smoother visual flow. + # Version 1.1.1: - **New**: Added entry animations for carts and cart items. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4f185f1..b0c2408 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,7 +15,7 @@ android { applicationId = "com.d4rk.cartcalculator" minSdk = 23 targetSdk = 35 - versionCode = 69 + versionCode = 70 versionName = "1.1.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" resourceConfigurations += listOf( diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/FloatingActionButton.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/FloatingActionButton.kt new file mode 100644 index 0000000..acb8148 --- /dev/null +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/FloatingActionButton.kt @@ -0,0 +1,63 @@ +package com.d4rk.cartcalculator.ui.components + +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import com.d4rk.cartcalculator.ui.components.animations.bounceClick + +@Composable +fun AnimatedExtendedFloatingActionButton( + visible: Boolean = true, + onClick: () -> Unit, + icon: @Composable () -> Unit, + text: (@Composable () -> Unit)? = null, +) { + var isInitiallyVisible by rememberSaveable { mutableStateOf(false) } + + SideEffect { + if (!isInitiallyVisible) { + isInitiallyVisible = true + } + } + + val animationSpec = tween( + durationMillis = 300, delayMillis = 0, easing = FastOutSlowInEasing + ) + + val alpha by animateFloatAsState( + targetValue = if (visible) 1f else 0f, animationSpec = animationSpec, label = "Alpha" + ) + val offsetX by animateFloatAsState( + targetValue = if (visible) 0f else 50f, animationSpec = animationSpec, label = "OffsetX" + ) + val offsetY by animateFloatAsState( + targetValue = if (visible) 0f else 50f, animationSpec = animationSpec, label = "OffsetY" + ) + val scale by animateFloatAsState( + targetValue = if (visible) 1f else 0.8f, animationSpec = animationSpec, label = "Scale" + ) + + ExtendedFloatingActionButton( + onClick = onClick, + icon = icon, + text = text ?: {}, + modifier = Modifier + .bounceClick() + .graphicsLayer { + this.alpha = alpha + this.translationX = offsetX + this.translationY = offsetY + this.scaleX = scale + this.scaleY = scale + } + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/AddNewCartAlertDialog.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/AddNewCartAlertDialog.kt index a61b649..df63da4 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/AddNewCartAlertDialog.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/AddNewCartAlertDialog.kt @@ -3,6 +3,7 @@ package com.d4rk.cartcalculator.ui.components.dialogs import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Info @@ -17,7 +18,9 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.dp import com.d4rk.cartcalculator.R @@ -43,35 +46,46 @@ fun AddNewCartAlertDialog(onDismiss : () -> Unit , onCartCreated : (ShoppingCart onDismiss() } }) { - Text(text =stringResource(android.R.string.ok)) + Text(text = stringResource(android.R.string.ok)) } } , dismissButton = { TextButton(onClick = { onDismiss() }) { - Text(text =stringResource(android.R.string.cancel)) + Text(text = stringResource(android.R.string.cancel)) } }) } @Composable fun AddNewCartAlertDialogContent( - newCart : MutableState , nameText : MutableState , + newCart : MutableState , + nameText : MutableState , ) { val currentDate = Date() - val defaultName = stringResource(R.string.shopping_cart) + val defaultName = stringResource(id = R.string.shopping_cart) + + val keyboardController = LocalSoftwareKeyboardController.current + Column { OutlinedTextField(value = nameText.value , onValueChange = { nameText.value = it } , - label = { Text(text =stringResource(id = R.string.cart_name)) } , - keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences) , - placeholder = { Text(text =stringResource(R.string.shopping_cart)) }) + label = { Text(text = stringResource(id = R.string.cart_name)) } , + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.Sentences , + imeAction = ImeAction.Done + ) , + keyboardActions = KeyboardActions(onDone = { + newCart.value = + ShoppingCartTable(name = nameText.value.ifEmpty { defaultName } , + date = currentDate) + keyboardController?.hide() + }) , + placeholder = { Text(text = stringResource(id = R.string.shopping_cart)) }) Spacer(modifier = Modifier.height(24.dp)) Icon(imageVector = Icons.Outlined.Info , contentDescription = null) Spacer(modifier = Modifier.height(12.dp)) - Text(text =stringResource(R.string.summary_cart_dialog)) + Text(text = stringResource(id = R.string.summary_cart_dialog)) } - newCart.value = - ShoppingCartTable(name = nameText.value.ifEmpty { defaultName } , date = currentDate) } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/AddNewCartItemAlertDialog.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/AddNewCartItemAlertDialog.kt index 8641fa5..b592347 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/AddNewCartItemAlertDialog.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/AddNewCartItemAlertDialog.kt @@ -3,6 +3,7 @@ package com.d4rk.cartcalculator.ui.components.dialogs import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Info @@ -17,7 +18,11 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp @@ -26,76 +31,102 @@ import com.d4rk.cartcalculator.data.database.table.ShoppingCartItemsTable @Composable fun AddNewCartItemAlertDialog( - cartId: Int, onDismiss: () -> Unit, onCartCreated: (ShoppingCartItemsTable) -> Unit + cartId : Int , onDismiss : () -> Unit , onCartCreated : (ShoppingCartItemsTable) -> Unit ) { val newCartItem = remember { mutableStateOf(null) } - AlertDialog(onDismissRequest = onDismiss, text = { - AddNewCartItemAlertDialogContent(cartId, newCartItem) - }, icon = { + AlertDialog(onDismissRequest = onDismiss , text = { + AddNewCartItemAlertDialogContent(cartId , newCartItem) + } , icon = { Icon( - Icons.Outlined.ShoppingBag, contentDescription = null + Icons.Outlined.ShoppingBag , contentDescription = null ) - }, confirmButton = { + } , confirmButton = { TextButton(onClick = { newCartItem.value?.let { cartItem -> onCartCreated(cartItem) } - }, enabled = newCartItem.value != null) { - Text(text =stringResource(android.R.string.ok)) + } , enabled = newCartItem.value != null) { + Text(text = stringResource(android.R.string.ok)) } - }, dismissButton = { + } , dismissButton = { TextButton(onClick = { onDismiss() }) { - Text(text =stringResource(android.R.string.cancel)) + Text(text = stringResource(android.R.string.cancel)) } }) } @Composable -fun AddNewCartItemAlertDialogContent(cartId: Int, newCartItem: MutableState) { - val nameText = remember { mutableStateOf("") } - val priceText = remember { mutableStateOf("") } - val quantityText = remember { mutableStateOf("") } +fun AddNewCartItemAlertDialogContent( + cartId : Int , + newCartItem : MutableState +) { + val nameText = remember { mutableStateOf(value = "") } + val priceText = remember { mutableStateOf(value = "") } + val quantityText = remember { mutableStateOf(value = "") } + + val nameFocusRequester = remember { FocusRequester() } + val priceFocusRequester = remember { FocusRequester() } + val quantityFocusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + Column { - OutlinedTextField(value = nameText.value, - onValueChange = { - nameText.value = it - }, - label = { Text(stringResource(id = R.string.item_name)) }, - keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences), - placeholder = { Text(stringResource(id = R.string.enter_item_name)) }) - OutlinedTextField(value = priceText.value, - onValueChange = { priceText.value = it }, - label = { Text(stringResource(id = R.string.item_price)) }, - placeholder = { Text(stringResource(id = R.string.enter_item_price)) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) - ) - OutlinedTextField(value = quantityText.value, - onValueChange = { quantityText.value = it }, - label = { Text(stringResource(id = R.string.quantity)) }, - placeholder = { Text(stringResource(id = R.string.enter_quantity)) }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) - ) + OutlinedTextField(value = nameText.value , + onValueChange = { nameText.value = it } , + label = { Text(text = stringResource(id = R.string.item_name)) } , + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.Sentences , + imeAction = ImeAction.Next + ) , + keyboardActions = KeyboardActions(onNext = { priceFocusRequester.requestFocus() }) , + placeholder = { Text(text = stringResource(id = R.string.enter_item_name)) } , + modifier = Modifier.focusRequester(nameFocusRequester)) + + OutlinedTextField(value = priceText.value , + onValueChange = { priceText.value = it } , + label = { Text(text = stringResource(id = R.string.item_price)) } , + placeholder = { Text(text = stringResource(id = R.string.enter_item_price)) } , + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number , imeAction = ImeAction.Next + ) , + keyboardActions = KeyboardActions(onNext = { quantityFocusRequester.requestFocus() }) , + modifier = Modifier.focusRequester(priceFocusRequester)) + + OutlinedTextField(value = quantityText.value , + onValueChange = { quantityText.value = it } , + label = { Text(text = stringResource(id = R.string.quantity)) } , + placeholder = { Text(text = stringResource(id = R.string.enter_quantity)) } , + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number , imeAction = ImeAction.Done + ) , + keyboardActions = KeyboardActions(onDone = { + keyboardController?.hide() + }) , + modifier = Modifier.focusRequester(quantityFocusRequester)) + Spacer(modifier = Modifier.height(24.dp)) - Icon(imageVector = Icons.Outlined.Info, contentDescription = null) + Icon(imageVector = Icons.Outlined.Info , contentDescription = null) Spacer(modifier = Modifier.height(12.dp)) Text(stringResource(id = R.string.dialog_info_cart_item)) } + if (nameText.value.isNotEmpty() && priceText.value.isNotEmpty() && quantityText.value.isNotEmpty()) { - val price = priceText.value.replace(',', '.').toDoubleOrNull() + val price = priceText.value.replace(oldChar = ',' , newChar = '.').toDoubleOrNull() val quantity = quantityText.value.toIntOrNull() if (price != null && quantity != null) { newCartItem.value = ShoppingCartItemsTable( - cartId = cartId, - name = nameText.value, - price = price.toString(), + cartId = cartId , + name = nameText.value , + price = price.toString() , quantity = quantity ) - } else { + } + else { newCartItem.value = null } - } else { + } + else { newCartItem.value = null } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/DeleteCartAlertDialog.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/DeleteCartAlertDialog.kt index ccd76cc..02be68e 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/DeleteCartAlertDialog.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/DeleteCartAlertDialog.kt @@ -28,7 +28,7 @@ fun DeleteCartAlertDialog( AlertDialog(onDismissRequest = { onDismiss() } , title = { Text( - text = stringResource(R.string.delete_cart_title) , + text = stringResource(id= R.string.delete_cart_title) , ) } , text = { DeleteCartAlertDialogContent(cart) } , confirmButton = { TextButton(onClick = { @@ -56,14 +56,14 @@ fun DeleteCartAlertDialogContent(cart : ShoppingCartTable?) { ) Spacer(modifier = Modifier.height(24.dp)) Text( - text = stringResource(R.string.delete_cart_message) , + text = stringResource(id= R.string.delete_cart_message) , style = MaterialTheme.typography.bodyLarge ) Spacer(modifier = Modifier.height(24.dp)) Icon(imageVector = Icons.Outlined.Info , contentDescription = null) Spacer(modifier = Modifier.height(12.dp)) Text( - text = stringResource(R.string.delete_cart_warning , cart?.name ?: "") , + text = stringResource(id= R.string.delete_cart_warning , cart?.name ?: "") , style = MaterialTheme.typography.bodyLarge ) } diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/DeleteCartItemAlertDialog.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/DeleteCartItemAlertDialog.kt index 499c999..0991eb9 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/DeleteCartItemAlertDialog.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/components/dialogs/DeleteCartItemAlertDialog.kt @@ -29,7 +29,7 @@ fun DeleteCartItemAlertDialog( onDismissRequest = onDismiss, title = { Text( - text = stringResource(R.string.delete_cart_item_title), + text = stringResource(id= R.string.delete_cart_item_title), ) }, text = { DeleteCartItemAlertDialogContent(cartItem!!) }, @@ -60,14 +60,14 @@ fun DeleteCartItemAlertDialogContent(cartItem: ShoppingCartItemsTable) { ) Spacer(modifier = Modifier.height(24.dp)) Text( - text = stringResource(R.string.delete_cart_item_message), + text = stringResource(id= R.string.delete_cart_item_message), style = MaterialTheme.typography.bodyLarge, ) Spacer(modifier = Modifier.height(24.dp)) Icon(imageVector = Icons.Outlined.Info, contentDescription = null) Spacer(modifier = Modifier.height(12.dp)) Text( - text = stringResource(R.string.delete_cart_item_warning, cartItem.name), + text = stringResource(id= R.string.delete_cart_item_warning, cartItem.name), style = MaterialTheme.typography.bodyLarge ) } diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/help/HelpScreen.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/help/HelpScreen.kt index 87f4fb7..f8f820c 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/help/HelpScreen.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/help/HelpScreen.kt @@ -26,7 +26,6 @@ import androidx.compose.material3.Card import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar @@ -51,6 +50,7 @@ import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.d4rk.cartcalculator.R +import com.d4rk.cartcalculator.ui.components.AnimatedExtendedFloatingActionButton import com.d4rk.cartcalculator.ui.components.animations.bounceClick import com.d4rk.cartcalculator.ui.components.dialogs.VersionInfoAlertDialog import com.d4rk.cartcalculator.utils.IntentUtils @@ -150,7 +150,7 @@ fun HelpScreen(activity : HelpActivity , viewModel : HelpViewModel) { ) }) DropdownMenuItem(modifier = Modifier.bounceClick() , - text = { Text(text = stringResource(R.string.oss_license_title)) } , + text = { Text(text = stringResource(id= R.string.oss_license_title)) } , onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) IntentUtils.openLicensesScreen( @@ -171,22 +171,18 @@ fun HelpScreen(activity : HelpActivity , viewModel : HelpViewModel) { ) } , floatingActionButton = { - ExtendedFloatingActionButton(text = { Text(text = stringResource(id = R.string.feedback)) } , - onClick = { - view.playSoundEffect(SoundEffectConstants.CLICK) - viewModel.reviewInfo.value?.let { safeReviewInfo -> - viewModel.launchReviewFlow( - activity , safeReviewInfo - ) - } - } , - icon = { - Icon( - Icons.Default.MailOutline , - contentDescription = null - ) - } , - modifier = Modifier.bounceClick()) + AnimatedExtendedFloatingActionButton(onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) + viewModel.reviewInfo.value?.let { safeReviewInfo -> + viewModel.launchReviewFlow( + activity , safeReviewInfo + ) + } + } , text = { Text(text = stringResource(id = R.string.feedback)) } , icon = { + Icon( + Icons.Default.MailOutline , contentDescription = null + ) + }) } , ) { paddingValues -> Box( diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/home/HomeViewModel.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/home/HomeViewModel.kt index 2b12f99..622c6ac 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/home/HomeViewModel.kt @@ -45,6 +45,7 @@ class HomeViewModel(application : Application) : BaseViewModel(application) { lessonIndex == index || _visibilityStates.value[lessonIndex] } } + showFab() } } diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/main/MainScreen.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/main/MainScreen.kt index 4a8143f..d711cde 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/main/MainScreen.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/main/MainScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.AddShoppingCart import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerValue -import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost @@ -20,6 +19,8 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -29,8 +30,8 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.d4rk.cartcalculator.R import com.d4rk.cartcalculator.data.datastore.DataStore +import com.d4rk.cartcalculator.ui.components.AnimatedExtendedFloatingActionButton import com.d4rk.cartcalculator.ui.components.ads.AdBanner -import com.d4rk.cartcalculator.ui.components.animations.bounceClick import com.d4rk.cartcalculator.ui.components.navigation.NavigationDrawer import com.d4rk.cartcalculator.ui.components.navigation.TopAppBarMain import com.d4rk.cartcalculator.ui.screens.home.HomeScreen @@ -56,20 +57,20 @@ fun MainScreenContent( ) { val viewModel : HomeViewModel = viewModel() val snackbarHostState = remember { SnackbarHostState() } + val isFabVisible by viewModel.isFabVisible.collectAsState() Scaffold(topBar = { - TopAppBarMain(view = view, drawerState = drawerState, context = context, coroutineScope = coroutineScope) + TopAppBarMain( + view = view , + drawerState = drawerState , + context = context , + coroutineScope = coroutineScope + ) } , floatingActionButton = { - ExtendedFloatingActionButton(modifier = Modifier.bounceClick() , text = { - Text( - text = stringResource(R.string.add_new_cart) - ) - } , onClick = { - view.playSoundEffect( - SoundEffectConstants.CLICK - ) + AnimatedExtendedFloatingActionButton(visible = isFabVisible , onClick = { + view.playSoundEffect(SoundEffectConstants.CLICK) viewModel.openNewCartDialog() - } , icon = { + } , text = { Text(text = stringResource(id = R.string.add_new_cart)) } , icon = { Icon( Icons.Outlined.AddShoppingCart , contentDescription = null ) diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/settings/about/AboutSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/settings/about/AboutSettingsComposable.kt index 4c5e709..8d79a2c 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/settings/about/AboutSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/settings/about/AboutSettingsComposable.kt @@ -57,7 +57,7 @@ fun AboutSettingsComposable(activity : AboutSettingsActivity) { ) } item(key = "oss_licenses") { - PreferenceItem(title = stringResource(R.string.oss_license_title) , + PreferenceItem(title = stringResource(id= R.string.oss_license_title) , summary = stringResource(id = R.string.summary_preference_settings_oss) , onClick = { IntentUtils.openLicensesScreen( diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/settings/cart/CartSettingsComposable.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/settings/cart/CartSettingsComposable.kt index cb74124..c114b11 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/settings/cart/CartSettingsComposable.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/screens/settings/cart/CartSettingsComposable.kt @@ -30,8 +30,8 @@ fun CartSettingsComposable(activity : CartSettingsActivity) { .padding(paddingValues) , ) { item { - PreferenceCategoryItem(title = stringResource(R.string.shopping_cart)) - PreferenceItem(title = stringResource(R.string.currency) , + PreferenceCategoryItem(title = stringResource(id= R.string.shopping_cart)) + PreferenceItem(title = stringResource(id= R.string.currency) , summary = stringResource(id = R.string.summary_preference_settings_currency) , onClick = { showDialog.value = true }) } diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/viewmodel/BaseViewModel.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/viewmodel/BaseViewModel.kt index 36ded39..279102a 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/ui/viewmodel/BaseViewModel.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/ui/viewmodel/BaseViewModel.kt @@ -10,7 +10,6 @@ import com.d4rk.cartcalculator.constants.error.ErrorType import com.d4rk.cartcalculator.data.model.ui.error.UiErrorModel import com.d4rk.cartcalculator.utils.error.ErrorHandler import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -18,57 +17,70 @@ import kotlinx.coroutines.launch import java.io.FileNotFoundException import java.io.IOException -open class BaseViewModel(application: Application) : AndroidViewModel(application) { +open class BaseViewModel(application : Application) : AndroidViewModel(application) { private val _isLoading = MutableStateFlow(value = false) - val isLoading: StateFlow = _isLoading + val isLoading : StateFlow = _isLoading private val _uiErrorModel = MutableStateFlow(UiErrorModel()) - val uiErrorModel: StateFlow = _uiErrorModel.asStateFlow() + val uiErrorModel : StateFlow = _uiErrorModel.asStateFlow() - protected val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception -> - Log.e("BaseViewModel", "Coroutine Exception: ", exception) + protected val coroutineExceptionHandler = CoroutineExceptionHandler { _ , exception -> + Log.e("BaseViewModel" , "Coroutine Exception: " , exception) handleError(exception) } val _visibilityStates = MutableStateFlow>(emptyList()) val visibilityStates : StateFlow> = _visibilityStates.asStateFlow() - private fun handleError(exception: Throwable) { - val errorType: ErrorType = when (exception) { - is SecurityException -> ErrorType.SECURITY_EXCEPTION - is IOException -> ErrorType.IO_EXCEPTION - is FileNotFoundException -> ErrorType.FILE_NOT_FOUND - is ActivityNotFoundException -> ErrorType.ACTIVITY_NOT_FOUND - is IllegalArgumentException -> ErrorType.ILLEGAL_ARGUMENT - else -> ErrorType.UNKNOWN_ERROR + private val _isFabVisible = MutableStateFlow(false) + val isFabVisible : StateFlow = _isFabVisible.asStateFlow() + + protected fun showFab() { + viewModelScope.launch(coroutineExceptionHandler) { + _isFabVisible.value = true } - handleError(errorType, exception) + } - _uiErrorModel.value = UiErrorModel( - showErrorDialog = true, errorMessage = when (errorType) { - ErrorType.SECURITY_EXCEPTION -> getApplication().getString(R.string.security_error) - ErrorType.IO_EXCEPTION -> getApplication().getString(R.string.io_error) - ErrorType.FILE_NOT_FOUND -> getApplication().getString(R.string.file_not_found) - ErrorType.ACTIVITY_NOT_FOUND -> getApplication().getString(R.string.activity_not_found) - ErrorType.ILLEGAL_ARGUMENT -> getApplication().getString(R.string.illegal_argument_error) - ErrorType.UNKNOWN_ERROR -> getApplication().getString(R.string.unknown_error) + private fun handleError(exception : Throwable) { + viewModelScope.launch(coroutineExceptionHandler) { + val errorType : ErrorType = when (exception) { + is SecurityException -> ErrorType.SECURITY_EXCEPTION + is IOException -> ErrorType.IO_EXCEPTION + is FileNotFoundException -> ErrorType.FILE_NOT_FOUND + is ActivityNotFoundException -> ErrorType.ACTIVITY_NOT_FOUND + is IllegalArgumentException -> ErrorType.ILLEGAL_ARGUMENT + else -> ErrorType.UNKNOWN_ERROR } - ) - } + handleError(errorType , exception) - fun dismissErrorDialog() { - _uiErrorModel.value = UiErrorModel(showErrorDialog = false) + _uiErrorModel.value = UiErrorModel( + showErrorDialog = true , errorMessage = when (errorType) { + ErrorType.SECURITY_EXCEPTION -> getApplication().getString(R.string.security_error) + ErrorType.IO_EXCEPTION -> getApplication().getString(R.string.io_error) + ErrorType.FILE_NOT_FOUND -> getApplication().getString(R.string.file_not_found) + ErrorType.ACTIVITY_NOT_FOUND -> getApplication().getString(R.string.activity_not_found) + ErrorType.ILLEGAL_ARGUMENT -> getApplication().getString(R.string.illegal_argument_error) + ErrorType.UNKNOWN_ERROR -> getApplication().getString(R.string.unknown_error) + } + ) + } } - protected open fun handleError(errorType: ErrorType, ignoredException: Throwable) { - ErrorHandler.handleError(getApplication(), errorType) + protected open fun handleError(errorType : ErrorType , ignoredException : Throwable) { + viewModelScope.launch(coroutineExceptionHandler) { + ErrorHandler.handleError(getApplication() , errorType) + } } protected fun showLoading() { - _isLoading.value = true + viewModelScope.launch(coroutineExceptionHandler) { + _isLoading.value = true + } } protected fun hideLoading() { - _isLoading.value = false + viewModelScope.launch(coroutineExceptionHandler) { + _isLoading.value = false + } } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/OpenSourceLicensesUtils.kt b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/OpenSourceLicensesUtils.kt index bd0e980..2e47f63 100644 --- a/app/src/main/kotlin/com/d4rk/cartcalculator/utils/OpenSourceLicensesUtils.kt +++ b/app/src/main/kotlin/com/d4rk/cartcalculator/utils/OpenSourceLicensesUtils.kt @@ -32,12 +32,12 @@ object OpenSourceLicensesUtils { return@withContext reader.readText() } } else { - getStringResource(R.string.error_loading_changelog) + getStringResource(id= R.string.error_loading_changelog) } } finally { connection.disconnect() } - } ?: getStringResource(R.string.error_loading_changelog) + } ?: getStringResource(id= R.string.error_loading_changelog) } } @@ -52,12 +52,12 @@ object OpenSourceLicensesUtils { return@withContext reader.readText() } } else { - getStringResource(R.string.error_loading_eula) + getStringResource(id= R.string.error_loading_eula) } } finally { connection.disconnect() } - } ?: getStringResource(R.string.error_loading_eula) + } ?: getStringResource(id= R.string.error_loading_eula) } }