package com.example.dattingapp.registration import android.content.Intent import android.os.Bundle import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material.icons.filled.AlternateEmail import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.Badge import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Mail import androidx.compose.material.icons.filled.PhoneIphone import androidx.compose.material.icons.filled.RadioButtonUnchecked import androidx.compose.material.icons.filled.SmartToy import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.dattingapp.ui.theme.InputBg import com.example.dattingapp.ui.theme.LightBorderColor import com.example.dattingapp.ui.theme.PinkGradientStart import com.example.dattingapp.ui.theme.PurpleGradientEnd import com.example.dattingapp.ui.theme.textDark import com.example.dattingapp.ui.theme.textPink import androidx.compose.material.icons.filled.Cake import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.viewmodel.compose.viewModel import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date import java.util.Locale import androidx.compose.animation.core.* import androidx.compose.material.icons.filled.CompassCalibration import androidx.compose.material.icons.filled.FavoriteBorder import androidx.compose.material.icons.filled.LocalBar import androidx.compose.material.icons.filled.People import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.draw.scale import android.annotation.SuppressLint import android.content.Context import android.content.pm.PackageManager import android.location.LocationManager import android.os.Looper import android.Manifest import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.material.icons.filled.LocationOn // Permission check import androidx.core.content.ContextCompat import com.example.dattingapp.homes.networkandapi.TokenManager class RegistrationScreen : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) setContent { MaterialTheme { RegistrationFlowContainer() } } } } @Composable fun RegistrationFlowContainer( registrationViewModel: RegistrationViewModel = viewModel() ) { val context = LocalContext.current val uiState by registrationViewModel.uiState.collectAsState() var currentStep by remember { mutableIntStateOf(1) } val totalSteps = 7 BackHandler(enabled = currentStep > 1 && uiState !is RegistrationUiState.Loading) { currentStep-- } // Input States var nameInput by rememberSaveable { mutableStateOf("") } var nameError by rememberSaveable { mutableStateOf(null) } val nameRegex = remember { Regex("^[a-zA-Z\\s]+$") } var selectedTab by rememberSaveable { mutableIntStateOf(0) } var emailInput by rememberSaveable { mutableStateOf("") } var phoneInput by rememberSaveable { mutableStateOf("") } var countryCode by rememberSaveable { mutableStateOf("+86") } var emailError by rememberSaveable { mutableStateOf(null) } var phoneError by rememberSaveable { mutableStateOf(null) } var passwordInput by remember { mutableStateOf("") } var confirmPasswordInput by remember { mutableStateOf("") } var passwordError by remember { mutableStateOf(null) } var confirmPasswordError by remember { mutableStateOf(null) } var selectedBirthDateMillis by rememberSaveable { mutableStateOf(null) } // var addressInput by rememberSaveable { mutableStateOf("") } var selectedGender by rememberSaveable { mutableStateOf("") } var selectedRealationStatus by remember { mutableStateOf("") } var userLat by rememberSaveable { mutableStateOf(null) } var userLng by rememberSaveable { mutableStateOf(null) } val scrollState = rememberScrollState() // Handle Network Success & Error Reactions LaunchedEffect(uiState) { when (val state = uiState) { is RegistrationUiState.Success -> { Toast.makeText(context, "Registration Successful!", Toast.LENGTH_SHORT).show() val tokenManager = TokenManager.getInstance(context) tokenManager.saveTokens( accessToken = state.response.accessToken, refreshToken = state.response.refreshToken ) // Navigate to UploadProfileImageActivity and finish flow val intent = Intent(context, UploadProfileImageActivity::class.java).apply { putExtra("ACCESS_TOKEN", state.response.accessToken) putExtra("REFRESH_TOKEN", state.response.refreshToken) flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } context.startActivity(intent) } is RegistrationUiState.Error -> { Toast.makeText(context, state.message, Toast.LENGTH_LONG).show() } else -> {} } } Box( modifier = Modifier .fillMaxSize() .statusBarsPadding() .navigationBarsPadding() .imePadding() ) { // Show Full Screen Loading Overlay if API call is active if (uiState is RegistrationUiState.Loading) { Box( modifier = Modifier .fillMaxSize() .background(Color.White.copy(alpha = 0.95f)), contentAlignment = Alignment.Center ) { UniquePulseLoader(message = "Setting up your profile...") } } else { Column( modifier = Modifier .fillMaxSize() .verticalScroll(scrollState) .padding(horizontal = 24.dp) ) { Spacer(modifier = Modifier.height(16.dp)) // 1. App Bar Header Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { IconButton( onClick = { if (currentStep > 1) currentStep-- }, enabled = currentStep > 1 ) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", tint = if (currentStep > 1) textDark else Color.Transparent ) } Text( text = "VibeCheck", style = MaterialTheme.typography.titleLarge.copy( fontWeight = FontWeight.Bold, color = PurpleGradientEnd, fontSize = 24.sp ) ) TextButton(onClick = { /* Skip */ }) { Text(text = "SKIP", color = Color.Gray, fontWeight = FontWeight.Medium) } } Spacer(modifier = Modifier.height(16.dp)) // 2. Step Indicator Text Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Text( text = "STEP $currentStep OF $totalSteps", color = textPink, fontWeight = FontWeight.Bold, fontSize = 12.sp ) Text( text = when (currentStep) { 1 -> "Getting Started" 2 -> "Contact Details" 3 -> "Password" 4 -> "Date of Birth" 5 -> "Location" 6 -> "Interested Gender" 7-> "Relationship status" else -> "" }, color = Color.Gray, fontSize = 12.sp ) } Spacer(modifier = Modifier.height(8.dp)) // 3. Step Progress Bar Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp) ) { for (i in 1..totalSteps) { Box( modifier = Modifier .weight(1f) .height(6.dp) .clip(RoundedCornerShape(3.dp)) .background( if (i <= currentStep) Brush.horizontalGradient(listOf(PinkGradientStart, PurpleGradientEnd)) else Brush.horizontalGradient(listOf(Color(0xFFE0E0E0), Color(0xFFE0E0E0))) ) ) } } Spacer(modifier = Modifier.height(20.dp)) // 4. Avatar Frame Box( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center ) { Box( modifier = Modifier .size(90.dp) .clip(RoundedCornerShape(20.dp)) .background(Color(0xFFFDE8ED)), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Default.SmartToy, contentDescription = "Mascot", tint = PinkGradientStart, modifier = Modifier.size(50.dp) ) } } Spacer(modifier = Modifier.height(20.dp)) // 5. Dynamic Flow Steps AnimatedContent( targetState = currentStep, label = "StepAnimation" ) { step -> when (step) { 1 -> NameCompose( name = nameInput, errorMessage = nameError, onNameChange = { nameInput = it; nameError = null } ) 2 -> EmailorPhoneCompose( selectedTab = selectedTab, onTabSelected = { selectedTab = it; emailError = null; phoneError = null }, emailText = emailInput, onEmailChange = { emailInput = it; emailError = null }, phoneText = phoneInput, onPhoneChange = { if (it.all { char -> char.isDigit() }) { phoneInput = it; phoneError = null } }, countryCode = countryCode, emailError = emailError, phoneError = phoneError, isEmailValid = emailInput.trim().matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$".toRegex()), isPhoneValid = phoneInput.trim().length in 10..15 ) 3 -> PasswordComose( passwordInput = passwordInput, onPasswordChange = { passwordInput = it; passwordError = null }, confirmPasswordInput = confirmPasswordInput, onConfirmPasswordChange = { confirmPasswordInput = it; confirmPasswordError = null }, passwordError = passwordError, confirmPasswordError = confirmPasswordError ) 4 -> BirthDayCompose( selectedDateInMillis = selectedBirthDateMillis, onDateSelected = { selectedBirthDateMillis = it } ) 5 -> AddressCompose( latitude = userLat, longitude = userLng, onLocationFetched = { lat, lng -> userLat = lat userLng = lng } ) 6 -> VibeCheckScreen( selectedRealationStatus = selectedRealationStatus ?: "", onRelationSelected = { selectedRealationStatus = it } ) 7 -> GenderCompose( selectedGender = selectedGender, onGenderSelected = { selectedGender = it } ) else -> Text("Step $step") } } Spacer(modifier = Modifier.height(50.dp)) // 6. Action Button Button( onClick = { when (currentStep) { 1 -> { val trimmed = nameInput.trim() when { trimmed.isEmpty() -> nameError = "Name cannot be empty" !nameRegex.matches(trimmed) -> nameError = "Numbers and special characters not allowed" else -> currentStep++ } } 2 -> { if (selectedTab == 0) { val trimmedEmail = emailInput.trim() val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$".toRegex() if (trimmedEmail.isEmpty() || !emailRegex.matches(trimmedEmail)) { emailError = "Enter a valid email" } else currentStep++ } else { val trimmedPhone = phoneInput.trim() if (trimmedPhone.isEmpty() || trimmedPhone.length !in 10..15) { phoneError = "Enter a valid phone number" } else currentStep++ } } 3 -> { val trimmedPassword = passwordInput.trim() val trimmedConfirm = confirmPasswordInput.trim() var isValid = true if (trimmedPassword.length !in 8..20 || !trimmedPassword.any { it.isDigit() }) { passwordError = "Password must be 8-20 characters with letters and numbers" isValid = false } if (trimmedPassword != trimmedConfirm) { confirmPasswordError = "Passwords do not match" isValid = false } if (isValid) currentStep++ } 4 -> { if (selectedBirthDateMillis == null) { Toast.makeText(context, "Select date of birth", Toast.LENGTH_SHORT).show() } else currentStep++ } 5 -> { currentStep++ } 6 -> { if (selectedRealationStatus.isNullOrEmpty()) { Toast.makeText(context, "Please select your vibe", Toast.LENGTH_SHORT).show() } else { currentStep++ } } 7 -> { if (selectedGender.isBlank()) { Toast.makeText(context, "Select gender interest", Toast.LENGTH_SHORT).show() return@Button } val formattedDate = selectedBirthDateMillis?.let { millis -> SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date(millis)) } ?: "" val contact = if (selectedTab == 0) emailInput.trim() else "$countryCode${phoneInput.trim()}" val payload = RegisterRequest( emailOrPhone = contact, password = passwordInput, confirmPassword = confirmPasswordInput, relationshipGoal = selectedRealationStatus.lowercase(Locale.ROOT).replace(" ", "_"), fullName = nameInput.trim(), gender = selectedGender.lowercase(Locale.ROOT), birthDate = formattedDate, city = "lat: $userLat" + "lng: $userLng", bio = null, videoIntro = null, profileImage = null, interests = null ) registrationViewModel.registerUser(context, payload) } } }, modifier = Modifier .fillMaxWidth() .height(52.dp) .background( brush = Brush.horizontalGradient(listOf(PinkGradientStart, PurpleGradientEnd)), shape = RoundedCornerShape(26.dp) ), colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent), contentPadding = PaddingValues() ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { Text( text = if (currentStep < totalSteps) "Continue" else "Finish Registration", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color.White ) Spacer(modifier = Modifier.width(8.dp)) Icon( imageVector = Icons.AutoMirrored.Filled.ArrowForward, contentDescription = null, tint = Color.White ) } } Spacer(modifier = Modifier.height(24.dp)) } } } } // --- Step Content Components --- @Composable fun NameCompose( name: String, errorMessage: String?, onNameChange: (String) -> Unit ) { Column { Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = Color.White), border = BorderStroke(1.dp, LightBorderColor) ) { Text( text = "Welcome! What should we call you?", style = MaterialTheme.typography.titleMedium.copy( fontWeight = FontWeight.Bold, fontSize = 18.sp, color = textDark ), modifier = Modifier.padding(20.dp) ) } Spacer(modifier = Modifier.height(25.dp)) Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = Color.White), border = BorderStroke( width = 2.dp, color = if (errorMessage != null) Color.Red else textPink ) ) { Column(modifier = Modifier.padding(16.dp)) { Text( text = "YOUR FULL NAME OR FIRST NAME", color = if (errorMessage != null) Color.Red else textPink, fontSize = 11.sp, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(InputBg) .border( width = 1.dp, color = if (errorMessage != null) Color.Red else Color(0xFFE5E7EB), shape = RoundedCornerShape(12.dp) ) .padding(horizontal = 12.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = Icons.Default.Badge, contentDescription = null, tint = Color.Gray ) Spacer(modifier = Modifier.width(8.dp)) BasicTextField( value = name, onValueChange = onNameChange, modifier = Modifier.weight(1f), singleLine = true ) } // Display the error message if present if (errorMessage != null) { Spacer(modifier = Modifier.height(6.dp)) Text( text = errorMessage, color = Color.Red, fontSize = 12.sp, fontWeight = FontWeight.Medium, modifier = Modifier.padding(start = 4.dp) ) } } } } } @Composable fun EmailorPhoneCompose(selectedTab: Int, onTabSelected: (Int) -> Unit, emailText: String, onEmailChange: (String) -> Unit, phoneText: String, onPhoneChange: (String) -> Unit, countryCode: String, emailError: String?, phoneError: String?, isEmailValid: Boolean = false, isPhoneValid: Boolean = false ) { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { // Title Header Card Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = Color.White), border = BorderStroke(1.dp, LightBorderColor) ) { Column( modifier = Modifier.padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = if (selectedTab == 0) "How should we reach you?" else "What is your phone number?", style = MaterialTheme.typography.titleMedium.copy( fontWeight = FontWeight.Bold, fontSize = 18.sp, color = textDark ) ) Spacer(modifier = Modifier.height(4.dp)) Text( text = if (selectedTab == 0) "Choose whether you'd like to register with your email or phone number." else "We'll send a quick verification code to confirm it's you.", fontSize = 12.sp, color = Color.Gray, textAlign = TextAlign.Center ) } } Spacer(modifier = Modifier.height(16.dp)) // Tab Selector (Email / Phone Number) Surface( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(16.dp), color = Color(0xFFEEF0F4) ) { Row(modifier = Modifier.padding(4.dp)) { Surface( onClick = { onTabSelected(0) }, modifier = Modifier.weight(1f), shape = RoundedCornerShape(12.dp), color = if (selectedTab == 0) Color.White else Color.Transparent, shadowElevation = if (selectedTab == 0) 2.dp else 0.dp ) { Row( modifier = Modifier.padding(vertical = 12.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = Icons.Default.Mail, contentDescription = null, tint = if (selectedTab == 0) textPink else Color.Gray, modifier = Modifier.size(18.dp) ) Spacer(modifier = Modifier.width(6.dp)) Text( text = "Email", fontSize = 14.sp, fontWeight = FontWeight.Bold, color = if (selectedTab == 0) textPink else Color.Gray ) } } Surface( onClick = { onTabSelected(1) }, modifier = Modifier.weight(1f), shape = RoundedCornerShape(12.dp), color = if (selectedTab == 1) Color.White else Color.Transparent, shadowElevation = if (selectedTab == 1) 2.dp else 0.dp ) { Row( modifier = Modifier.padding(vertical = 12.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = Icons.Default.PhoneIphone, contentDescription = null, tint = if (selectedTab == 1) textPink else Color.Gray, modifier = Modifier.size(18.dp) ) Spacer(modifier = Modifier.width(6.dp)) Text( text = "Phone Number", fontSize = 14.sp, fontWeight = FontWeight.Bold, color = if (selectedTab == 1) textPink else Color.Gray ) } } } } Spacer(modifier = Modifier.height(16.dp)) // Dynamic Input Section Card Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = Color.White), border = BorderStroke( width = 1.dp, color = if ((selectedTab == 0 && emailError != null) || (selectedTab == 1 && phoneError != null)) Color.Red else LightBorderColor ) ) { Column(modifier = Modifier.padding(16.dp)) { Text( text = if (selectedTab == 0) "YOUR EMAIL ADDRESS" else "YOUR PHONE NUMBER", color = if ((selectedTab == 0 && emailError != null) || (selectedTab == 1 && phoneError != null)) Color.Red else textPink, fontSize = 11.sp, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(8.dp)) if (selectedTab == 0) { Row( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(InputBg) .border( width = 1.dp, color = if (emailError != null) Color.Red else Color(0xFFE5E7EB), shape = RoundedCornerShape(12.dp) ) .padding(horizontal = 12.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = Icons.Default.AlternateEmail, contentDescription = null, tint = Color.Gray, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.width(8.dp)) BasicTextField( value = emailText, onValueChange = onEmailChange, modifier = Modifier.weight(1f), textStyle = LocalTextStyle.current.copy( fontSize = 14.sp, fontWeight = FontWeight.Medium, color = textDark ), singleLine = true ) if (isEmailValid) { Icon( imageVector = Icons.Default.CheckCircle, contentDescription = "Valid", tint = Color(0xFF00C853), modifier = Modifier.size(20.dp) ) } } if (emailError != null) { Spacer(modifier = Modifier.height(6.dp)) Text( text = emailError, color = Color.Red, fontSize = 12.sp, fontWeight = FontWeight.Medium, modifier = Modifier.padding(start = 4.dp) ) } } else { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically ) { Row( modifier = Modifier .clip(RoundedCornerShape(12.dp)) .background(InputBg) .border(1.dp, Color(0xFFE5E7EB), RoundedCornerShape(12.dp)) .padding(horizontal = 10.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically ) { Text(text = "🇨🇳", fontSize = 16.sp) Spacer(modifier = Modifier.width(4.dp)) Text( text = countryCode, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = textDark ) Icon( imageVector = Icons.Default.ArrowDropDown, contentDescription = "Select Country", tint = Color.Gray, modifier = Modifier.size(18.dp) ) } Row( modifier = Modifier .weight(1f) .clip(RoundedCornerShape(12.dp)) .background(InputBg) .border( width = 1.dp, color = if (phoneError != null) Color.Red else Color(0xFFE5E7EB), shape = RoundedCornerShape(12.dp) ) .padding(horizontal = 12.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically ) { BasicTextField( value = phoneText, onValueChange = onPhoneChange, modifier = Modifier.weight(1f), textStyle = LocalTextStyle.current.copy( fontSize = 14.sp, fontWeight = FontWeight.Medium, color = textDark ), singleLine = true ) if (isPhoneValid) { Icon( imageVector = Icons.Default.CheckCircle, contentDescription = "Valid", tint = Color(0xFF00C853), modifier = Modifier.size(20.dp) ) } } } if (phoneError != null) { Spacer(modifier = Modifier.height(6.dp)) Text( text = phoneError, color = Color.Red, fontSize = 12.sp, fontWeight = FontWeight.Medium, modifier = Modifier.padding(start = 4.dp) ) } } Spacer(modifier = Modifier.height(8.dp)) Row(verticalAlignment = Alignment.CenterVertically) { Icon( imageVector = Icons.Default.Lock, contentDescription = null, tint = Color.Gray, modifier = Modifier.size(12.dp) ) Spacer(modifier = Modifier.width(4.dp)) Text( text = if (selectedTab == 0) "We'll send an instant verification link. Never shared publicly." else "We'll text you a 6-digit verification code. Standard rates may apply.", fontSize = 10.sp, color = Color.Gray ) } } } } } @Composable fun PasswordComose( passwordInput: String, onPasswordChange: (String) -> Unit, confirmPasswordInput: String, onConfirmPasswordChange: (String) -> Unit, passwordError: String?, confirmPasswordError: String? ) { var isPasswordVisible by remember { mutableStateOf(false) } var isConfirmPasswordVisible by remember { mutableStateOf(false) } // Real-time requirement trackers for the UI indicators val hasValidLength = passwordInput.length in 8..20 val hasLetterAndNumber = passwordInput.any { it.isLetter() } && passwordInput.any { it.isDigit() } val hasSpecialChar = passwordInput.any { !it.isLetterOrDigit() } val passwordsMatch = passwordInput.isNotEmpty() && passwordInput == confirmPasswordInput Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { // 1. Header Card Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = Color.White), border = BorderStroke(1.dp, LightBorderColor) ) { Column( modifier = Modifier.padding(10.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = "Create a secure password", style = MaterialTheme.typography.titleMedium.copy( fontWeight = FontWeight.Bold, fontSize = 18.sp, color = textDark ) ) Spacer(modifier = Modifier.height(4.dp)) Text( text = "Choose a strong password to protect your VibeCheck account.", fontSize = 12.sp, color = Color.Gray, textAlign = TextAlign.Center ) } } Spacer(modifier = Modifier.height(5.dp)) // 2. Main Input Card Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = Color.White), border = BorderStroke(1.dp, LightBorderColor) ) { Column(modifier = Modifier.padding(16.dp)) { // --- PASSWORD FIELD --- Text( text = "PASSWORD", color = textPink, fontSize = 11.sp, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(4.dp)) Row( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(InputBg) .border( width = 1.dp, color = if (passwordError != null) Color.Red else Color(0xFFE5E7EB), shape = RoundedCornerShape(12.dp) ) .padding(horizontal = 12.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = Icons.Default.Lock, contentDescription = null, tint = Color.Gray, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.width(3.dp)) BasicTextField( value = passwordInput, onValueChange = onPasswordChange, modifier = Modifier.weight(1f), textStyle = LocalTextStyle.current.copy( fontSize = 14.sp, fontWeight = FontWeight.Medium, color = textDark ), singleLine = true, visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), decorationBox = { innerTextField -> if (passwordInput.isEmpty()) { Text( text = "Enter secure password", color = Color.LightGray, fontSize = 14.sp ) } innerTextField() } ) IconButton( onClick = { isPasswordVisible = !isPasswordVisible }, modifier = Modifier.size(24.dp) ) { Icon( imageVector = if (isPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff, contentDescription = "Toggle Password Visibility", tint = Color.Gray, modifier = Modifier.size(20.dp) ) } } // Password Error Message if (passwordError != null) { Text( text = passwordError, color = Color.Red, fontSize = 12.sp, modifier = Modifier.padding(top = 4.dp, start = 4.dp) ) } Spacer(modifier = Modifier.height(5.dp)) // --- CONFIRM PASSWORD FIELD --- Text( text = "CONFIRM PASSWORD", color = textPink, fontSize = 11.sp, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(InputBg) .border( width = 1.dp, color = if (confirmPasswordError != null) Color.Red else Color(0xFFE5E7EB), shape = RoundedCornerShape(12.dp) ) .padding(horizontal = 12.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = Icons.Default.Lock, contentDescription = null, tint = Color.Gray, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.width(8.dp)) BasicTextField( value = confirmPasswordInput, onValueChange = onConfirmPasswordChange, modifier = Modifier.weight(1f), textStyle = LocalTextStyle.current.copy( fontSize = 14.sp, fontWeight = FontWeight.Medium, color = textDark ), singleLine = true, visualTransformation = if (isConfirmPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), decorationBox = { innerTextField -> if (confirmPasswordInput.isEmpty()) { Text( text = "Re-enter password", color = Color.LightGray, fontSize = 14.sp ) } innerTextField() } ) IconButton( onClick = { isConfirmPasswordVisible = !isConfirmPasswordVisible }, modifier = Modifier.size(24.dp) ) { Icon( imageVector = if (passwordsMatch) Icons.Default.CheckCircle else if (isConfirmPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff, contentDescription = "Toggle Password Visibility", tint = if (passwordsMatch) Color(0xFF00C853) else Color.Gray, modifier = Modifier.size(20.dp) ) } } // Confirm Password Error Message if (confirmPasswordError != null) { Text( text = confirmPasswordError, color = Color.Red, fontSize = 12.sp, modifier = Modifier.padding(top = 4.dp, start = 4.dp) ) } Spacer(modifier = Modifier.height(5.dp)) // --- PASSWORD REQUIREMENTS BOX --- Column( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) .background(Color(0xFFF8F9FA)) .padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { ValidationRequirementRow( isMet = hasValidLength, label = "8 to 20 characters long" ) ValidationRequirementRow( isMet = hasLetterAndNumber, label = "Includes letters and numbers" ) ValidationRequirementRow( isMet = hasSpecialChar, label = "Includes at least one special character" ) } } } } } @Composable private fun ValidationRequirementRow( isMet: Boolean, label: String ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Icon( imageVector = if (isMet) Icons.Default.CheckCircle else Icons.Default.RadioButtonUnchecked, contentDescription = null, tint = if (isMet) Color(0xFF00C853) else Color.Gray, modifier = Modifier.size(16.dp) ) Text( text = label, fontSize = 12.sp, color = if (isMet) textDark else Color.Gray, fontWeight = if (isMet) FontWeight.Medium else FontWeight.Normal ) } } @Composable fun AddressCompose( addressText: String = "", latitude: Double? = null, longitude: Double? = null, onLocationFetched: (Double, Double) -> Unit = { _, _ -> } ) { val context = LocalContext.current var isLoading by remember { mutableStateOf(false) } val locationManager = remember { context.getSystemService(Context.LOCATION_SERVICE) as LocationManager } @SuppressLint("MissingPermission") fun fetchLocation() { isLoading = true val hasGps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) val hasNetwork = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) if (!hasGps && !hasNetwork) { isLoading = false Toast.makeText(context, "请打开手机定位服务", Toast.LENGTH_SHORT).show() return } // 1) Try last known location first (instant) val lastKnown = listOf( LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER ).mapNotNull { provider -> try { locationManager.getLastKnownLocation(provider) } catch (e: SecurityException) { null } }.maxByOrNull { it.time } if (lastKnown != null) { isLoading = false onLocationFetched(lastKnown.latitude, lastKnown.longitude) return } // 2) No cached fix → request a fresh single update val provider = if (hasGps) LocationManager.GPS_PROVIDER else LocationManager.NETWORK_PROVIDER try { locationManager.requestSingleUpdate( provider, { location -> isLoading = false onLocationFetched(location.latitude, location.longitude) }, Looper.getMainLooper() ) } catch (e: SecurityException) { isLoading = false } } val permissionLauncher = rememberLauncherForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { result -> if (result.values.any { it }) fetchLocation() else Toast.makeText(context, "需要定位权限才能获取位置", Toast.LENGTH_SHORT).show() } Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = Color.White), border = BorderStroke(1.dp, Color(0xFFE0E0E0)) ) { Column( modifier = Modifier.padding(16.dp), horizontalAlignment = Alignment.Start ) { Text( text = "WHERE ARE YOU LOCATED?", color = Color(0xFFFF4081), fontSize = 11.sp, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(12.dp)) Button( onClick = { val granted = ContextCompat.checkSelfPermission( context, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( context, Manifest.permission.ACCESS_COARSE_LOCATION ) == PackageManager.PERMISSION_GRANTED if (granted) fetchLocation() else permissionLauncher.launch( arrayOf( Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ) ) }, modifier = Modifier .fillMaxWidth() .height(50.dp), shape = RoundedCornerShape(12.dp), colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFF8F9FA)), enabled = !isLoading ) { if (isLoading) { CircularProgressIndicator( modifier = Modifier.size(20.dp), strokeWidth = 2.dp, color = Color(0xFFFF4081) ) } else { Icon( imageVector = Icons.Default.LocationOn, contentDescription = null, tint = Color(0xFFFF4081) ) Spacer(modifier = Modifier.width(8.dp)) Text( text = if (latitude != null) "位置已获取 ✓" else "获取我的位置", color = Color(0xFF333333) ) } } if (latitude != null && longitude != null) { Spacer(modifier = Modifier.height(8.dp)) Text( text = "📍 ${"%.5f".format(latitude)}, ${"%.5f".format(longitude)}", fontSize = 13.sp, color = Color.Gray ) } } } } data class GenderPreferenceOption( val id: String, val title: String, val subtitle: String, val icon: ImageVector ) @OptIn(ExperimentalMaterial3Api::class) @Composable fun BirthDayCompose( selectedDateInMillis: Long? = null, onDateSelected: (Long) -> Unit = {} ) { var showDatePickerDialog by rememberSaveable { mutableStateOf(false) } var selectedMillis by rememberSaveable { mutableStateOf(selectedDateInMillis) } var ageError by rememberSaveable { mutableStateOf(null) } // Date Formatter val dateFormatter = remember { SimpleDateFormat("MMMM dd, yyyy", Locale.getDefault()) } // Calculate age from selected milliseconds val calculatedAge = remember(selectedMillis) { selectedMillis?.let { millis -> val dob = Calendar.getInstance().apply { timeInMillis = millis } val today = Calendar.getInstance() var age = today.get(Calendar.YEAR) - dob.get(Calendar.YEAR) if (today.get(Calendar.DAY_OF_YEAR) < dob.get(Calendar.DAY_OF_YEAR)) { age-- } age } } Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { // Header Title Card Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = Color.White), border = BorderStroke(1.dp, LightBorderColor) ) { Column( modifier = Modifier.padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = "When is your birthday?", style = MaterialTheme.typography.titleMedium.copy( fontWeight = FontWeight.Bold, fontSize = 18.sp, color = textDark ) ) Spacer(modifier = Modifier.height(4.dp)) Text( text = "Your profile will display your age, not your birthday.", fontSize = 12.sp, color = Color.Gray, textAlign = TextAlign.Center ) } } Spacer(modifier = Modifier.height(16.dp)) // Input Card with Calendar Trigger Button Card( modifier = Modifier.fillMaxWidth() .clickable { showDatePickerDialog = true } , shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = Color.White), border = BorderStroke( width = 1.dp, color = if (ageError != null) Color.Red else LightBorderColor ) ) { Column(modifier = Modifier.padding(16.dp)) { Text( text = "DATE OF BIRTH", color = if (ageError != null) Color.Red else textPink, fontSize = 11.sp, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(12.dp)) // Date Display Field & Select Button Row( modifier = Modifier .fillMaxWidth() .background(InputBg, shape = RoundedCornerShape(12.dp)) .clickable { showDatePickerDialog = true } // Added clickable modifier here .padding(horizontal = 12.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ){ Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(1f) ) { Icon( imageVector = Icons.Default.Cake, contentDescription = null, tint = Color.Gray, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.width(10.dp)) Text( text = selectedMillis?.let { dateFormatter.format(Date(it)) } ?: "Select your birth date", style = MaterialTheme.typography.bodyMedium.copy( fontSize = 14.sp, fontWeight = if (selectedMillis != null) FontWeight.SemiBold else FontWeight.Normal, color = if (selectedMillis != null) textDark else Color.Gray ) ) } // Open Calendar Button } // Display Age Badge or Error Message if (calculatedAge != null) { Spacer(modifier = Modifier.height(10.dp)) Text( text = "You are $calculatedAge years old", color = if (calculatedAge >= 18) textPink else Color.Red, fontSize = 13.sp, fontWeight = FontWeight.Medium, modifier = Modifier.padding(start = 4.dp) ) } if (ageError != null) { Spacer(modifier = Modifier.height(6.dp)) Text( text = ageError!!, color = Color.Red, fontSize = 12.sp, fontWeight = FontWeight.Medium, modifier = Modifier.padding(start = 4.dp) ) } } } } // Material 3 Date Picker Dialog if (showDatePickerDialog) { val datePickerState = rememberDatePickerState( initialSelectedDateMillis = selectedMillis ?: System.currentTimeMillis() ) DatePickerDialog( onDismissRequest = { showDatePickerDialog = false }, confirmButton = { TextButton( onClick = { datePickerState.selectedDateMillis?.let { millis -> // Calculate Age Validation val dob = Calendar.getInstance().apply { timeInMillis = millis } val today = Calendar.getInstance() var age = today.get(Calendar.YEAR) - dob.get(Calendar.YEAR) if (today.get(Calendar.DAY_OF_YEAR) < dob.get(Calendar.DAY_OF_YEAR)) age-- if (age < 18) { ageError = "You must be at least 18 years old to register." } else { ageError = null selectedMillis = millis onDateSelected(millis) } } showDatePickerDialog = false } ) { Text("OK", color = textPink, fontWeight = FontWeight.Bold) } }, dismissButton = { TextButton(onClick = { showDatePickerDialog = false }) { Text("Cancel", color = Color.Gray) } } ) { DatePicker( state = datePickerState, colors = DatePickerDefaults.colors( selectedDayContainerColor = PinkGradientStart, todayDateBorderColor = PurpleGradientEnd ) ) } } } @Composable fun GenderCompose( selectedGender: String = "", onGenderSelected: (String) -> Unit = {} ) { val options = listOf("Men", "Women", "Everyone") Card( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = Color.White), border = BorderStroke(1.dp, Color(0xFFE0E0E0)) ) { Column( modifier = Modifier.padding(16.dp), horizontalAlignment = Alignment.Start ) { Text( text = "I AM INTERESTED IN", color = Color(0xFFFF4081), fontSize = 11.sp, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(12.dp)) options.forEach { option -> val isSelected = selectedGender.equals(option, ignoreCase = true) Card( modifier = Modifier .fillMaxWidth() .padding(vertical = 4.dp) .clickable { onGenderSelected(option) }, shape = RoundedCornerShape(12.dp), colors = CardDefaults.cardColors( containerColor = if (isSelected) Color(0xFFFF4081).copy(alpha = 0.1f) else Color(0xFFF8F9FA) ), border = BorderStroke( width = 1.dp, color = if (isSelected) Color(0xFFFF4081) else Color.Transparent ) ) { Row( modifier = Modifier .fillMaxWidth() .padding(16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { Text( text = option, style = MaterialTheme.typography.bodyMedium.copy( fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, color = if (isSelected) Color(0xFFFF4081) else Color(0xFF212121) ) ) RadioButton( selected = isSelected, onClick = { onGenderSelected(option) }, colors = RadioButtonDefaults.colors(selectedColor = Color(0xFFFF4081)) ) } } } } } } @Composable fun UniquePulseLoader( modifier: Modifier = Modifier, message: String = "Creating your account..." ) { val infiniteTransition = rememberInfiniteTransition(label = "PulseTransition") // Animated scales for 3 staggered expanding rings val scale1 by infiniteTransition.animateFloat( initialValue = 0.6f, targetValue = 1.4f, animationSpec = infiniteRepeatable( animation = tween(1400, easing = LinearOutSlowInEasing), repeatMode = RepeatMode.Restart ), label = "Ring1" ) val alpha1 by infiniteTransition.animateFloat( initialValue = 0.8f, targetValue = 0f, animationSpec = infiniteRepeatable( animation = tween(1400, easing = LinearOutSlowInEasing), repeatMode = RepeatMode.Restart ), label = "Alpha1" ) val scale2 by infiniteTransition.animateFloat( initialValue = 0.6f, targetValue = 1.4f, animationSpec = infiniteRepeatable( animation = tween(1400, delayMillis = 450, easing = LinearOutSlowInEasing), repeatMode = RepeatMode.Restart ), label = "Ring2" ) val alpha2 by infiniteTransition.animateFloat( initialValue = 0.8f, targetValue = 0f, animationSpec = infiniteRepeatable( animation = tween(1400, delayMillis = 450, easing = LinearOutSlowInEasing), repeatMode = RepeatMode.Restart ), label = "Alpha2" ) Column( modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Box( modifier = Modifier.size(140.dp), contentAlignment = Alignment.Center ) { // Outer Ring 2 Box( modifier = Modifier .size(100.dp) .scale(scale2) .background(PurpleGradientEnd.copy(alpha = alpha2), CircleShape) ) // Outer Ring 1 Box( modifier = Modifier .size(100.dp) .scale(scale1) .background(PinkGradientStart.copy(alpha = alpha1), CircleShape) ) // Center Gradient Orb with Mascot Icon Box( modifier = Modifier .size(80.dp) .background( brush = Brush.linearGradient(listOf(PinkGradientStart, PurpleGradientEnd)), shape = CircleShape ), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Default.SmartToy, contentDescription = null, tint = Color.White, modifier = Modifier.size(42.dp) ) } } Spacer(modifier = Modifier.height(16.dp)) Text( text = message, fontSize = 15.sp, fontWeight = FontWeight.SemiBold, color = PurpleGradientEnd ) } } data class VibeOption( val title: String, val value: String, // Value sent to backend API val description: String, val icon: ImageVector, val iconBgColor: Color, val iconTint: Color ) @Composable fun VibeCheckScreen( selectedRealationStatus: String = "", onRelationSelected: (String) -> Unit = {} ) { val vibeOptions = remember { listOf( VibeOption( title = "Serious Relationship", value = "serious_relationship", description = "Looking for the one", icon = Icons.Default.FavoriteBorder, iconBgColor = Color(0xFFFFE3E3), iconTint = Color(0xFFC2185B) ), VibeOption( title = "Casual Dates", value = "casual_dates", description = "Drinks & good times", icon = Icons.Default.LocalBar, iconBgColor = Color(0xFFEDE7F6), iconTint = Color(0xFF673AB7) ), VibeOption( title = "New Friends", value = "new_friends", description = "Expand my circle", icon = Icons.Default.People, iconBgColor = Color(0xFFFFE0B2), iconTint = Color(0xFFE65100) ), VibeOption( title = "Marriage", value = "marriage", description = "Seeing where things go", icon = Icons.Default.CompassCalibration, iconBgColor = Color(0xFFE0E0E0), iconTint = Color(0xFF424242) ) ) } // ... rest of background and header layout ... Column( verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth() ) { vibeOptions.forEach { option -> // Check selection against option.value val isSelected = option.value == selectedRealationStatus VibeCard( option = option, isSelected = isSelected, onClick = { // Send backend value instead of UI title onRelationSelected(option.value) } ) } } } @Composable fun VibeCard( option: VibeOption, isSelected: Boolean, onClick: () -> Unit ) { val borderColor = if (isSelected) Color(0xFFB0003A) else Color(0xFFFFE4E6) val borderWidth = if (isSelected) 2.dp else 1.dp Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(16.dp)) .background(Color.White) .border( width = borderWidth, color = borderColor, shape = RoundedCornerShape(16.dp) ) .clickable { onClick() } .padding(horizontal = 16.dp, vertical = 14.dp) ) { // Rounded Icon Container Box( contentAlignment = Alignment.Center, modifier = Modifier .size(44.dp) .clip(CircleShape) .background(option.iconBgColor) ) { Icon( imageVector = option.icon, contentDescription = option.title, tint = option.iconTint, modifier = Modifier.size(22.dp) ) } Spacer(modifier = Modifier.width(16.dp)) // Label and Description Column { Text( text = option.title, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color(0xFF1E293B) ) Spacer(modifier = Modifier.height(2.dp)) Text( text = option.description, fontSize = 12.sp, color = Color(0xFF64748B) ) } } } @Preview(showBackground = true, showSystemUi = true) @Composable fun VibeCheckScreenPreview() { VibeCheckScreen() } // 1. Email Tab Preview (Normal State) @Preview(showBackground = true, name = "Step Two - Email Tab") @Composable fun StepTwoContentEmailPreview() { MaterialTheme { EmailorPhoneCompose( selectedTab = 0, onTabSelected = {}, emailText = "john.doe@example.com", onEmailChange = {}, phoneText = "", onPhoneChange = {}, countryCode = "+1", emailError = null, phoneError = null, isEmailValid = true, isPhoneValid = false ) } } @Preview(showBackground = true, name = "Step Two - Phone Tab with Error") @Composable fun StepTwoContentPhonePreview() { MaterialTheme { EmailorPhoneCompose( selectedTab = 1, onTabSelected = {}, emailText = "", onEmailChange = {}, phoneText = "12345", onPhoneChange = {}, countryCode = "+1", emailError = null, phoneError = "Enter a valid phone number (10-15 digits)", isEmailValid = false, isPhoneValid = false ) } }