From 82e5066950643b15ff14a0c96eb02f5ece24c12d Mon Sep 17 00:00:00 2001 From: dany Date: Sat, 22 Nov 2025 04:17:22 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20fixing=20bugs=20with=20phone=20input=20f?= =?UTF-8?q?ield.=20feat:=20=D0=B0=D0=B1=D1=81=D0=BE=D0=BB=D1=8E=D1=82?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=20=D1=8D=D0=BA?= =?UTF-8?q?=D1=80=D0=B0=D0=BD=20profile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/navigation/TTasksApp.kt | 15 +- .../presentation/navigation/TTasksNavHost.kt | 5 +- .../fillProfile/FillProfileViewModel.kt | 4 +- .../screens/profile/ProfileScreen.kt | 252 +++++++++++++----- .../screens/profile/ProfileScreenViewModel.kt | 82 +++++- .../presentation/utils/PhoneTransformation.kt | 41 ++- .../presentation/utils/ui/AppSnackbar.kt | 34 +++ 7 files changed, 350 insertions(+), 83 deletions(-) create mode 100644 app/src/main/java/com/prodhack/moscow2025/presentation/utils/ui/AppSnackbar.kt diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksApp.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksApp.kt index 56a4234..924fe40 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksApp.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksApp.kt @@ -19,6 +19,8 @@ import androidx.compose.ui.Modifier import androidx.navigation.compose.currentBackStackEntryAsState import com.prodhack.moscow2025.presentation.components.TBottomNavigation import com.prodhack.moscow2025.presentation.theme.MoscowHackatonTemplateTheme +import com.prodhack.moscow2025.presentation.utils.ui.AppSnackbarVisuals +import com.prodhack.moscow2025.presentation.utils.ui.SnackbarStyle @Composable fun TTasksApp( @@ -53,10 +55,19 @@ fun TTasksApp( SnackbarHost( hostState = snackbarHostState, snackbar = { data -> + val style = (data.visuals as? AppSnackbarVisuals)?.style ?: SnackbarStyle.Error + val containerColor = when (style) { + SnackbarStyle.Success -> MaterialTheme.colorScheme.tertiaryContainer + SnackbarStyle.Error -> MaterialTheme.colorScheme.errorContainer + } + val contentColor = when (style) { + SnackbarStyle.Success -> MaterialTheme.colorScheme.onTertiaryContainer + SnackbarStyle.Error -> MaterialTheme.colorScheme.onErrorContainer + } Snackbar( snackbarData = data, - containerColor = MaterialTheme.colorScheme.errorContainer, - contentColor = MaterialTheme.colorScheme.onErrorContainer, + containerColor = containerColor, + contentColor = contentColor, shape = MaterialTheme.shapes.medium ) } diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt index 9a85457..e20efba 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/navigation/TTasksNavHost.kt @@ -94,7 +94,10 @@ fun TTasksNavHost( composable(AppDestination.Profile.route) { ProfileScreen( - snackbarHostState = snackbarHostState + snackbarHostState = snackbarHostState, + navigateToLoginScreen = { + navController.navigate(AppDestination.Login.route) + } ) } } diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileViewModel.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileViewModel.kt index 982b6f8..2ee6f25 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileViewModel.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/fillProfile/FillProfileViewModel.kt @@ -95,9 +95,11 @@ class FillProfileViewModel( } fun onPhoneChange(value: String) { + val maxDigits = chosenPattern.value?.pattern?.count { it == '0' } ?: Int.MAX_VALUE + val digits = value.filter { it.isDigit() }.take(maxDigits) _formStateFillProfile.update { it.copy( - phone = value, + phone = digits, errors = it.errors - AuthField.Phone ) } diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreen.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreen.kt index 2b3280c..b577a53 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreen.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreen.kt @@ -1,20 +1,36 @@ package com.prodhack.moscow2025.presentation.screens.profile -import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -24,27 +40,38 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.prodhack.moscow2025.R import com.prodhack.moscow2025.domain.usecase.auth.AuthField import com.prodhack.moscow2025.presentation.components.standart.BigButton +import com.prodhack.moscow2025.presentation.components.standart.FieldWrapper import com.prodhack.moscow2025.presentation.components.standart.TTTextField import com.prodhack.moscow2025.presentation.theme.Paddings +import com.prodhack.moscow2025.presentation.theme.Shapes import com.prodhack.moscow2025.presentation.utils.ErrorCollectorScope +import com.prodhack.moscow2025.presentation.utils.PhoneVisualTransformation import com.prodhack.moscow2025.presentation.utils.UIState +import com.prodhack.moscow2025.presentation.utils.ui.SnackbarStyle +import com.prodhack.moscow2025.presentation.utils.ui.showSnackbar import org.koin.androidx.compose.koinViewModel +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ErrorCollectorScope.ProfileScreen( modifier: Modifier = Modifier, snackbarHostState: SnackbarHostState, + navigateToLoginScreen: () -> Unit, viewModel: ProfileScreenViewModel = koinViewModel() ) { val typography = androidx.compose.material3.MaterialTheme.typography + val sheetState = rememberModalBottomSheetState() + val isSheetOpen = remember { mutableStateOf(false) } val formState by viewModel.formStateFillProfile.collectAsState() @@ -71,6 +98,7 @@ fun ErrorCollectorScope.ProfileScreen( if (profileState is UIState.Success) { snackbarHostState.showSnackbar( message = "Данные профиля обновлены", + style = SnackbarStyle.Success, duration = SnackbarDuration.Short ) } @@ -85,75 +113,171 @@ fun ErrorCollectorScope.ProfileScreen( } } - Box( - modifier = modifier - .fillMaxSize() - .imePadding() - .systemBarsPadding(), - contentAlignment = Alignment.BottomStart - ) { - Image( - painter = painterResource(R.drawable.lottie), - contentDescription = null, - modifier = Modifier - .align(Alignment.BottomStart) - .padding(start = 16.dp) - .fillMaxWidth(0.35f), - contentScale = ContentScale.FillWidth - ) - - Column( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 30.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Top + Column( + modifier = Modifier + .fillMaxSize() + .imePadding() + .systemBarsPadding() + .padding(horizontal = 30.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top + ) { + Spacer(Modifier.height(32.dp)) + Text( + text = "Профиль", + style = typography.titleLarge, + fontSize = 40.sp + ) + Spacer(Modifier.height(20.dp)) + TTTextField( + value = formState.firstName, + onValueChange = viewModel::onFirstNameChange, + label = "Имя", + error = formState.errors[AuthField.FirstName], + ) + Spacer(Modifier.height(12.dp)) + TTTextField( + value = formState.lastName, + onValueChange = viewModel::onLastNameChange, + label = "Фамилия", + error = formState.errors[AuthField.LastName], + ) + Spacer(Modifier.height(12.dp)) + TTTextField( + value = formState.email, + onValueChange = viewModel::onEmailChange, + label = "Email", + error = formState.errors[AuthField.Email], + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email) + ) + Spacer(Modifier.height(12.dp)) + Row( + modifier = Modifier.height(IntrinsicSize.Min), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(Paddings.medium) ) { - Spacer(Modifier.height(32.dp)) - Text( - text = "Профиль", - style = typography.titleLarge, - fontSize = 32.sp - ) - Spacer(Modifier.height(20.dp)) - TTTextField( - value = formState.firstName, - onValueChange = viewModel::onFirstNameChange, - label = "Имя", - error = formState.errors[AuthField.FirstName], - ) - Spacer(Modifier.height(12.dp)) - TTTextField( - value = formState.lastName, - onValueChange = viewModel::onLastNameChange, - label = "Фамилия", - error = formState.errors[AuthField.LastName], - ) - Spacer(Modifier.height(12.dp)) - TTTextField( - value = formState.email, - onValueChange = viewModel::onEmailChange, - label = "Email", - error = formState.errors[AuthField.Email], - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email) - ) - Spacer(Modifier.height(12.dp)) + FieldWrapper( + modifier = Modifier + .width(IntrinsicSize.Min) + .fillMaxHeight() + ) { + BasicTextField( + modifier = Modifier + .fillMaxSize() + .padding(bottom = 16.dp) + .clip(Shapes.smallRoundedBox) + .background( + color = colorScheme.primary, + shape = Shapes.smallRoundedBox + ), + value = viewModel.chosenPattern.value?.prefix ?: "", + onValueChange = {}, + readOnly = true, + textStyle = TextStyle( + color = colorScheme.onPrimary + ), + decorationBox = { innerTextField -> + Row( + modifier = Modifier + .clickable { isSheetOpen.value = true } + .padding(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box(modifier = Modifier.weight(1f)) { + innerTextField() + } + Icon( + modifier = Modifier.size(15.dp), + painter = painterResource(R.drawable.ic_arr_dropdown), + tint = colorScheme.onPrimary, + contentDescription = null + ) + } + } + ) + } + TTTextField( value = formState.phone, onValueChange = viewModel::onPhoneChange, label = "Телефон", - error = formState.errors[AuthField.Phone], - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone) + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Phone + ), + visualTransformation = viewModel.chosenPattern.value?.pattern?.let { + PhoneVisualTransformation(it, '0') + } ?: VisualTransformation.None, + error = formState.errors[AuthField.Phone] ) + } - Spacer(modifier = Modifier.height(24.dp)) - BigButton( - onClick = viewModel::submit, - modifier = Modifier.fillMaxWidth(), - buttonText = "Сохранить", - isLoading = profileState is UIState.Loading - ) - Spacer(modifier = Modifier.height(48.dp)) + Spacer(modifier = Modifier.height(24.dp)) + BigButton( + onClick = viewModel::submit, + modifier = Modifier.fillMaxWidth(), + buttonText = "Сохранить", + isLoading = profileState is UIState.Loading + ) + Spacer(Modifier.height(15.dp)) + Button( + modifier = modifier + .fillMaxWidth() + .height(60.dp), + shape = Shapes.smallRoundedBox, + onClick = { + viewModel.logout() + navigateToLoginScreen() + }, + colors = ButtonColors( + containerColor = colorScheme.errorContainer, + contentColor = colorScheme.onErrorContainer, + disabledContainerColor = colorScheme.errorContainer, + disabledContentColor = colorScheme.onErrorContainer + ) + ) { + + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "Выйти из аккаунта", + style = typography.titleMedium, + fontSize = 24.sp + ) + Spacer(Modifier.width(Paddings.small)) + Icon( + painter = painterResource(R.drawable.logout_icon), + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + } + } + Spacer(modifier = Modifier.height(48.dp)) + } + + if (isSheetOpen.value) { + ModalBottomSheet( + sheetState = sheetState, + onDismissRequest = { isSheetOpen.value = false }, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + LazyColumn(modifier = Modifier.fillMaxSize()) { + items(viewModel.phoneNumberPatterns) { pattern -> + Text( + text = pattern.name, + modifier = Modifier + .fillMaxWidth() + .padding(10.dp) + .clickable { + viewModel.chosenPattern.value = pattern + viewModel.onPhoneChange(formState.phone) + isSheetOpen.value = false + } + ) + } + } + } } } } diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreenViewModel.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreenViewModel.kt index 60669ed..763b83a 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreenViewModel.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/screens/profile/ProfileScreenViewModel.kt @@ -6,18 +6,26 @@ import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable import android.net.Uri import android.provider.MediaStore +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.viewModelScope import androidx.paging.map import coil.ImageLoader import coil.request.ImageRequest +import com.prodhack.moscow2025.data.data_providers.PhoneNumberPatternsProvider import com.prodhack.moscow2025.domain.interfaces.GalleryRepository import com.prodhack.moscow2025.domain.models.UpdateUserData import com.prodhack.moscow2025.domain.usecase.auth.AuthField import com.prodhack.moscow2025.domain.usecase.auth.GetUserUseCase +import com.prodhack.moscow2025.domain.usecase.auth.LogOutUseCase import com.prodhack.moscow2025.domain.usecase.auth.UpdateUserUseCase import com.prodhack.moscow2025.domain.usecase.auth.ValidateAuthFieldsUseCase +import com.prodhack.moscow2025.domain.usecase.GetDefaultPhoneNumberPatternUseCase +import com.prodhack.moscow2025.presentation.screens.fillProfile.UIPhoneNumberPattern +import com.prodhack.moscow2025.presentation.screens.fillProfile.mapToUI import com.prodhack.moscow2025.presentation.utils.UIState import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel +import com.prodhack.moscow2025.presentation.utils.convertNumberToPattern import com.prodhack.moscow2025.presentation.utils.toByteArray import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -68,6 +76,8 @@ class ProfileScreenViewModel( private val getUserUseCase: GetUserUseCase, private val updateUserUseCase: UpdateUserUseCase, private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase, + private val logOutUseCase: LogOutUseCase, + private val getDefaultPhoneNumberPatternUseCase: GetDefaultPhoneNumberPatternUseCase, private val galleryRepository: GalleryRepository ): BaseViewModel() { private val _formStateProfile = MutableStateFlow(ProfileState()) @@ -76,6 +86,9 @@ class ProfileScreenViewModel( private val _profileState = MutableUIStateFlow() val profileState: StateFlow> = _profileState + val chosenPattern = mutableStateOf(null) + val phoneNumberPatterns = mutableStateListOf() + fun onEmailChange(value: String) { _formStateProfile.update { it.copy( @@ -104,9 +117,11 @@ class ProfileScreenViewModel( } fun onPhoneChange(value: String) { + val maxDigits = chosenPattern.value?.pattern?.count { it == '0' } ?: Int.MAX_VALUE + val digits = value.filter { it.isDigit() }.take(maxDigits) _formStateProfile.update { it.copy( - phone = value, + phone = digits, errors = it.errors - AuthField.Phone ) } @@ -167,38 +182,93 @@ class ProfileScreenViewModel( phone = _formStateProfile.value.phone ) - if (!validation.isValid) { - _formStateProfile.update { it.copy(errors = validation.errors) } + val errors = validation.errors.toMutableMap() + + val pattern = chosenPattern.value + if (pattern == null) { + errors[AuthField.Phone] = "Выберите код страны" + } else { + val expectedDigits = pattern.pattern.count { it == '0' } + if (_formStateProfile.value.phone.length != expectedDigits) { + errors[AuthField.Phone] = "Номер должен содержать $expectedDigits цифр" + } + } + + if (errors.isNotEmpty()) { + _formStateProfile.update { it.copy(errors = errors) } return@launch } _profileState.emit(UIState.Loading()) + val formattedPhone = pattern?.mapToDomain()?.let { phonePattern -> + convertNumberToPattern(phonePattern, _formStateProfile.value.phone) + } ?: _formStateProfile.value.phone + val result = updateUserUseCase( UpdateUserData( firstName = _formStateProfile.value.firstName, lastName = _formStateProfile.value.lastName, email = _formStateProfile.value.email, - phone = _formStateProfile.value.phone + phone = formattedPhone ) ) result.map { it.id }.collectRequest(_profileState) } } + fun logout() { + viewModelScope.launch { + logOutUseCase() + } + } + init { viewModelScope.launch { + loadPhonePatterns() + val user = getUserUseCase().getOrNull() if (user != null) { + val digits = user.phone.orEmpty().filter { it.isDigit() } + val selectedPattern = phoneNumberPatterns.firstOrNull { pattern -> + val codeDigits = pattern.countryCode.filter { it.isDigit() } + digits.startsWith(codeDigits) && digits.length >= codeDigits.length + } ?: phoneNumberPatterns.firstOrNull { + it.countryCodeISO.equals( + getDefaultPhoneNumberPatternUseCase.execute()?.countryCodeISO, + ignoreCase = true + ) + } ?: phoneNumberPatterns.firstOrNull() + + selectedPattern?.let { chosenPattern.value = it } + + val digitsWithoutCode = selectedPattern?.let { + val codeDigits = it.countryCode.filter { d -> d.isDigit() } + if (digits.startsWith(codeDigits)) digits.drop(codeDigits.length) else digits + } ?: digits + + val maxDigits = selectedPattern?.pattern?.count { it == '0' } ?: Int.MAX_VALUE + _formStateProfile.update { it.copy( firstName = user.firstName.orEmpty(), lastName = user.lastName.orEmpty(), email = user.email, - phone = user.phone.orEmpty() + phone = digitsWithoutCode.take(maxDigits) ) } } } } -} \ No newline at end of file + + private fun loadPhonePatterns() { + phoneNumberPatterns.clear() + phoneNumberPatterns.addAll( + PhoneNumberPatternsProvider.phoneNumberPatterns.map { it.mapToUI() } + ) + if (chosenPattern.value == null) { + val defaultPattern = getDefaultPhoneNumberPatternUseCase.execute()?.mapToUI() + chosenPattern.value = defaultPattern ?: phoneNumberPatterns.firstOrNull() + } + } +} diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/utils/PhoneTransformation.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/utils/PhoneTransformation.kt index 46dad8b..d23b318 100644 --- a/app/src/main/java/com/prodhack/moscow2025/presentation/utils/PhoneTransformation.kt +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/utils/PhoneTransformation.kt @@ -29,7 +29,19 @@ class PhoneVisualTransformation(val mask: String, val maskNumber: Char) : Visual } } - return TransformedText(annotatedString, PhoneOffsetMapper(mask, maskNumber)) + if (annotatedString.isEmpty()) { + return TransformedText(annotatedString, OffsetMapping.Identity) + } + + return TransformedText( + annotatedString, + PhoneOffsetMapper( + mask = mask, + numberChar = maskNumber, + transformedLength = annotatedString.length, + maxDigits = trimmed.length + ) + ) } override fun equals(other: Any?): Boolean { @@ -45,19 +57,30 @@ class PhoneVisualTransformation(val mask: String, val maskNumber: Char) : Visual } } -private class PhoneOffsetMapper(val mask: String, val numberChar: Char) : OffsetMapping { +private class PhoneOffsetMapper( + val mask: String, + val numberChar: Char, + private val transformedLength: Int, + private val maxDigits: Int +) : OffsetMapping { override fun originalToTransformed(offset: Int): Int { - var noneDigitCount = 0 - var i = 0 - while (i < offset + noneDigitCount) { - if (mask[i++] != numberChar) noneDigitCount++ + if (offset <= 0) return 0 + var digitsSeen = 0 + var index = 0 + val targetDigits = offset.coerceAtMost(maxDigits) + + while (index < mask.length && digitsSeen < targetDigits) { + if (mask[index] == numberChar) { + digitsSeen++ + } + index++ } - return offset + noneDigitCount + return index.coerceAtMost(transformedLength) } override fun transformedToOriginal(offset: Int): Int = - offset - mask.take(offset).count { it != numberChar } + mask.take(offset.coerceAtMost(transformedLength)).count { it == numberChar } } fun convertNumberToPattern(pattern: PhoneNumberPattern, number: String): String { @@ -66,4 +89,4 @@ fun convertNumberToPattern(pattern: PhoneNumberPattern, number: String): String answer = answer.replaceFirst('*', i) } return "${pattern.countryCode} $answer" -} \ No newline at end of file +} diff --git a/app/src/main/java/com/prodhack/moscow2025/presentation/utils/ui/AppSnackbar.kt b/app/src/main/java/com/prodhack/moscow2025/presentation/utils/ui/AppSnackbar.kt new file mode 100644 index 0000000..055a512 --- /dev/null +++ b/app/src/main/java/com/prodhack/moscow2025/presentation/utils/ui/AppSnackbar.kt @@ -0,0 +1,34 @@ +package com.prodhack.moscow2025.presentation.utils.ui + +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarVisuals + +enum class SnackbarStyle { + Success, + Error, +} + +data class AppSnackbarVisuals( + override val message: String, + override val actionLabel: String? = null, + override val withDismissAction: Boolean = false, + override val duration: SnackbarDuration = SnackbarDuration.Short, + val style: SnackbarStyle = SnackbarStyle.Error +) : SnackbarVisuals + +suspend fun SnackbarHostState.showSnackbar( + message: String, + style: SnackbarStyle, + actionLabel: String? = null, + withDismissAction: Boolean = false, + duration: SnackbarDuration = SnackbarDuration.Short +) = showSnackbar( + AppSnackbarVisuals( + message = message, + actionLabel = actionLabel, + withDismissAction = withDismissAction, + duration = duration, + style = style + ) +)