Skip to content

Commit

Permalink
feat: Improve text fields and FABs
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Mihai-Cristian Condrea committed Nov 23, 2024
1 parent 70e530f commit 69bee4b
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 66 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ android {
applicationId = "com.d4rk.cartcalculator"
minSdk = 23
targetSdk = 35
versionCode = 70
versionName = "1.1.1"
versionCode = 73
versionName = "1.1.2"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
resourceConfigurations += listOf(
"en" ,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,52 @@
package com.d4rk.cartcalculator.ui.components

import androidx.compose.animation.core.AnimationSpec
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<Float>(
durationMillis = 300, delayMillis = 0, easing = FastOutSlowInEasing
visible : Boolean = true ,
onClick : () -> Unit ,
icon : @Composable () -> Unit ,
text : (@Composable () -> Unit)? = null ,
offsetX : Float = 50f ,
offsetY : Float = 50f ,
scale : Float = 0.8f ,
animationSpec : AnimationSpec<Float> = tween(
durationMillis = 300 , 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 animatedOffsetX by animateFloatAsState(
targetValue = if (visible) 0f else offsetX ,
animationSpec = animationSpec ,
label = "OffsetX"
)
val offsetY by animateFloatAsState(
targetValue = if (visible) 0f else 50f, animationSpec = animationSpec, label = "OffsetY"
val animatedOffsetY by animateFloatAsState(
targetValue = if (visible) 0f else offsetY ,
animationSpec = animationSpec ,
label = "OffsetY"
)
val scale by animateFloatAsState(
targetValue = if (visible) 1f else 0.8f, animationSpec = animationSpec, label = "Scale"
val animatedScale by animateFloatAsState(
targetValue = if (visible) 1f else scale , 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
}
)
ExtendedFloatingActionButton(onClick = onClick ,
icon = icon ,
text = text ?: {} ,
modifier = Modifier
.bounceClick()
.graphicsLayer {
scaleX = animatedScale
scaleY = animatedScale
translationX = animatedOffsetX
translationY = animatedOffsetY
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ fun SwitchCardComposable(
val view : View = LocalView.current
Card(modifier = Modifier
.fillMaxWidth()
.padding(24.dp)
.padding(all = 24.dp)
.clip(RoundedCornerShape(28.dp))
.clickable {
view.playSoundEffect(SoundEffectConstants.CLICK)
Expand All @@ -60,7 +60,7 @@ fun SwitchCardComposable(
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp) ,
.padding(all = 16.dp) ,
horizontalArrangement = Arrangement.SpaceBetween ,
verticalAlignment = Alignment.CenterVertically
) {
Expand Down Expand Up @@ -161,7 +161,7 @@ fun PreferenceItem(
Icon(it , contentDescription = null)
}
Column(
modifier = Modifier.padding(16.dp)
modifier = Modifier.padding(all = 16.dp)
) {
title?.let {
Text(
Expand Down Expand Up @@ -218,7 +218,7 @@ fun SwitchPreferenceItem(
}
Column(
modifier = Modifier
.padding(16.dp)
.padding(all = 16.dp)
.weight(1f)
) {
Text(text = title , style = MaterialTheme.typography.titleLarge)
Expand All @@ -229,7 +229,7 @@ fun SwitchPreferenceItem(
Switch(
checked = checked , onCheckedChange = { isChecked ->
onCheckedChange(isChecked)
} , modifier = Modifier.padding(16.dp)
} , modifier = Modifier.padding(all = 16.dp)
)
}
}
Expand Down Expand Up @@ -276,7 +276,7 @@ fun SwitchPreferenceItemWithDivider(
}
Column(
modifier = Modifier
.padding(16.dp)
.padding(all = 16.dp)
.weight(1f)
) {
Text(text = title , style = MaterialTheme.typography.titleLarge)
Expand All @@ -293,6 +293,6 @@ fun SwitchPreferenceItemWithDivider(
Switch(checked = checked , onCheckedChange = { isChecked ->
onCheckedChange(isChecked)
onSwitchClick(isChecked)
} , modifier = Modifier.padding(16.dp))
} , modifier = Modifier.padding(all = 16.dp))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ fun CartScreen(activity : CartActivity , cartId : Int) {
}

AdBanner(
modifier = Modifier.padding(12.dp) , dataStore = dataStore
modifier = Modifier.padding(all = 12.dp) , dataStore = dataStore
)

Card(
Expand Down Expand Up @@ -364,7 +364,7 @@ fun CartItemComposable(
Box(
modifier = modifier
.fillMaxWidth()
.padding(24.dp)
.padding(all = 24.dp)
) {
Row(
modifier = Modifier.fillMaxSize() ,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand Down Expand Up @@ -65,6 +66,7 @@ fun HelpScreen(activity : HelpActivity , viewModel : HelpViewModel) {
var showMenu : Boolean by remember { mutableStateOf(value = false) }
val context : Context = LocalContext.current
val view : View = LocalView.current
val isFabVisible by viewModel.isFabVisible.collectAsState()
val showDialog : MutableState<Boolean> = remember { mutableStateOf(value = false) }
val reviewInfo : ReviewInfo? = viewModel.reviewInfo.value

Expand Down Expand Up @@ -171,7 +173,9 @@ fun HelpScreen(activity : HelpActivity , viewModel : HelpViewModel) {
)
} ,
floatingActionButton = {
AnimatedExtendedFloatingActionButton(onClick = {
AnimatedExtendedFloatingActionButton(
visible = isFabVisible,
onClick = {
view.playSoundEffect(SoundEffectConstants.CLICK)
viewModel.reviewInfo.value?.let { safeReviewInfo ->
viewModel.launchReviewFlow(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,32 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.d4rk.cartcalculator.ui.viewmodel.BaseViewModel
import com.d4rk.cartcalculator.utils.IntentUtils
import com.google.android.gms.tasks.Task
import com.google.android.play.core.review.ReviewInfo
import com.google.android.play.core.review.ReviewManager
import com.google.android.play.core.review.ReviewManagerFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class HelpViewModel(application: Application) : AndroidViewModel(application) {
class HelpViewModel(application: Application) : BaseViewModel(application) {

private var _reviewInfo: MutableState<ReviewInfo?> = mutableStateOf(value = null)
val reviewInfo: State<ReviewInfo?> = _reviewInfo

init {
initializeVisibilityStates()
}

private fun initializeVisibilityStates() {
viewModelScope.launch(coroutineExceptionHandler) {
delay(timeMillis = 50L)
showFab()
}
}

fun requestReviewFlow() {
viewModelScope.launch(Dispatchers.IO) {
val reviewManager: ReviewManager = ReviewManagerFactory.create(getApplication())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class HomeViewModel(application : Application) : BaseViewModel(application) {
lessonIndex == index || _visibilityStates.value[lessonIndex]
}
}
delay(timeMillis = 50L)
showFab()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class HomeRepository(application : Application) :
}
}

suspend fun addCartRepository(cart : ShoppingCartTable , onSuccess : (ShoppingCartTable) -> Unit , ) {
suspend fun addCartRepository(cart : ShoppingCartTable , onSuccess : (ShoppingCartTable) -> Unit) {
withContext(Dispatchers.IO) {
val addedCart = addCartImplementation(cart = cart)
withContext(Dispatchers.Main) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,17 @@ open class BaseViewModel(application : Application) : AndroidViewModel(applicati
private val _uiErrorModel = MutableStateFlow(UiErrorModel())
val uiErrorModel : StateFlow<UiErrorModel> = _uiErrorModel.asStateFlow()

protected val coroutineExceptionHandler = CoroutineExceptionHandler { _ , exception ->
protected val coroutineExceptionHandler = CoroutineExceptionHandler { _ , exception : Throwable ->
Log.e("BaseViewModel" , "Coroutine Exception: " , exception)
handleError(exception)
}

val _visibilityStates = MutableStateFlow<List<Boolean>>(emptyList())
val visibilityStates : StateFlow<List<Boolean>> = _visibilityStates.asStateFlow()

private val _isFabVisible = MutableStateFlow(false)
private val _isFabVisible = MutableStateFlow(value= false)
val isFabVisible : StateFlow<Boolean> = _isFabVisible.asStateFlow()

protected fun showFab() {
viewModelScope.launch(coroutineExceptionHandler) {
_isFabVisible.value = true
}
}

private fun handleError(exception : Throwable) {
viewModelScope.launch(coroutineExceptionHandler) {
val errorType : ErrorType = when (exception) {
Expand Down Expand Up @@ -83,4 +77,10 @@ open class BaseViewModel(application : Application) : AndroidViewModel(applicati
_isLoading.value = false
}
}

protected fun showFab() {
viewModelScope.launch(coroutineExceptionHandler) {
_isFabVisible.value = true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import java.net.HttpURLConnection
import java.net.URL

object OpenSourceLicensesUtils {
val packageName = BuildConfig.APPLICATION_ID
const val packageName = BuildConfig.APPLICATION_ID

private suspend fun getChangelogMarkdown(): String {
return withContext(Dispatchers.IO) {
Expand Down

0 comments on commit 69bee4b

Please sign in to comment.