From 7f664c21fd5344a61b9d48cdfe74d603361385c1 Mon Sep 17 00:00:00 2001 From: Sad Ellie Date: Sat, 4 Feb 2023 00:25:20 +0400 Subject: [PATCH] Actual text fields for epoch converter --- .../unitto/feature/epoch/EpochScreen.kt | 88 ++++------ .../unitto/feature/epoch/EpochViewModel.kt | 12 +- .../feature/epoch/component/DateTextField.kt | 154 ------------------ .../epoch/component/DateTextFieldUtils.kt | 66 ++++++++ .../feature/epoch/component/SwapButton.kt | 11 +- .../unitto/feature/epoch/component/TopPart.kt | 99 +++++++++-- .../feature/epoch/component/UnixTextField.kt | 73 --------- gradle/libs.versions.toml | 2 +- 8 files changed, 202 insertions(+), 303 deletions(-) delete mode 100644 feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/DateTextField.kt create mode 100644 feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/DateTextFieldUtils.kt delete mode 100644 feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/UnixTextField.kt diff --git a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/EpochScreen.kt b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/EpochScreen.kt index 166413d2..65b1cf1c 100644 --- a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/EpochScreen.kt +++ b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/EpochScreen.kt @@ -18,24 +18,22 @@ package com.sadellie.unitto.feature.epoch -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sadellie.unitto.core.ui.common.UnittoTopAppBar import com.sadellie.unitto.core.ui.common.PortraitLandscape -import com.sadellie.unitto.feature.epoch.component.DateTextField import com.sadellie.unitto.feature.epoch.component.EpochKeyboard import com.sadellie.unitto.feature.epoch.component.TopPart -import com.sadellie.unitto.feature.epoch.component.UnixTextField @Composable internal fun EpochRoute( @@ -57,8 +55,8 @@ internal fun EpochRoute( private fun EpochScreen( navigateUpAction: () -> Unit, uiState: EpochUIState, - addSymbol: (String) -> Unit, - deleteSymbol: () -> Unit, + addSymbol: (String, Int) -> Unit, + deleteSymbol: (Int) -> Unit, clearSymbols: () -> Unit, swap: () -> Unit ) { @@ -66,55 +64,41 @@ private fun EpochScreen( title = stringResource(R.string.epoch_converter), navigateUpAction = navigateUpAction ) { padding -> + var selection: TextRange by remember { mutableStateOf(TextRange.Zero) } + PortraitLandscape( modifier = Modifier.padding(padding), - content1 = { + content1 = { topContentModifier -> TopPart( - modifier = it, - swap = swap, - unixToDate = !uiState.dateToUnix, - dateField = { - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .animateItemPlacement() - ) { - DateTextField( - modifier = Modifier.fillMaxWidth(), - date = uiState.dateField - ) - Text( - text = "date", - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.End - ) - } + modifier = topContentModifier.padding(horizontal = 8.dp), + dateToUnix = uiState.dateToUnix, + dateValue = uiState.dateField, + unixValue = uiState.unixField, + swap = { + swap() + selection = TextRange.Zero }, - unixField = { - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.background) - .animateItemPlacement() - ) { - UnixTextField( - modifier = Modifier.fillMaxWidth(), - unix = uiState.unixField - ) - Text( - text = "unix", - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.End - ) - } - } + selection = selection, + onCursorChange = { selection = it.selection } ) }, - content2 = { + content2 = { bottomModifier -> EpochKeyboard( - modifier = it, - addSymbol = addSymbol, - clearSymbols = clearSymbols, - deleteSymbol = deleteSymbol + modifier = bottomModifier, + addSymbol = { + addSymbol(it, selection.start) + selection = TextRange(selection.start + 1) + }, + clearSymbols = { + clearSymbols() + selection = TextRange.Zero + }, + deleteSymbol = { + if (selection.start != 0) { + deleteSymbol(selection.start - 1) + selection = TextRange(selection.start - 1) + } + } ) } ) diff --git a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/EpochViewModel.kt b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/EpochViewModel.kt index cb671d87..e86e4118 100644 --- a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/EpochViewModel.kt +++ b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/EpochViewModel.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update +import java.lang.Integer.min import javax.inject.Inject data class EpochUIState( @@ -68,12 +69,17 @@ class EpochViewModel @Inject constructor() : ViewModel() { viewModelScope, SharingStarted.WhileSubscribed(5000L), EpochUIState() ) - fun addSymbol(symbol: String) { + fun addSymbol(symbol: String, position: Int) { val maxSymbols = if (_fromDateToUnix.value) 14 else 10 - if (_input.value.length < maxSymbols) _input.update { it + symbol } + if (_input.value.length >= maxSymbols) return + _input.update { it.replaceRange(position, position, symbol) } } - fun deleteSymbol() = _input.update { it.dropLast(1) } + fun deleteSymbol(position: Int) { + _input.update { + it.removeRange(position, min(position + 1, _input.value.length)) + } + } fun clearSymbols() = _input.update { "" } diff --git a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/DateTextField.kt b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/DateTextField.kt deleted file mode 100644 index c444d705..00000000 --- a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/DateTextField.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Unitto is a unit converter for Android - * Copyright (c) 2023 Elshan Agaev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.sadellie.unitto.feature.epoch.component - -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.SizeTransform -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically -import androidx.compose.animation.with -import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.rememberScrollState -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium -import com.sadellie.unitto.feature.epoch.R -import kotlinx.coroutines.delay - -@Composable -fun DateTextField( - modifier: Modifier, - date: String -) { - val inputWithPadding: String = date.padEnd(14, '0') - - fun inFocus(range: Int): Boolean = date.length > range - - Column( - modifier = modifier - .height(IntrinsicSize.Min) - .horizontalScroll(rememberScrollState()), - horizontalAlignment = Alignment.End - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(2.dp) - ) { - // Hour - AnimatedText(text = inputWithPadding[0].toString(), focus = inFocus(0)) - AnimatedText(text = inputWithPadding[1].toString(), focus = inFocus(1)) - // Divider - SimpleText(text = ":", focus = inFocus(2)) - // Minute - AnimatedText(text = inputWithPadding[2].toString(), focus = inFocus(2)) - AnimatedText(text = inputWithPadding[3].toString(), focus = inFocus(3)) - // Divider - SimpleText(text = ":", focus = inFocus(4)) - // Second - AnimatedText(text = inputWithPadding[4].toString(), focus = inFocus(4)) - AnimatedText(text = inputWithPadding[5].toString(), focus = inFocus(5)) - } - Row( - horizontalArrangement = Arrangement.spacedBy(2.dp) - ) { - Row { - AnimatedText(text = inputWithPadding[6].toString(), focus = inFocus(6)) - AnimatedText(text = inputWithPadding[7].toString(), focus = inFocus(7)) - SimpleText(text = stringResource(R.string.day_short), focus = inFocus(6)) - } - Row { - AnimatedText(text = inputWithPadding[8].toString(), focus = inFocus(8)) - AnimatedText(text = inputWithPadding[9].toString(), focus = inFocus(9)) - SimpleText(text = stringResource(R.string.month_short), focus = inFocus(8)) - } - Row { - AnimatedText(text = inputWithPadding[10].toString(), focus = inFocus(10)) - AnimatedText(text = inputWithPadding[11].toString(), focus = inFocus(11)) - AnimatedText(text = inputWithPadding[12].toString(), focus = inFocus(12)) - AnimatedText(text = inputWithPadding[13].toString(), focus = inFocus(13)) - SimpleText(text = stringResource(R.string.year_short), focus = inFocus(10)) - } - } - } -} - -@Composable -private fun AnimatedText(text: String, focus: Boolean) { - AnimatedContent( - targetState = text, - transitionSpec = { - if (targetState.toInt() > initialState.toInt()) { - slideInVertically { height -> height } + fadeIn() with - slideOutVertically { height -> -height } + fadeOut() - } else { - slideInVertically { height -> -height } + fadeIn() with - slideOutVertically { height -> height } + fadeOut() - }.using( - SizeTransform(clip = false) - ) - } - ) { - SimpleText(text = text, focus = focus) - } -} - -@Composable -private fun SimpleText(text: String, focus: Boolean) { - val color = animateColorAsState( - if (focus) MaterialTheme.colorScheme.onBackground else MaterialTheme.colorScheme.outline - ) - Text( - text = text, - style = NumbersTextStyleDisplayMedium, - color = color.value - ) -} - -@Preview -@Composable -private fun PreviewDateTextField() { - var date: String by remember { mutableStateOf("2") } - - DateTextField(modifier = Modifier, date = date) - - LaunchedEffect(Unit) { - "3550002011999".forEach { - date += it.toString() - delay(2500L) - } - } -} diff --git a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/DateTextFieldUtils.kt b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/DateTextFieldUtils.kt new file mode 100644 index 00000000..6ca7165c --- /dev/null +++ b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/DateTextFieldUtils.kt @@ -0,0 +1,66 @@ +/* + * Unitto is a unit converter for Android + * Copyright (c) 2023 Elshan Agaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sadellie.unitto.feature.epoch.component + +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation + +internal object DateVisTrans : VisualTransformation { + override fun filter(text: AnnotatedString): TransformedText = TransformedText( + text = AnnotatedString(text.text.toDateMask()), + offsetMapping = offsetMapping + ) + + private val offsetMapping = object : OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + if (offset <= 2) return offset + if (offset <= 4) return offset + 1 + if (offset <= 6) return offset + 2 + if (offset <= 8) return offset + 3 + if (offset <= 10) return offset + 4 + if (offset <= 14) return offset + 5 + return 20 + } + + override fun transformedToOriginal(offset: Int): Int { + if (offset <= 2) return offset + if (offset <= 5) return offset - 1 + if (offset <= 9) return offset - 2 + if (offset <= 11) return offset - 3 + if (offset <= 14) return offset - 4 + if (offset <= 19) return offset - 5 + return 14 + } + } +} + +internal fun String.toDateMask(): String { + var maskedText = this + + if (maskedText.length > 2) maskedText = maskedText.replaceRange(2, 2, ":") + if (maskedText.length > 5) maskedText = maskedText.replaceRange(5, 5, ":") + if (maskedText.length > 8) maskedText = maskedText.replaceRange(8, 8, "\n") + if (maskedText.length > 10) maskedText = maskedText.replaceRange(11, 11, "d") + if (maskedText.length > 13) maskedText = maskedText.replaceRange(14, 14, "m") + if (maskedText.length > 18) maskedText = maskedText.replaceRange(19, 19, "y") + + return maskedText +} diff --git a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/SwapButton.kt b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/SwapButton.kt index 2fa72aa8..f4cead05 100644 --- a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/SwapButton.kt +++ b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/SwapButton.kt @@ -25,8 +25,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -41,7 +39,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @Composable -internal fun SwapButton(swap: () -> Unit) { +internal fun SwapButton( + modifier: Modifier, + swap: () -> Unit +) { val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() val cornerRadius: Int by animateIntAsState( @@ -52,9 +53,7 @@ internal fun SwapButton(swap: () -> Unit) { Button( onClick = swap, shape = RoundedCornerShape(cornerRadius), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp), + modifier = modifier, contentPadding = PaddingValues(vertical = 26.dp, horizontal = 8.dp), interactionSource = interactionSource ) { diff --git a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/TopPart.kt b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/TopPart.kt index 12a90968..867c37d5 100644 --- a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/TopPart.kt +++ b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/TopPart.kt @@ -18,37 +18,108 @@ package com.sadellie.unitto.feature.epoch.component +import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalTextInputService +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium @Composable fun TopPart( modifier: Modifier, - unixToDate: Boolean, + dateToUnix: Boolean, swap: () -> Unit, - dateField: @Composable() (LazyItemScope.() -> Unit), - unixField: @Composable() (LazyItemScope.() -> Unit), + dateValue: String, + unixValue: String, + selection: TextRange, + onCursorChange: (TextFieldValue) -> Unit ) { Column( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp) ) { - LazyColumn( - verticalArrangement = Arrangement.Bottom - ) { - if (unixToDate) { - item("unix") { unixField() } - item("date") { dateField() } + Crossfade(dateToUnix) { + if (it) { + DateUnixTextFields( + fromTextFieldValue = TextFieldValue(text = dateValue, selection = selection), + onCursorChange = onCursorChange, + fromSupportText = "date", + toTextValue = unixValue, + toSupportText = "unix", + visualTransformation = DateVisTrans, + fromPlaceholderText = dateValue.padEnd(14, '0').toDateMask(), + toPlaceholderText = "0" + ) } else { - item("date") { dateField() } - item("unix") { unixField() } + val dateMasked = dateValue.padEnd(14, '0').toDateMask() + DateUnixTextFields( + fromTextFieldValue = TextFieldValue(text = unixValue, selection = selection), + onCursorChange = onCursorChange, + fromSupportText = "unix", + toTextValue = dateMasked, + toSupportText = "date", + visualTransformation = VisualTransformation.None, + fromPlaceholderText = if (unixValue.isEmpty()) "0" else "", + toPlaceholderText = dateMasked, + ) } } - SwapButton(swap) + SwapButton(modifier = Modifier.fillMaxWidth(), swap = swap) + } +} + +@Composable +fun DateUnixTextFields( + fromTextFieldValue: TextFieldValue, + onCursorChange: (TextFieldValue) -> Unit, + fromSupportText: String, + toTextValue: String, + toSupportText: String, + visualTransformation: VisualTransformation, + fromPlaceholderText: String, + toPlaceholderText: String +) { + Column { + CompositionLocalProvider( + LocalTextInputService provides null + ) { + BasicTextField( + value = fromTextFieldValue, + onValueChange = onCursorChange, + textStyle = NumbersTextStyleDisplayMedium.copy(textAlign = TextAlign.Start), + minLines = 1, + maxLines = 2, + visualTransformation = visualTransformation, + decorationBox = { innerTextField -> + Text( + text = fromPlaceholderText, + minLines = 1, + maxLines = 2, + style = NumbersTextStyleDisplayMedium, + color = MaterialTheme.colorScheme.outline, + textAlign = TextAlign.Start + ) + innerTextField() + } + ) + } + Text(text = fromSupportText) + Text( + text = toTextValue.ifEmpty { toPlaceholderText }, + style = NumbersTextStyleDisplayMedium, + ) + Text(text = toSupportText) } } diff --git a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/UnixTextField.kt b/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/UnixTextField.kt deleted file mode 100644 index f41a9382..00000000 --- a/feature/epoch/src/main/java/com/sadellie/unitto/feature/epoch/component/UnixTextField.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Unitto is a unit converter for Android - * Copyright (c) 2023 Elshan Agaev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.sadellie.unitto.feature.epoch.component - -import androidx.compose.animation.AnimatedContent -import androidx.compose.animation.SizeTransform -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically -import androidx.compose.animation.with -import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.rememberScrollState -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium - -@Composable -fun UnixTextField( - modifier: Modifier, - unix: String -) { - Row( - modifier = modifier - .height(IntrinsicSize.Min) - .horizontalScroll(rememberScrollState()), - horizontalArrangement = Arrangement.End - ) { - AnimatedContent( - targetState = unix.ifEmpty { "0" }, - transitionSpec = { - if (targetState.toBigDecimal() > initialState.toBigDecimal()) { - slideInVertically { height -> height } + fadeIn() with - slideOutVertically { height -> -height } + fadeOut() - } else { - slideInVertically { height -> -height } + fadeIn() with - slideOutVertically { height -> height } + fadeOut() - }.using( - SizeTransform(clip = false) - ) - } - ) { - Text( - text = it, - modifier = modifier, - style = NumbersTextStyleDisplayMedium, - textAlign = TextAlign.End - ) - } - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 547e1a37..35977dcb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ androidxTestRunner = "1.5.1" androidxTestRules = "1.5.0" orgRobolectric = "4.9" orgJetbrainsKotlinxCoroutinesTest = "1.6.4" -androidxCompose = "1.4.0-alpha02" +androidxCompose = "1.4.0-alpha05" androidxComposeCompiler = "1.4.0" androidxComposeUi = "1.4.0-alpha05" androidxNavigation = "2.5.3"