package com.example.dattingapp import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.animation.* import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip 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.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import java.text.SimpleDateFormat import java.util.* import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Explore import androidx.compose.material.icons.outlined.FavoriteBorder import androidx.compose.material.icons.outlined.Group import androidx.compose.material.icons.outlined.LocalBar import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign import com.example.dattingapp.registration.UploadProfileImageActivity // Global Email Validation Regex val EMAIL_PATTERN_REGEX = Regex("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$") data class VibeOption( val title: String, val subtitle: String, val icon: ImageVector, val iconBgColor: Color, val iconTintColor: Color ) class RegistrationScreen : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) setContent { MaterialTheme { FullscreenRegistrationWizard() } } } } @Composable fun FullscreenRegistrationWizard() { // Active step state var currentStep by remember { mutableIntStateOf(1) } val totalSteps = 7 // Form Field States var isPhoneSelected by remember { mutableStateOf(false) } var countryCode by remember { mutableStateOf("+880") } var contactInput by remember { mutableStateOf("") } var contactError by remember { mutableStateOf(null) } var passwordInput by remember { mutableStateOf("") } var confirmPasswordInput by remember { mutableStateOf("") } var passwordError by remember { mutableStateOf(null) } var nameInput by remember { mutableStateOf("") } var nameError by remember { mutableStateOf(null) } var dobInput by remember { mutableStateOf("") } var dobError by remember { mutableStateOf(null) } var genderInput by remember { mutableStateOf("") } var genderError by remember { mutableStateOf(null) } val selectedInterests = remember { mutableStateListOf() } var interestsError by remember { mutableStateOf(null) } var vibeInput by remember { mutableStateOf("") } var vibeError by remember { mutableStateOf(null) } // Inside your FullscreenRegistrationWizard composable: val context = LocalContext.current // Validation & Navigation fun validateAndProceed() { when (currentStep) { 1 -> { if (nameInput.trim().isBlank()) { nameError = "Please enter your name" } else { nameError = null currentStep = 2 } } 2 -> { if (isPhoneSelected) { when { contactInput.isBlank() -> contactError = "Please enter a phone number" contactInput.length < 7 || contactInput.length > 15 -> contactError = "Enter 7-15 digits" else -> { contactError = null; currentStep = 2 } } } else { val trimmedEmail = contactInput.trim() when { trimmedEmail.isBlank() -> contactError = "Please enter an email address" !EMAIL_PATTERN_REGEX.matches(trimmedEmail) -> contactError = "Invalid email format" else -> { contactError = null; currentStep = 4 } } } } 3 -> { when { passwordInput.length < 6 -> passwordError = "Password must be at least 6 characters" passwordInput != confirmPasswordInput -> passwordError = "Passwords do not match" else -> { passwordError = null; currentStep = 3 } } } 4 -> { if (dobInput.isBlank()) { dobError = "Please select your date of birth" } else { dobError = null currentStep = 5 } } 5 -> { if (genderInput.isBlank()) { genderError = "Please select your gender" } else { genderError = null currentStep = 6 } } 6 -> { if (selectedInterests.size < 2) { interestsError = "Please select at least 2 interests" } else { interestsError = null // Submit registration payload currentStep = 7 } } 7 -> { if (vibeInput.isBlank()) { vibeError = "Please select your vibe to continue" } else { vibeError = null // Launch UploadImageActivity with all collected data val intent = Intent(context, UploadProfileImageActivity::class.java).apply { putExtra("EXTRA_IS_PHONE", isPhoneSelected) putExtra("EXTRA_COUNTRY_CODE", countryCode) putExtra("EXTRA_CONTACT_VALUE", contactInput) putExtra("EXTRA_PASSWORD", passwordInput) putExtra("EXTRA_NAME", nameInput) putExtra("EXTRA_DOB", dobInput) putExtra("EXTRA_GENDER", genderInput) putExtra("EXTRA_INTERESTS", ArrayList(selectedInterests)) putExtra("EXTRA_VIBE", vibeInput) } context.startActivity(intent) } } } } Box( modifier = Modifier .fillMaxSize() .background(Color(0xFFF8F9FA)) .systemBarsPadding() ) { Column(modifier = Modifier.fillMaxSize()) { // TOP BAR & PROGRESS Column( modifier = Modifier .fillMaxWidth() .padding(horizontal = 20.dp, vertical = 16.dp) ) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { if (currentStep > 1) { TextButton( onClick = { currentStep-- }, contentPadding = PaddingValues(0.dp) ) { Text("Back", color = Color.Gray, fontSize = 16.sp) } } else { Spacer(modifier = Modifier.width(48.dp)) } Text( text = "Step $currentStep of $totalSteps", fontWeight = FontWeight.Bold, fontSize = 16.sp, color = Color.Black ) Spacer(modifier = Modifier.width(48.dp)) } Spacer(modifier = Modifier.height(12.dp)) LinearProgressIndicator( progress = { currentStep / totalSteps.toFloat() }, modifier = Modifier .fillMaxWidth() .height(6.dp), color = Color.Black, trackColor = Color(0xFFE0E0E0) ) } // SLIDING CONTENT CONTAINER Box( modifier = Modifier .weight(1f) .fillMaxWidth() .padding(horizontal = 24.dp), contentAlignment = Alignment.Center ) { AnimatedContent( targetState = currentStep, transitionSpec = { if (targetState > initialState) { slideIntoContainer( AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(400) ) + fadeIn(animationSpec = tween(400)) togetherWith slideOutOfContainer( AnimatedContentTransitionScope.SlideDirection.Left, animationSpec = tween(400) ) + fadeOut(animationSpec = tween(400)) } else { slideIntoContainer( AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(400) ) + fadeIn(animationSpec = tween(400)) togetherWith slideOutOfContainer( AnimatedContentTransitionScope.SlideDirection.Right, animationSpec = tween(400) ) + fadeOut(animationSpec = tween(400)) } }, label = "WizardSlideAnimation" ) { step -> Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { when (step) { 1 -> NameInputWidget( name = nameInput, onNameChange = { nameInput = it; nameError = null }, errorMessage = nameError ) 2 -> ContactInputWidget( isPhoneSelected = isPhoneSelected, onModeChange = { isPhoneSelected = it; contactError = null }, countryCode = countryCode, onCountryCodeChange = { countryCode = it }, contactValue = contactInput, onContactChange = { contactInput = it; contactError = null }, errorMessage = contactError ) 3 -> PasswordInputWidget( password = passwordInput, onPasswordChange = { passwordInput = it; passwordError = null }, confirmPassword = confirmPasswordInput, onConfirmPasswordChange = { confirmPasswordInput = it; passwordError = null }, errorMessage = passwordError ) 4 -> DobInputWidget( dob = dobInput, onDobChange = { dobInput = it; dobError = null }, errorMessage = dobError ) 5 -> GenderInputWidget( selectedGender = genderInput, onGenderSelect = { genderInput = it; genderError = null }, errorMessage = genderError ) 6 -> InterestsInputWidget( selectedInterests = selectedInterests, errorMessage = interestsError, onSelectionChanged = { interestsError = null } ) 7 -> VibeCheckWidget( selectedVibe = vibeInput, onVibeSelected = { vibeInput = it vibeError = null }, errorMessage = vibeError ) } } } } // BOTTOM ACTION BUTTON Box( modifier = Modifier .fillMaxWidth() .padding(20.dp) ) { Button( onClick = { validateAndProceed() }, modifier = Modifier .fillMaxWidth() .height(54.dp), shape = RoundedCornerShape(16.dp), colors = ButtonDefaults.buttonColors(containerColor = Color.Black) ) { Text( text = if (currentStep == totalSteps) "Complete Registration" else "Continue", color = Color.White, fontSize = 16.sp, fontWeight = FontWeight.Bold ) } } } } } // ==================== STEP WIDGET COMPOSABLES ==================== @Composable fun ContactInputWidget( isPhoneSelected: Boolean, onModeChange: (Boolean) -> Unit, countryCode: String, onCountryCodeChange: (String) -> Unit, contactValue: String, onContactChange: (String) -> Unit, errorMessage: String? ) { Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text( text = "ENTER YOUR CONTACT INFORMATION", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.Gray ) Row( modifier = Modifier .fillMaxWidth() .height(48.dp) .clip(RoundedCornerShape(24.dp)) .background(Color(0xFFEFEFEF)) .padding(4.dp), verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .weight(1f) .fillMaxHeight() .clip(RoundedCornerShape(20.dp)) .background(if (isPhoneSelected) Color.Black else Color.Transparent) .clickable { onModeChange(true) }, contentAlignment = Alignment.Center ) { Text( text = "Phone", fontWeight = FontWeight.Bold, fontSize = 14.sp, color = if (isPhoneSelected) Color.White else Color.DarkGray ) } Box( modifier = Modifier .weight(1f) .fillMaxHeight() .clip(RoundedCornerShape(20.dp)) .background(if (!isPhoneSelected) Color.Black else Color.Transparent) .clickable { onModeChange(false) }, contentAlignment = Alignment.Center ) { Text( text = "Email", fontWeight = FontWeight.Bold, fontSize = 14.sp, color = if (!isPhoneSelected) Color.White else Color.DarkGray ) } } if (isPhoneSelected) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { OutlinedTextField( value = countryCode, onValueChange = { if (it.length <= 4) onCountryCodeChange(it) }, placeholder = { Text("+880") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone), singleLine = true, isError = errorMessage != null, modifier = Modifier.width(90.dp), shape = RoundedCornerShape(12.dp), colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = Color.White, unfocusedContainerColor = Color.White ) ) OutlinedTextField( value = contactValue, onValueChange = { if (it.length <= 15) onContactChange(it) }, placeholder = { Text("Phone number") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), singleLine = true, isError = errorMessage != null, modifier = Modifier.weight(1f), shape = RoundedCornerShape(12.dp), colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = Color.White, unfocusedContainerColor = Color.White ) ) } } else { OutlinedTextField( value = contactValue, onValueChange = onContactChange, placeholder = { Text("Enter email address") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email), singleLine = true, isError = errorMessage != null, modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(12.dp), colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = Color.White, unfocusedContainerColor = Color.White ) ) } if (errorMessage != null) { Text( text = errorMessage, color = MaterialTheme.colorScheme.error, fontSize = 12.sp, fontWeight = FontWeight.Medium, modifier = Modifier.padding(start = 4.dp) ) } } } @Composable fun PasswordInputWidget( password: String, onPasswordChange: (String) -> Unit, confirmPassword: String, onConfirmPasswordChange: (String) -> Unit, errorMessage: String? ) { Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text( text = "CREATE A PASSWORD", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.Gray ) OutlinedTextField( value = password, onValueChange = onPasswordChange, placeholder = { Text("Enter password") }, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), singleLine = true, isError = errorMessage != null, modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(12.dp), colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = Color.White, unfocusedContainerColor = Color.White ) ) OutlinedTextField( value = confirmPassword, onValueChange = onConfirmPasswordChange, placeholder = { Text("Confirm password") }, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), singleLine = true, isError = errorMessage != null, modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(12.dp), colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = Color.White, unfocusedContainerColor = Color.White ) ) if (errorMessage != null) { Text( text = errorMessage, color = MaterialTheme.colorScheme.error, fontSize = 12.sp, fontWeight = FontWeight.Medium, modifier = Modifier.padding(start = 4.dp) ) } } } @Composable fun NameInputWidget( name: String, onNameChange: (String) -> Unit, errorMessage: String? ) { Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text( text = "WHAT IS YOUR NAME?", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.Gray ) OutlinedTextField( value = name, onValueChange = onNameChange, placeholder = { Text("Enter your full name") }, singleLine = true, isError = errorMessage != null, modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(12.dp), colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = Color.White, unfocusedContainerColor = Color.White ) ) if (errorMessage != null) { Text( text = errorMessage, color = MaterialTheme.colorScheme.error, fontSize = 12.sp, fontWeight = FontWeight.Medium, modifier = Modifier.padding(start = 4.dp) ) } } } @OptIn(ExperimentalMaterial3Api::class) @Composable fun DobInputWidget( dob: String, onDobChange: (String) -> Unit, errorMessage: String? ) { var showDatePicker by remember { mutableStateOf(false) } val datePickerState = rememberDatePickerState() if (showDatePicker) { DatePickerDialog( onDismissRequest = { showDatePicker = false }, confirmButton = { TextButton(onClick = { datePickerState.selectedDateMillis?.let { millis -> val formatter = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()) onDobChange(formatter.format(Date(millis))) } showDatePicker = false }) { Text("OK") } }, dismissButton = { TextButton(onClick = { showDatePicker = false }) { Text("Cancel") } } ) { DatePicker(state = datePickerState) } } Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text( text = "YOUR DATE OF BIRTH", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.Gray ) Box(modifier = Modifier.fillMaxWidth()) { OutlinedTextField( value = dob, onValueChange = {}, readOnly = true, placeholder = { Text("DD/MM/YYYY") }, isError = errorMessage != null, modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(12.dp), colors = OutlinedTextFieldDefaults.colors( focusedContainerColor = Color.White, unfocusedContainerColor = Color.White ) ) Box( modifier = Modifier .matchParentSize() .clickable { showDatePicker = true } ) } if (errorMessage != null) { Text( text = errorMessage, color = MaterialTheme.colorScheme.error, fontSize = 12.sp, fontWeight = FontWeight.Medium, modifier = Modifier.padding(start = 4.dp) ) } } } @Composable fun GenderInputWidget( selectedGender: String, onGenderSelect: (String) -> Unit, errorMessage: String? ) { val options = listOf("Male", "Female", "Other") Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text( text = "SELECT YOUR GENDER", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.Gray ) options.forEach { gender -> val isSelected = selectedGender == gender Card( shape = RoundedCornerShape(12.dp), colors = CardDefaults.cardColors( containerColor = if (isSelected) Color.Black else Color.White ), modifier = Modifier .fillMaxWidth() .clickable { onGenderSelect(gender) } ) { Box( modifier = Modifier.padding(16.dp), contentAlignment = Alignment.CenterStart ) { Text( text = gender, color = if (isSelected) Color.White else Color.Black, fontWeight = FontWeight.SemiBold, fontSize = 15.sp ) } } } if (errorMessage != null) { Text( text = errorMessage, color = MaterialTheme.colorScheme.error, fontSize = 12.sp, fontWeight = FontWeight.Medium, modifier = Modifier.padding(start = 4.dp) ) } } } @OptIn(ExperimentalLayoutApi::class) @Composable fun InterestsInputWidget( selectedInterests: SnapshotStateList, errorMessage: String? = null, onSelectionChanged: () -> Unit = {} ) { val allInterests = listOf("Photography", "Dogs", "Travel", "Music", "Gaming", "Fitness", "Cooking") Card( shape = RoundedCornerShape(16.dp), colors = CardDefaults.cardColors(containerColor = Color.White), modifier = Modifier.fillMaxWidth() ) { Column(modifier = Modifier.padding(16.dp)) { Text( text = "INTERESTS (SELECT AT LEAST 2)", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.Gray ) Spacer(modifier = Modifier.height(12.dp)) FlowRow( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { allInterests.forEach { interest -> val isSelected = selectedInterests.contains(interest) FilterChip( selected = isSelected, onClick = { if (isSelected) selectedInterests.remove(interest) else selectedInterests.add(interest) onSelectionChanged() }, label = { Text(interest) } ) } } if (errorMessage != null) { Spacer(modifier = Modifier.height(8.dp)) Text( text = errorMessage, color = MaterialTheme.colorScheme.error, fontSize = 12.sp, fontWeight = FontWeight.Medium ) } } } } @Composable fun VibeCheckWidget( selectedVibe: String, onVibeSelected: (String) -> Unit, errorMessage: String? = null ) { val options = listOf( VibeOption("Serious Relationship", "Looking for the one", Icons.Outlined.FavoriteBorder, Color(0xFFFFEAEA), Color(0xFFE91E63)), VibeOption("Casual Dates", "Drinks & good times", Icons.Outlined.LocalBar, Color(0xFFEDE7F6), Color(0xFF673AB7)), VibeOption("New Friends", "Expand my circle", Icons.Outlined.Group, Color(0xFFFFECB3), Color(0xFFFF8F00)), VibeOption("Marraige", "Seeing where things go", Icons.Outlined.Explore, Color(0xFFE0F2F1), Color(0xFF00897B)) ) Column( modifier = Modifier .fillMaxWidth() .padding(horizontal = 8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { // App Title / Brand Header Text( text = "VibeCheck", fontSize = 24.sp, fontWeight = FontWeight.Bold, color = Color(0xFFC2185B) ) Spacer(modifier = Modifier.height(8.dp)) // Three Indicator Bars Row( horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically ) { Box(modifier = Modifier.width(20.dp).height(4.dp).background(Color(0xFFE0E0E0), CircleShape)) Box(modifier = Modifier.width(20.dp).height(4.dp).background(Color(0xFFC2185B), CircleShape)) Box(modifier = Modifier.width(20.dp).height(4.dp).background(Color(0xFFE0E0E0), CircleShape)) } Spacer(modifier = Modifier.height(20.dp)) // Title & Description Text( text = "What's your vibe?", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = Color.Black ) Spacer(modifier = Modifier.height(8.dp)) Text( text = "Select your main goal right now to help us find the right connections.", fontSize = 13.sp, color = Color.Gray, textAlign = TextAlign.Center, modifier = Modifier.padding(horizontal = 16.dp) ) Spacer(modifier = Modifier.height(24.dp)) // Vibe Cards List Column( verticalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth() ) { options.forEach { option -> val isSelected = selectedVibe == option.title Card( shape = RoundedCornerShape(16.dp), colors = CardDefaults.cardColors(containerColor = Color.White), border = BorderStroke( width = if (isSelected) 2.dp else 1.dp, color = if (isSelected) Color(0xFFC2185B) else Color(0xFFEEEEEE) ), modifier = Modifier .fillMaxWidth() .clickable { onVibeSelected(option.title) } ) { Row( modifier = Modifier .fillMaxWidth() .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { // Round Icon Container Box( modifier = Modifier .size(42.dp) .background(option.iconBgColor, CircleShape), contentAlignment = Alignment.Center ) { Icon( imageVector = option.icon, contentDescription = option.title, tint = option.iconTintColor, modifier = Modifier.size(22.dp) ) } Spacer(modifier = Modifier.width(16.dp)) // Text Labels Column { Text( text = option.title, fontSize = 15.sp, fontWeight = FontWeight.Bold, color = Color.Black ) Text( text = option.subtitle, fontSize = 12.sp, color = Color.Gray ) } } } } } if (errorMessage != null) { Spacer(modifier = Modifier.height(12.dp)) Text( text = errorMessage, color = MaterialTheme.colorScheme.error, fontSize = 12.sp, fontWeight = FontWeight.Medium ) } Spacer(modifier = Modifier.height(16.dp)) Text( text = "You can always change this later in your profile.", fontSize = 11.sp, color = Color.Gray ) } } @Preview( name = "Light Mode - Full Wizard", showBackground = true, showSystemUi = true ) @Composable fun FullscreenRegistrationWizardPreview() { Surface(modifier = Modifier.fillMaxSize()) { FullscreenRegistrationWizard() } }