package com.suno.android.ui.screens.onboarding.phone.input import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row 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.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.rememberModalBottomSheetState 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.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.suno.android.common_ui.components.loading.SunoSpinner import com.suno.android.common_ui.components.omni.CircleIconButton import com.suno.android.common_ui.theme.ExtendedTheme import com.suno.android.common_ui.theme.NavBarSelection import com.suno.android.common_ui.theme.SunoTheme import com.suno.android.ui.bottom_sheets.country_picker.CountryPickerBottomSheet import kotlinx.coroutines.launch import kotlinx.serialization.Serializable import com.suno.android.common_res.R as CommonResR import com.suno.android.common_ui.R as CommonUIR @Composable fun LoginPhoneNumberScreen( vm: LoginPhoneNumberScreenVM?, onPhoneNumberEntered: (PhoneNumberAuthPayload) -> Unit, onBackPressed: () -> Unit, ) { val viewState = vm?.state?.collectAsStateWithLifecycle()?.value var phoneNumberValue by remember { mutableStateOf("") } val countryPickerSheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true, ) var showCountryPickerBottomSheet by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() LaunchedEffect(Unit) { vm?.effects?.collect { effect -> when (effect) { is LoginPhoneNumberScreenEffect.OnPhoneNumberSubmittedSuccessfully -> { onPhoneNumberEntered.invoke( PhoneNumberAuthPayload( signInId = effect.signInId, signUpId = effect.signUpId, phoneNumber = effect.phoneNumber, ), ) } LoginPhoneNumberScreenEffect.PhoneNumberAlreadyExists -> { vm.sendEvent( LoginPhoneNumberScreenEvent.AttemptToSignInWithPhoneNumberEvent( phoneNumber = phoneNumberValue, ), ) } is LoginPhoneNumberScreenEffect.OTPCodeRequestError -> { // todo: actually handle this error? show a snackbar? right now user sees nothing } } } } Column( modifier = Modifier .fillMaxSize() .imePadding() .background(ExtendedTheme.colors.backgroundPrimary), horizontalAlignment = Alignment.CenterHorizontally, ) { Row( modifier = Modifier .navigationBarsPadding() .systemBarsPadding() .fillMaxWidth() .height(64.dp) .padding(8.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { CircleIconButton( vectorDrawableResId = CommonUIR.drawable.arrow_left, contentDescription = stringResource(CommonResR.string.back), onClick = { onBackPressed() }, ) Text( text = stringResource(CommonResR.string.phone_entry_navigation_title), style = ExtendedTheme.typography.largeBody, color = ExtendedTheme.colors.textPrimary, ) CircleIconButton( modifier = Modifier.alpha(0f), vectorDrawableResId = CommonUIR.drawable.close, contentDescription = stringResource(CommonResR.string.close), ) } Column( modifier = Modifier .fillMaxHeight() .padding(horizontal = 12.dp), verticalArrangement = Arrangement.Center, ) { Column( modifier = Modifier.weight(1f), verticalArrangement = Arrangement.Center, ) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp), ) { val countryCodeString = stringResource(CommonResR.string.country_code) Row( modifier = Modifier .height(60.dp) .clip(RoundedCornerShape(8.dp)) .padding(start = 8.dp) .semantics(mergeDescendants = true) { contentDescription = viewState?.selectedCountry?.code ?: countryCodeString } .clickable { scope.launch { showCountryPickerBottomSheet = true countryPickerSheetState.show() } }, verticalAlignment = Alignment.CenterVertically, ) { Text( text = viewState?.selectedCountry?.flagEmoji ?: "", ) Icon( modifier = Modifier.padding(start = 8.dp), imageVector = ImageVector.vectorResource( id = CommonUIR.drawable.chevron_down, ), contentDescription = null, tint = ExtendedTheme.colors.textTertiary, ) Text( text = viewState?.selectedCountry?.dialCode ?: "", style = ExtendedTheme.typography.largeBody, color = ExtendedTheme.colors.textPrimary, ) } TextField( modifier = Modifier .clip(RoundedCornerShape(8.dp)) .height(60.dp) .fillMaxWidth(), value = viewState?.phoneNumber ?: "", onValueChange = { it: String -> phoneNumberValue = it vm?.sendEvent( LoginPhoneNumberScreenEvent.OnPhoneNumberEnteredEvent( phoneNumber = it, ), ) }, keyboardOptions = KeyboardOptions.Default.copy( keyboardType = KeyboardType.Phone, imeAction = ImeAction.Done, ), placeholder = { Text( modifier = Modifier.fillMaxWidth(), text = viewState?.selectedCountry?.examplePhoneNumber ?: stringResource(CommonResR.string.phone_number), style = ExtendedTheme.typography.largeBody, color = ExtendedTheme.colors.textTertiary, ) }, textStyle = ExtendedTheme.typography.largeBody, colors = TextFieldDefaults.colors( unfocusedContainerColor = Color.Transparent, focusedContainerColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, focusedTextColor = ExtendedTheme.colors.textPrimary, unfocusedTextColor = ExtendedTheme.colors.textPrimary, cursorColor = NavBarSelection, ), singleLine = true, ) } } Button( modifier = Modifier .fillMaxWidth() .padding(8.dp) .navigationBarsPadding(), shape = RoundedCornerShape(8.dp), colors = ButtonDefaults.buttonColors( ExtendedTheme.colors.backgroundInvert, ), enabled = phoneNumberValue.isNotBlank() && viewState?.loading == false, onClick = { vm?.sendEvent( LoginPhoneNumberScreenEvent.AttemptToSignUpWithPhoneNumberEvent( phoneNumber = phoneNumberValue, ), ) }, ) { if (viewState?.loading == true) { SunoSpinner( modifier = Modifier .alpha(0.5f) .padding(horizontal = 8.dp) .size(32.dp), ) } Text( modifier = Modifier.padding(8.dp), text = if (viewState?.loading == true) { stringResource(CommonResR.string.loading) } else { stringResource( CommonResR.string.confirm, ) }, color = ExtendedTheme.colors.backgroundPrimary, ) } } } if (showCountryPickerBottomSheet) { CountryPickerBottomSheet( sheetState = countryPickerSheetState, onCountrySelected = { country -> vm?.sendEvent( LoginPhoneNumberScreenEvent.SetCountryEvent( country = country, ), ) }, onDismiss = { scope.launch { countryPickerSheetState.hide() showCountryPickerBottomSheet = false } }, ) } } @Serializable data class PhoneNumberAuthPayload( val signInId: String? = null, val signUpId: String? = null, val phoneNumber: String, ) @Preview() @Composable private fun LoginPhoneNumberScreen_Preview() { SunoTheme { LoginPhoneNumberScreen( vm = null, onPhoneNumberEntered = {}, onBackPressed = {}, ) } }