fix: fixing bugs with phone input field. feat: абсолютно готов экран profile

This commit is contained in:
dany
2025-11-22 04:17:22 +03:00
parent 336472a5b8
commit 82e5066950
7 changed files with 350 additions and 83 deletions
@@ -19,6 +19,8 @@ import androidx.compose.ui.Modifier
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import com.prodhack.moscow2025.presentation.components.TBottomNavigation import com.prodhack.moscow2025.presentation.components.TBottomNavigation
import com.prodhack.moscow2025.presentation.theme.MoscowHackatonTemplateTheme import com.prodhack.moscow2025.presentation.theme.MoscowHackatonTemplateTheme
import com.prodhack.moscow2025.presentation.utils.ui.AppSnackbarVisuals
import com.prodhack.moscow2025.presentation.utils.ui.SnackbarStyle
@Composable @Composable
fun TTasksApp( fun TTasksApp(
@@ -53,10 +55,19 @@ fun TTasksApp(
SnackbarHost( SnackbarHost(
hostState = snackbarHostState, hostState = snackbarHostState,
snackbar = { data -> 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( Snackbar(
snackbarData = data, snackbarData = data,
containerColor = MaterialTheme.colorScheme.errorContainer, containerColor = containerColor,
contentColor = MaterialTheme.colorScheme.onErrorContainer, contentColor = contentColor,
shape = MaterialTheme.shapes.medium shape = MaterialTheme.shapes.medium
) )
} }
@@ -94,7 +94,10 @@ fun TTasksNavHost(
composable(AppDestination.Profile.route) composable(AppDestination.Profile.route)
{ {
ProfileScreen( ProfileScreen(
snackbarHostState = snackbarHostState snackbarHostState = snackbarHostState,
navigateToLoginScreen = {
navController.navigate(AppDestination.Login.route)
}
) )
} }
} }
@@ -95,9 +95,11 @@ class FillProfileViewModel(
} }
fun onPhoneChange(value: String) { 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 { _formStateFillProfile.update {
it.copy( it.copy(
phone = value, phone = digits,
errors = it.errors - AuthField.Phone errors = it.errors - AuthField.Phone
) )
} }
@@ -1,20 +1,36 @@
package com.prodhack.moscow2025.presentation.screens.profile 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.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding 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.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.SnackbarDuration
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -24,27 +40,38 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.prodhack.moscow2025.R import com.prodhack.moscow2025.R
import com.prodhack.moscow2025.domain.usecase.auth.AuthField import com.prodhack.moscow2025.domain.usecase.auth.AuthField
import com.prodhack.moscow2025.presentation.components.standart.BigButton 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.components.standart.TTTextField
import com.prodhack.moscow2025.presentation.theme.Paddings 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.ErrorCollectorScope
import com.prodhack.moscow2025.presentation.utils.PhoneVisualTransformation
import com.prodhack.moscow2025.presentation.utils.UIState 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 import org.koin.androidx.compose.koinViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ErrorCollectorScope.ProfileScreen( fun ErrorCollectorScope.ProfileScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
navigateToLoginScreen: () -> Unit,
viewModel: ProfileScreenViewModel = koinViewModel() viewModel: ProfileScreenViewModel = koinViewModel()
) { ) {
val typography = androidx.compose.material3.MaterialTheme.typography val typography = androidx.compose.material3.MaterialTheme.typography
val sheetState = rememberModalBottomSheetState()
val isSheetOpen = remember { mutableStateOf(false) }
val formState by viewModel.formStateFillProfile.collectAsState() val formState by viewModel.formStateFillProfile.collectAsState()
@@ -71,6 +98,7 @@ fun ErrorCollectorScope.ProfileScreen(
if (profileState is UIState.Success) { if (profileState is UIState.Success) {
snackbarHostState.showSnackbar( snackbarHostState.showSnackbar(
message = "Данные профиля обновлены", message = "Данные профиля обновлены",
style = SnackbarStyle.Success,
duration = SnackbarDuration.Short duration = SnackbarDuration.Short
) )
} }
@@ -85,26 +113,11 @@ 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( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.imePadding()
.systemBarsPadding()
.padding(horizontal = 30.dp), .padding(horizontal = 30.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top verticalArrangement = Arrangement.Top
@@ -113,7 +126,7 @@ fun ErrorCollectorScope.ProfileScreen(
Text( Text(
text = "Профиль", text = "Профиль",
style = typography.titleLarge, style = typography.titleLarge,
fontSize = 32.sp fontSize = 40.sp
) )
Spacer(Modifier.height(20.dp)) Spacer(Modifier.height(20.dp))
TTTextField( TTTextField(
@@ -138,13 +151,65 @@ fun ErrorCollectorScope.ProfileScreen(
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email) keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
) )
Spacer(Modifier.height(12.dp)) Spacer(Modifier.height(12.dp))
Row(
modifier = Modifier.height(IntrinsicSize.Min),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Paddings.medium)
) {
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( TTTextField(
value = formState.phone, value = formState.phone,
onValueChange = viewModel::onPhoneChange, onValueChange = viewModel::onPhoneChange,
label = "Телефон", label = "Телефон",
error = formState.errors[AuthField.Phone], keyboardOptions = KeyboardOptions(
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone) 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)) Spacer(modifier = Modifier.height(24.dp))
BigButton( BigButton(
@@ -153,7 +218,66 @@ fun ErrorCollectorScope.ProfileScreen(
buttonText = "Сохранить", buttonText = "Сохранить",
isLoading = profileState is UIState.Loading 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)) 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
}
)
}
}
}
}
} }
} }
@@ -6,18 +6,26 @@ import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.net.Uri import android.net.Uri
import android.provider.MediaStore import android.provider.MediaStore
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.paging.map import androidx.paging.map
import coil.ImageLoader import coil.ImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import com.prodhack.moscow2025.data.data_providers.PhoneNumberPatternsProvider
import com.prodhack.moscow2025.domain.interfaces.GalleryRepository import com.prodhack.moscow2025.domain.interfaces.GalleryRepository
import com.prodhack.moscow2025.domain.models.UpdateUserData import com.prodhack.moscow2025.domain.models.UpdateUserData
import com.prodhack.moscow2025.domain.usecase.auth.AuthField import com.prodhack.moscow2025.domain.usecase.auth.AuthField
import com.prodhack.moscow2025.domain.usecase.auth.GetUserUseCase 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.UpdateUserUseCase
import com.prodhack.moscow2025.domain.usecase.auth.ValidateAuthFieldsUseCase 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.UIState
import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel import com.prodhack.moscow2025.presentation.utils.base.BaseViewModel
import com.prodhack.moscow2025.presentation.utils.convertNumberToPattern
import com.prodhack.moscow2025.presentation.utils.toByteArray import com.prodhack.moscow2025.presentation.utils.toByteArray
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -68,6 +76,8 @@ class ProfileScreenViewModel(
private val getUserUseCase: GetUserUseCase, private val getUserUseCase: GetUserUseCase,
private val updateUserUseCase: UpdateUserUseCase, private val updateUserUseCase: UpdateUserUseCase,
private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase, private val validateAuthFieldsUseCase: ValidateAuthFieldsUseCase,
private val logOutUseCase: LogOutUseCase,
private val getDefaultPhoneNumberPatternUseCase: GetDefaultPhoneNumberPatternUseCase,
private val galleryRepository: GalleryRepository private val galleryRepository: GalleryRepository
): BaseViewModel() { ): BaseViewModel() {
private val _formStateProfile = MutableStateFlow(ProfileState()) private val _formStateProfile = MutableStateFlow(ProfileState())
@@ -76,6 +86,9 @@ class ProfileScreenViewModel(
private val _profileState = MutableUIStateFlow<String>() private val _profileState = MutableUIStateFlow<String>()
val profileState: StateFlow<UIState<String>> = _profileState val profileState: StateFlow<UIState<String>> = _profileState
val chosenPattern = mutableStateOf<UIPhoneNumberPattern?>(null)
val phoneNumberPatterns = mutableStateListOf<UIPhoneNumberPattern>()
fun onEmailChange(value: String) { fun onEmailChange(value: String) {
_formStateProfile.update { _formStateProfile.update {
it.copy( it.copy(
@@ -104,9 +117,11 @@ class ProfileScreenViewModel(
} }
fun onPhoneChange(value: String) { 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 { _formStateProfile.update {
it.copy( it.copy(
phone = value, phone = digits,
errors = it.errors - AuthField.Phone errors = it.errors - AuthField.Phone
) )
} }
@@ -167,38 +182,93 @@ class ProfileScreenViewModel(
phone = _formStateProfile.value.phone phone = _formStateProfile.value.phone
) )
if (!validation.isValid) { val errors = validation.errors.toMutableMap()
_formStateProfile.update { it.copy(errors = validation.errors) }
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 return@launch
} }
_profileState.emit(UIState.Loading()) _profileState.emit(UIState.Loading())
val formattedPhone = pattern?.mapToDomain()?.let { phonePattern ->
convertNumberToPattern(phonePattern, _formStateProfile.value.phone)
} ?: _formStateProfile.value.phone
val result = updateUserUseCase( val result = updateUserUseCase(
UpdateUserData( UpdateUserData(
firstName = _formStateProfile.value.firstName, firstName = _formStateProfile.value.firstName,
lastName = _formStateProfile.value.lastName, lastName = _formStateProfile.value.lastName,
email = _formStateProfile.value.email, email = _formStateProfile.value.email,
phone = _formStateProfile.value.phone phone = formattedPhone
) )
) )
result.map { it.id }.collectRequest(_profileState) result.map { it.id }.collectRequest(_profileState)
} }
} }
fun logout() {
viewModelScope.launch {
logOutUseCase()
}
}
init { init {
viewModelScope.launch { viewModelScope.launch {
loadPhonePatterns()
val user = getUserUseCase().getOrNull() val user = getUserUseCase().getOrNull()
if (user != null) { 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 { _formStateProfile.update {
it.copy( it.copy(
firstName = user.firstName.orEmpty(), firstName = user.firstName.orEmpty(),
lastName = user.lastName.orEmpty(), lastName = user.lastName.orEmpty(),
email = user.email, email = user.email,
phone = user.phone.orEmpty() phone = digitsWithoutCode.take(maxDigits)
) )
} }
} }
} }
} }
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()
}
}
} }
@@ -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 { 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 { override fun originalToTransformed(offset: Int): Int {
var noneDigitCount = 0 if (offset <= 0) return 0
var i = 0 var digitsSeen = 0
while (i < offset + noneDigitCount) { var index = 0
if (mask[i++] != numberChar) noneDigitCount++ val targetDigits = offset.coerceAtMost(maxDigits)
while (index < mask.length && digitsSeen < targetDigits) {
if (mask[index] == numberChar) {
digitsSeen++
} }
return offset + noneDigitCount index++
}
return index.coerceAtMost(transformedLength)
} }
override fun transformedToOriginal(offset: Int): Int = 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 { fun convertNumberToPattern(pattern: PhoneNumberPattern, number: String): String {
@@ -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
)
)