diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 3fb775e..305a9c4 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -59,7 +59,27 @@ dependencies {
androidTestImplementation(libs.androidx.junit)
debugImplementation(libs.androidx.compose.ui.test.manifest)
debugImplementation(libs.androidx.compose.ui.tooling)
- implementation("androidx.compose.material:material-icons-extended:1.7.0")
+ implementation("androidx.compose.material:material-icons-extended:1.7.8")
implementation("io.coil-kt:coil-compose:2.6.0")
implementation("androidx.navigation:navigation-compose:2.9.3")
+ implementation("androidx.media3:media3-exoplayer:1.4.1")
+
+ // Media3 UI components (PlayerView)
+ implementation("androidx.media3:media3-ui:1.4.1")
+
+ // Media3 Common library (contains MediaItem)
+ implementation("androidx.media3:media3-common:1.4.1")
+
+ implementation("com.squareup.retrofit2:retrofit:2.11.0")
+ implementation("com.squareup.retrofit2:converter-gson:2.11.0")
+
+ implementation("com.squareup.okhttp3:logging-interceptor:5.5.0")
+
+ implementation("io.coil-kt:coil-compose:2.7.0")
+
+ implementation(libs.media3.exoplayer)
+ implementation(libs.media3.ui)
+
+
+
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 602e940..ac16119 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
+
-
-
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt b/app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt
index c37d2f6..791e2cc 100644
--- a/app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt
+++ b/app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt
@@ -28,6 +28,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.dattingapp.ui.theme.DattingAPPTheme
import com.example.dattingapp.R
+import com.example.dattingapp.registration.RegistrationScreen
// Renamed Activity to LoginActivity to fix the shadowing collision
class LoginActivity : ComponentActivity() {
diff --git a/app/src/main/java/com/example/dattingapp/Login/RegistrationScreen.kt b/app/src/main/java/com/example/dattingapp/Login/RegistrationScreen.kt
deleted file mode 100644
index 6238e04..0000000
--- a/app/src/main/java/com/example/dattingapp/Login/RegistrationScreen.kt
+++ /dev/null
@@ -1,486 +0,0 @@
-package com.example.dattingapp.Login
-
-import android.os.Bundle
-import android.view.WindowManager
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.fadeIn
-import androidx.compose.animation.slideInVertically
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-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.CheckCircle
-import androidx.compose.material.icons.filled.DateRange
-import androidx.compose.material.icons.filled.Person
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
-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.text.font.FontWeight
-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.DattingAPPTheme
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-import java.util.TimeZone
-
-/**
- * Main Activity hosting the Chat Registration Screen.
- */
-class RegistrationScreen : ComponentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- // Ensures the layout resizes and pans up when the soft keyboard opens
- window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
-
- enableEdgeToEdge()
- setContent {
- DattingAPPTheme {
- ChatRegistrationScreen()
- }
- }
- }
-}
-
-/**
- * Data model for individual chat history messages.
- */
-data class ChatMessage(
- val id: Int,
- val text: String,
- val isBot: Boolean
-)
-
-/**
- * Main Registration Screen Composable.
- */
-@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
-@Composable
-fun ChatRegistrationScreen(
- onRegistrationComplete: () -> Unit = {}
-) {
- // Current step index (0 to 4)
- var currentStep by remember { mutableIntStateOf(0) }
-
- // User form inputs
- var nameInput by remember { mutableStateOf("") }
- var dobInput by remember { mutableStateOf("") }
- var locationInput by remember { mutableStateOf("Dhaka, Bangladesh") }
- var selectedGender by remember { mutableStateOf("Man") }
- val selectedInterests = remember { mutableStateListOf("Photography", "Dogs") }
- var bioInput by remember { mutableStateOf("") } // Starts empty to display placeholder
-
- // Chat History State
- val chatMessages = remember {
- mutableStateListOf(
- ChatMessage(1, "Welcome! I'll help you set up your profile. What is your name?", isBot = true)
- )
- }
-
- val listState = rememberLazyListState()
- val coroutineScope = rememberCoroutineScope()
-
- // Smooth scroll helper to push view to bottom
- fun scrollToBottom() {
- coroutineScope.launch {
- delay(100)
- if (chatMessages.isNotEmpty()) {
- listState.animateScrollToItem(chatMessages.size)
- }
- }
- }
-
- // Auto-scroll whenever step updates to prevent text inputs from hiding under keyboard/bottom bar
- LaunchedEffect(currentStep) {
- scrollToBottom()
- }
-
- Scaffold(
- topBar = {
- Column(modifier = Modifier.background(Color.White)) {
- TopAppBar(
- title = {
- Text(
- text = "VibeCheck",
- fontWeight = FontWeight.Bold,
- color = Color(0xFF6B008A),
- fontSize = 20.sp
- )
- },
- navigationIcon = {
- IconButton(onClick = {
- if (currentStep > 0) currentStep--
- }) {
- Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
- }
- },
- actions = {
- Text(
- text = "Step ${currentStep + 1} of 5",
- fontSize = 12.sp,
- color = Color.Gray,
- modifier = Modifier.padding(end = 16.dp)
- )
- }
- )
- // Step Progress Indicator
- LinearProgressIndicator(
- progress = { (currentStep + 1) / 5f },
- modifier = Modifier.fillMaxWidth(),
- color = Color(0xFFC2003B),
- trackColor = Color(0xFFF0F0F0)
- )
- }
- },
- bottomBar = {
- Surface(
- shadowElevation = 8.dp,
- color = Color.White
- ) {
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp)
- ) {
- Button(
- onClick = {
- when (currentStep) {
- 0 -> {
- if (nameInput.isNotBlank()) {
- chatMessages.add(ChatMessage(chatMessages.size + 1, nameInput, isBot = false))
- chatMessages.add(ChatMessage(chatMessages.size + 1, "Great to meet you, $nameInput! When is your birthday?", isBot = true))
- currentStep = 1
- }
- }
- 1 -> {
- if (dobInput.isNotBlank()) {
- chatMessages.add(ChatMessage(chatMessages.size + 1, dobInput, isBot = false))
- chatMessages.add(ChatMessage(chatMessages.size + 1, "Where are you located?", isBot = true))
- currentStep = 2
- }
- }
- 2 -> {
- chatMessages.add(ChatMessage(chatMessages.size + 1, locationInput, isBot = false))
- chatMessages.add(ChatMessage(chatMessages.size + 1, "How do you identify, and what are some of your interests?", isBot = true))
- currentStep = 3
- }
- 3 -> {
- val summary = "$selectedGender | ${selectedInterests.joinToString(", ")}"
- chatMessages.add(ChatMessage(chatMessages.size + 1, summary, isBot = false))
- chatMessages.add(ChatMessage(chatMessages.size + 1, "Almost done! Lastly, tell us a bit about yourself. What's your vibe?", isBot = true))
- currentStep = 4
- }
- 4 -> {
- chatMessages.add(ChatMessage(chatMessages.size + 1, bioInput, isBot = false))
- onRegistrationComplete()
- }
- }
- },
- modifier = Modifier
- .fillMaxWidth()
- .height(52.dp),
- shape = RoundedCornerShape(16.dp),
- colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFC2003B))
- ) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- Text(
- text = if (currentStep == 4) "Complete Profile" else "Continue",
- fontSize = 16.sp,
- fontWeight = FontWeight.Bold,
- color = Color.White
- )
- Spacer(modifier = Modifier.width(8.dp))
- Icon(
- imageVector = if (currentStep == 4) Icons.Default.CheckCircle else Icons.AutoMirrored.Filled.ArrowForward,
- contentDescription = null,
- tint = Color.White
- )
- }
- }
- }
- }
- }
- ) { innerPadding ->
- LazyColumn(
- state = listState,
- modifier = Modifier
- .fillMaxSize()
- .background(Color(0xFFF9F9FB))
- .padding(innerPadding)
- .imePadding() // Dynamically adjusts list padding when soft keyboard appears
- .padding(horizontal = 16.dp),
- verticalArrangement = Arrangement.spacedBy(12.dp),
- contentPadding = PaddingValues(vertical = 16.dp)
- ) {
- // Render chat history
- items(chatMessages) { message ->
- ChatBubbleItem(message = message)
- }
-
- // Render current step widget
- item {
- AnimatedVisibility(
- visible = true,
- enter = fadeIn() + slideInVertically { it / 2 }
- ) {
- when (currentStep) {
- 0 -> NameInputWidget(name = nameInput, onNameChange = { nameInput = it })
- 1 -> DatePickerWidget(dob = dobInput, onDobChange = { dobInput = it })
- 2 -> LocationInputWidget(location = locationInput, onLocationChange = { locationInput = it })
- 3 -> GenderAndInterestsWidget(
- selectedGender = selectedGender,
- onGenderSelect = { selectedGender = it },
- selectedInterests = selectedInterests
- )
- 4 -> BioInputWidget(bio = bioInput, onBioChange = { bioInput = it })
- }
- }
- }
- }
- }
-}
-
-@Composable
-fun ChatBubbleItem(message: ChatMessage) {
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = if (message.isBot) Arrangement.Start else Arrangement.End,
- verticalAlignment = Alignment.Top
- ) {
- if (message.isBot) {
- Box(
- modifier = Modifier
- .size(32.dp)
- .clip(CircleShape)
- .background(Color(0xFFC2003B)),
- contentAlignment = Alignment.Center
- ) {
- Icon(
- imageVector = Icons.Default.Person,
- contentDescription = "Bot",
- tint = Color.White,
- modifier = Modifier.size(18.dp)
- )
- }
- Spacer(modifier = Modifier.width(8.dp))
- }
-
- Surface(
- shape = RoundedCornerShape(
- topStart = 16.dp,
- topEnd = 16.dp,
- bottomStart = if (message.isBot) 4.dp else 16.dp,
- bottomEnd = if (message.isBot) 16.dp else 4.dp
- ),
- color = if (message.isBot) Color(0xFFEEEEEE) else Color(0xFFC2003B),
- modifier = Modifier.widthIn(max = 280.dp)
- ) {
- Text(
- text = message.text,
- modifier = Modifier.padding(12.dp),
- color = if (message.isBot) Color.DarkGray else Color.White,
- fontSize = 14.sp,
- fontWeight = FontWeight.Medium
- )
- }
- }
-}
-
-// STEP 0 WIDGET
-@Composable
-fun NameInputWidget(name: String, onNameChange: (String) -> Unit) {
- OutlinedTextField(
- value = name,
- onValueChange = onNameChange,
- placeholder = { Text("Enter your full name") },
- modifier = Modifier.fillMaxWidth(),
- shape = RoundedCornerShape(12.dp),
- colors = OutlinedTextFieldDefaults.colors(
- focusedContainerColor = Color.White,
- unfocusedContainerColor = Color.White
- )
- )
-}
-
-// STEP 1 WIDGET (MATERIAL 3 DATE PICKER)
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun DatePickerWidget(dob: String, onDobChange: (String) -> Unit) {
- var showDatePicker by remember { mutableStateOf(false) }
- val datePickerState = rememberDatePickerState()
-
- OutlinedTextField(
- value = dob,
- onValueChange = { },
- readOnly = true,
- enabled = false,
- label = { Text("SELECT DATE OF BIRTH") },
- placeholder = { Text("DD/MM/YYYY") },
- trailingIcon = {
- Icon(
- imageVector = Icons.Default.DateRange,
- contentDescription = "Select Date"
- )
- },
- modifier = Modifier
- .fillMaxWidth()
- .clickable { showDatePicker = true },
- shape = RoundedCornerShape(12.dp),
- colors = OutlinedTextFieldDefaults.colors(
- disabledContainerColor = Color.White,
- disabledTextColor = Color.Black,
- disabledLabelColor = Color.Gray,
- disabledBorderColor = Color.Gray,
- disabledTrailingIconColor = Color(0xFFC2003B)
- )
- )
-
- if (showDatePicker) {
- DatePickerDialog(
- onDismissRequest = { showDatePicker = false },
- confirmButton = {
- TextButton(
- onClick = {
- datePickerState.selectedDateMillis?.let { millis ->
- val formatter = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).apply {
- timeZone = TimeZone.getTimeZone("UTC")
- }
- val formattedDate = formatter.format(Date(millis))
- onDobChange(formattedDate)
- }
- showDatePicker = false
- }
- ) {
- Text("OK", color = Color(0xFFC2003B))
- }
- },
- dismissButton = {
- TextButton(onClick = { showDatePicker = false }) {
- Text("CANCEL", color = Color.Gray)
- }
- }
- ) {
- DatePicker(state = datePickerState)
- }
- }
-}
-
-// STEP 2 WIDGET
-@Composable
-fun LocationInputWidget(location: String, onLocationChange: (String) -> Unit) {
- OutlinedTextField(
- value = location,
- onValueChange = onLocationChange,
- placeholder = { Text("Enter your city/location") },
- modifier = Modifier.fillMaxWidth(),
- shape = RoundedCornerShape(12.dp),
- colors = OutlinedTextFieldDefaults.colors(
- focusedContainerColor = Color.White,
- unfocusedContainerColor = Color.White
- )
- )
-}
-
-// STEP 3 WIDGET
-@OptIn(ExperimentalLayoutApi::class)
-@Composable
-fun GenderAndInterestsWidget(
- selectedGender: String,
- onGenderSelect: (String) -> Unit,
- selectedInterests: androidx.compose.runtime.snapshots.SnapshotStateList
-) {
- 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("I IDENTIFY AS", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.Gray)
- Spacer(modifier = Modifier.height(8.dp))
- Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
- listOf("Woman", "Man").forEach { gender ->
- FilterChip(
- selected = selectedGender == gender,
- onClick = { onGenderSelect(gender) },
- label = { Text(gender) }
- )
- }
- }
-
- Spacer(modifier = Modifier.height(16.dp))
- Text("INTERESTS (SELECT AT LEAST 2)", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = Color.Gray)
- Spacer(modifier = Modifier.height(8.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)
- },
- label = { Text(interest) }
- )
- }
- }
- }
- }
-}
-
-// STEP 4 WIDGET
-@Composable
-fun BioInputWidget(bio: String, onBioChange: (String) -> Unit) {
- OutlinedTextField(
- value = bio,
- onValueChange = { if (it.length <= 200) onBioChange(it) },
- modifier = Modifier
- .fillMaxWidth()
- .height(120.dp),
- shape = RoundedCornerShape(12.dp),
- placeholder = { Text("Write a short bio...", color = Color.Gray) },
- maxLines = 4,
- singleLine = false,
- colors = OutlinedTextFieldDefaults.colors(
- focusedContainerColor = Color.White,
- unfocusedContainerColor = Color.White
- )
- )
-}
-
-@Preview(
- name = "Chat Registration - Phone Screen",
- showBackground = true,
- showSystemUi = true,
- device = "spec:width=411dp,height=891dp,dpi=420"
-)
-@Composable
-fun ChatRegistrationScreenPreview() {
- DattingAPPTheme {
- ChatRegistrationScreen(
- onRegistrationComplete = {}
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/Login/UploadProfileImgae.kt b/app/src/main/java/com/example/dattingapp/Login/UploadProfileImgae.kt
deleted file mode 100644
index 4804f02..0000000
--- a/app/src/main/java/com/example/dattingapp/Login/UploadProfileImgae.kt
+++ /dev/null
@@ -1,300 +0,0 @@
-package com.example.dattingapp.Login
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import com.example.dattingapp.ui.theme.DattingAPPTheme
-import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.ArrowBack
-import androidx.compose.material.icons.filled.AddAPhoto
-import androidx.compose.material.icons.filled.CameraAlt
-import androidx.compose.material.icons.filled.Image
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.shadow
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.PathEffect
-import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.text.font.FontWeight
-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
-class UploadProfileImgae : ComponentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContent {
- DattingAPPTheme {
- ProfileImageUploadScreen()
-
-
- }
-
- }
- }
-}
-
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun ProfileImageUploadScreen(
- onBackClick: () -> Unit = {},
- onUploadFromGalleryClick: () -> Unit = {},
- onTakePhotoClick: () -> Unit = {},
- onContinueClick: () -> Unit = {},
- onContactSupportClick: () -> Unit = {}
-) {
- val backgroundGradient = Brush.verticalGradient(
- colors = listOf(
- Color(0xFFFFF7F8),
- Color(0xFFFFFFFF),
- Color(0xFFF7EFFC)
- )
- )
-
- val buttonGradient = Brush.horizontalGradient(
- colors = listOf(
- Color(0xFFD84B75),
- Color(0xFF8E54C1)
- )
- )
-
- val avatarGradientBorder = Brush.sweepGradient(
- colors = listOf(
- Color(0xFFB00020),
- Color(0xFF7B1FA2),
- Color(0xFFB00020)
- )
- )
-
- Scaffold(
- topBar = {
- TopAppBar(
- title = {
- Box(
- modifier = Modifier.fillMaxWidth(),
- contentAlignment = Alignment.Center
- ) {
- Text(
- text = "VibeCheck",
- fontWeight = FontWeight.Bold,
- color = Color(0xFF9C0037),
- fontSize = 20.sp,
- modifier = Modifier.padding(end = 48.dp) // Offset navigation icon width to center text perfectly
- )
- }
- },
- navigationIcon = {
- IconButton(onClick = onBackClick) {
- Icon(
- imageVector = Icons.AutoMirrored.Filled.ArrowBack,
- contentDescription = "Back",
- tint = Color.Black
- )
- }
- },
- colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)
- )
- },
- containerColor = Color.Transparent
- ) { innerPadding ->
- Column(
- modifier = Modifier
- .fillMaxSize()
- .background(brush = backgroundGradient)
- .padding(innerPadding)
- .padding(horizontal = 24.dp),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Spacer(modifier = Modifier.height(16.dp))
-
- Text(
- text = "Upload profile photo",
- fontSize = 22.sp,
- fontWeight = FontWeight.Bold,
- color = Color(0xFF1D1B20)
- )
-
- Spacer(modifier = Modifier.height(8.dp))
-
- Text(
- text = "Choose a photo that shows your\nbest self. You can always change\nthis later.",
- fontSize = 13.sp,
- color = Color(0xFF666666),
- textAlign = TextAlign.Center,
- lineHeight = 18.sp
- )
-
- Spacer(modifier = Modifier.height(36.dp))
-
- // Circular Avatar Placeholder with Gradient Ring
- Box(
- contentAlignment = Alignment.Center,
- modifier = Modifier
- .size(190.dp)
- .shadow(
- elevation = 16.dp,
- shape = CircleShape,
- spotColor = Color(0x33B00020),
- ambientColor = Color(0x337B1FA2)
- )
- .background(Color.White, CircleShape)
- .border(width = 3.dp, brush = avatarGradientBorder, shape = CircleShape)
- ) {
- // Dashed Inner Circle Outline
- Canvas(modifier = Modifier.size(160.dp)) {
- val stroke = Stroke(
- width = 2f,
- pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
- )
- drawCircle(
- color = Color(0xFFE0C4CD),
- style = stroke
- )
- }
-
- // Center Camera Icon
- Icon(
- imageVector = Icons.Default.AddAPhoto,
- contentDescription = "Add Photo",
- tint = Color(0xFFE5A8B8),
- modifier = Modifier.size(44.dp)
- )
- }
-
- Spacer(modifier = Modifier.weight(1f))
-
- // Secondary Action Options
- Column(
- verticalArrangement = Arrangement.spacedBy(12.dp),
- modifier = Modifier.fillMaxWidth()
- ) {
- // Upload from gallery Button
- Surface(
- onClick = onUploadFromGalleryClick,
- shape = RoundedCornerShape(12.dp),
- color = Color(0xFFE8DDED).copy(alpha = 0.6f),
- modifier = Modifier
- .fillMaxWidth()
- .height(48.dp)
- ) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center
- ) {
- Icon(
- imageVector = Icons.Default.Image,
- contentDescription = null,
- tint = Color(0xFF6B008A),
- modifier = Modifier.size(18.dp)
- )
- Spacer(modifier = Modifier.width(8.dp))
- Text(
- text = "Upload from gallery",
- color = Color(0xFF6B008A),
- fontSize = 13.sp,
- fontWeight = FontWeight.SemiBold
- )
- }
- }
-
- // Take a photo Button
- Surface(
- onClick = onTakePhotoClick,
- shape = RoundedCornerShape(12.dp),
- color = Color(0xFFE8DDED).copy(alpha = 0.6f),
- modifier = Modifier
- .fillMaxWidth()
- .height(48.dp)
- ) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center
- ) {
- Icon(
- imageVector = Icons.Default.CameraAlt,
- contentDescription = null,
- tint = Color(0xFF6B008A),
- modifier = Modifier.size(18.dp)
- )
- Spacer(modifier = Modifier.width(8.dp))
- Text(
- text = "Take a photo",
- color = Color(0xFF6B008A),
- fontSize = 13.sp,
- fontWeight = FontWeight.SemiBold
- )
- }
- }
- }
-
- Spacer(modifier = Modifier.height(20.dp))
-
- // Main Gradient Continue Button
- Button(
- onClick = onContinueClick,
- modifier = Modifier
- .fillMaxWidth()
- .height(50.dp)
- .background(
- brush = buttonGradient,
- shape = RoundedCornerShape(12.dp)
- ),
- colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),
- shape = RoundedCornerShape(12.dp)
- ) {
- Text(
- text = "Continue",
- color = Color.White,
- fontWeight = FontWeight.Bold,
- fontSize = 16.sp
- )
- }
-
- Spacer(modifier = Modifier.height(16.dp))
-
- // Need help? Contact support Footer
- Row(
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Text(
- text = "Need help? ",
- fontSize = 12.sp,
- color = Color(0xFF666666)
- )
- Text(
- text = "Contact support",
- fontSize = 12.sp,
- fontWeight = FontWeight.Bold,
- color = Color(0xFFB00020),
- modifier = Modifier.clickable { onContactSupportClick() }
- )
- }
-
- Spacer(modifier = Modifier.height(16.dp))
- }
- }
-}
-
-@Preview(showBackground = true, device = "spec:width=411dp,height=891dp")
-@Composable
-fun ProfileImageUploadScreenPreview() {
- ProfileImageUploadScreen()
-}
diff --git a/app/src/main/java/com/example/dattingapp/Login/VibeType.kt b/app/src/main/java/com/example/dattingapp/Login/VibeType.kt
deleted file mode 100644
index 5fa7ebc..0000000
--- a/app/src/main/java/com/example/dattingapp/Login/VibeType.kt
+++ /dev/null
@@ -1,271 +0,0 @@
-package com.example.dattingapp.Login
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import com.example.dattingapp.ui.theme.DattingAPPTheme
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.ArrowForward
-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.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
-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.style.TextAlign
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-
-class VedioIntroScreen : ComponentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContent {
- DattingAPPTheme {
- WhatsYourVibeScreen(
-
- )
- }
- }
- }
-}
-
-data class VibeOption(
- val title: String,
- val subtitle: String,
- val icon: ImageVector,
- val iconBackgroundColor: Color,
- val iconTintColor: Color
-)
-
-@Composable
-fun WhatsYourVibeScreen(
- onContinueClick: (VibeOption) -> Unit = {}
-) {
- val vibeOptions = listOf(
- VibeOption(
- title = "Serious Relationship",
- subtitle = "Looking for the one",
- icon = Icons.Default.FavoriteBorder,
- iconBackgroundColor = Color(0xFFFFEAEA),
- iconTintColor = Color(0xFFD32F2F)
- ),
- VibeOption(
- title = "Casual Dates",
- subtitle = "Drinks & good times",
- icon = Icons.Default.LocalBar,
- iconBackgroundColor = Color(0xFFF0E5FF),
- iconTintColor = Color(0xFF7B1FA2)
- ),
- VibeOption(
- title = "New Friends",
- subtitle = "Expand my circle",
- icon = Icons.Default.People,
- iconBackgroundColor = Color(0xFFFFF3E0),
- iconTintColor = Color(0xFFE65100)
- ),
- VibeOption(
- title = "Marriage",
- subtitle = "Seeing where things go",
- icon = Icons.Default.CompassCalibration,
- iconBackgroundColor = Color(0xFFEFEFEF),
- iconTintColor = Color(0xFF616161)
- )
- )
-
- var selectedOption by remember { mutableStateOf(null) }
-
- val backgroundGradient = Brush.verticalGradient(
- colors = listOf(
- Color(0xFFFFF5F7),
- Color(0xFFFFFFFF),
- Color(0xFFF9F0FF)
- )
- )
-
- Column(
- modifier = Modifier
- .fillMaxSize()
- .background(brush = backgroundGradient)
- .padding(horizontal = 24.dp, vertical = 16.dp),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Spacer(modifier = Modifier.height(24.dp))
-
- Text(
- text = "VibeCheck",
- fontSize = 24.sp,
- fontWeight = FontWeight.Bold,
- color = Color(0xFFB00020)
- )
-
- Spacer(modifier = Modifier.height(16.dp))
-
- // Indicator Bar
-
- Spacer(modifier = Modifier.height(32.dp))
-
- Text(
- text = "What's your vibe?",
- fontSize = 22.sp,
- fontWeight = FontWeight.Bold,
- color = Color(0xFF1D1B20)
- )
-
- Spacer(modifier = Modifier.height(8.dp))
-
- Text(
- text = "Select your main goal right now to help us find the right connections.",
- fontSize = 14.sp,
- color = Color(0xFF666666),
- textAlign = TextAlign.Center,
- modifier = Modifier.padding(horizontal = 16.dp)
- )
-
- Spacer(modifier = Modifier.height(32.dp))
-
- // Options List
- Column(
- verticalArrangement = Arrangement.spacedBy(12.dp),
- modifier = Modifier.weight(1f)
- ) {
- vibeOptions.forEach { option ->
- val isSelected = selectedOption == option
-
- val borderModifier = if (isSelected) {
- Modifier.border(
- width = 2.dp,
- color = Color(0xFFB00020),
- shape = RoundedCornerShape(16.dp)
- )
- } else {
- Modifier.border(
- width = 1.dp,
- color = Color(0xFFEAEAEA),
- shape = RoundedCornerShape(16.dp)
- )
- }
-
- Surface(
- shape = RoundedCornerShape(16.dp),
- color = Color.White,
- shadowElevation = if (isSelected) 4.dp else 0.dp,
- modifier = Modifier
- .fillMaxWidth()
- .then(borderModifier)
- .clickable { selectedOption = option }
- ) {
- Row(
- modifier = Modifier.padding(16.dp),
- verticalAlignment = Alignment.CenterVertically
- ) {
- Box(
- modifier = Modifier
- .size(44.dp)
- .background(option.iconBackgroundColor, 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))
-
- Column {
- Text(
- text = option.title,
- fontSize = 16.sp,
- fontWeight = FontWeight.Bold,
- color = Color(0xFF1D1B20)
- )
- Spacer(modifier = Modifier.height(2.dp))
- Text(
- text = option.subtitle,
- fontSize = 12.sp,
- color = Color(0xFF757575)
- )
- }
- }
- }
- }
- }
-
- // Bottom Action Area
- Button(
- onClick = { selectedOption?.let { onContinueClick(it) } },
- enabled = selectedOption != null,
- modifier = Modifier
- .fillMaxWidth()
- .height(52.dp)
- .background(
- brush = Brush.horizontalGradient(
- colors = listOf(Color(0xFFB00020), Color(0xFF4A148C))
- ),
- shape = RoundedCornerShape(26.dp)
- ),
- colors = ButtonDefaults.buttonColors(
- containerColor = Color.Transparent,
- disabledContainerColor = Color.Gray.copy(alpha = 0.3f)
- ),
- shape = RoundedCornerShape(26.dp)
- ) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center
- ) {
- Text(
- text = "Continue",
- color = Color.White,
- fontSize = 16.sp,
- fontWeight = FontWeight.Bold
- )
- Spacer(modifier = Modifier.width(8.dp))
- Icon(
- imageVector = Icons.AutoMirrored.Filled.ArrowForward,
- contentDescription = null,
- tint = Color.White,
- modifier = Modifier.size(18.dp)
- )
- }
- }
-
- Spacer(modifier = Modifier.height(12.dp))
-
- Text(
- text = "You can always change this later in your profile.",
- fontSize = 12.sp,
- color = Color(0xFF757575),
- textAlign = TextAlign.Center
- )
-
- Spacer(modifier = Modifier.height(8.dp))
- }
-}
-
-@Preview(showBackground = true, device = "spec:width=411dp,height=891dp")
-@Composable
-fun WhatsYourVibeScreenPreview() {
- WhatsYourVibeScreen()
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/Login/VideoIntroScreen.kt b/app/src/main/java/com/example/dattingapp/Login/VideoIntroScreen.kt
deleted file mode 100644
index 7e5418a..0000000
--- a/app/src/main/java/com/example/dattingapp/Login/VideoIntroScreen.kt
+++ /dev/null
@@ -1,338 +0,0 @@
-package com.example.dattingapp.Login
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import com.example.dattingapp.ui.theme.DattingAPPTheme
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.ArrowBack
-import androidx.compose.material.icons.filled.Cameraswitch
-import androidx.compose.material.icons.filled.FileUpload
-import androidx.compose.material.icons.filled.Videocam
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
-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.PathEffect
-import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.text.font.FontWeight
-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.R
-
-class VideoIntroScreen : ComponentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContent {
- DattingAPPTheme {
- VideoIntroScreen(
- onBackClick = {
- // TODO: Navigate back
- },
- onSkipClick = {
- // TODO: Skip video intro step
- },
- onUploadClick = {
- // TODO: Process final upload or proceed to next screen
- },
- onRecordClick = {
- // TODO: Start or stop recording video
- },
- onSelectFileClick = {
- // TODO: Launch video picker from gallery
- },
- onSwitchCameraClick = {
- // TODO: Switch between front and rear cameras
- }
- )
- }
- }
- }
-}
-
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun VideoIntroScreen(
- onBackClick: () -> Unit = {},
- onSkipClick: () -> Unit = {},
- onUploadClick: () -> Unit = {},
- onRecordClick: () -> Unit = {},
- onSelectFileClick: () -> Unit = {},
- onSwitchCameraClick: () -> Unit = {}
-) {
- Scaffold(
- topBar = {
- TopAppBar(
- title = {
- Box(
- modifier = Modifier.fillMaxWidth(),
- contentAlignment = Alignment.Center
- ) {
- Text(
- text = "VibeCheck",
- fontWeight = FontWeight.Bold,
- color = Color(0xFF6B008A),
- fontSize = 20.sp
- )
- }
- },
- navigationIcon = {
- IconButton(onClick = onBackClick) {
- Icon(
- imageVector = Icons.AutoMirrored.Filled.ArrowBack,
- contentDescription = "Back",
- tint = Color.Black
- )
- }
- },
- actions = {
- TextButton(onClick = onSkipClick) {
- Text(
- text = "SKIP",
- color = Color.Gray,
- fontSize = 12.sp,
- fontWeight = FontWeight.Bold
- )
- }
- },
- colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.White)
- )
- },
- bottomBar = {
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp)
- ) {
- Button(
- onClick = onUploadClick,
- modifier = Modifier
- .fillMaxWidth()
- .height(50.dp)
- .background(
- brush = Brush.horizontalGradient(
- colors = listOf(Color(0xFFC2003B), Color(0xFF6B008A))
- ),
- shape = RoundedCornerShape(12.dp)
- ),
- colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),
- shape = RoundedCornerShape(12.dp)
- ) {
- Text(
- text = "Upload",
- color = Color.White,
- fontWeight = FontWeight.Bold,
- fontSize = 16.sp,
- letterSpacing = 1.sp
- )
- }
- Spacer(Modifier.height(10.dp))
- Button(
- onClick = onUploadClick,
- modifier = Modifier
- .fillMaxWidth()
- .height(50.dp)
- .border(
- width = 1.dp,
- brush = Brush.horizontalGradient(
- colors = listOf(Color(0xFFC2003B), Color(0xFF6B008A))
- ),
- shape = RoundedCornerShape(12.dp)
- ),
- colors = ButtonDefaults.buttonColors(
- containerColor = Color.Transparent
- ),
- shape = RoundedCornerShape(12.dp)
- ) {
- Text(
- text = "Skip Now",
- color = Color(0xFF6B008A),
- fontWeight = FontWeight.Bold,
- fontSize = 16.sp,
- letterSpacing = 1.sp
- )
- }
- }
- }
- ) { innerPadding ->
- Column(
- modifier = Modifier
- .fillMaxSize()
- .background(Color.White)
- .padding(innerPadding)
- .padding(horizontal = 20.dp),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Spacer(modifier = Modifier.height(8.dp))
-
- Text(
- text = "Video Introduction",
- fontSize = 22.sp,
- fontWeight = FontWeight.Bold,
- color = Color(0xFF222222)
- )
-
- Spacer(modifier = Modifier.height(6.dp))
-
- Text(
- text = "Say hello! Share a bit about yourself in 60 seconds.",
- fontSize = 13.sp,
- color = Color.Gray,
- textAlign = TextAlign.Center
- )
-
- Spacer(modifier = Modifier.height(16.dp))
-
- // Main Video/Camera Viewport Frame
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .weight(1f)
- .clip(RoundedCornerShape(20.dp))
- ) {
- // Background Video Preview Image
- Image(
- painter = painterResource(id = R.drawable.ic_launcher_background), // Replace with your image/Camera Preview
- contentDescription = "Camera Feed",
- contentScale = ContentScale.Crop,
- modifier = Modifier.fillMaxSize()
- )
-
- // Face Outline Overlay Guideline
- val stroke = Stroke(
- width = 3f,
- pathEffect = PathEffect.dashPathEffect(floatArrayOf(12f, 12f), 0f)
- )
- Canvas(modifier = Modifier.fillMaxSize()) {
- drawOval(
- color = Color.White.copy(alpha = 0.5f),
- topLeft = androidx.compose.ui.geometry.Offset(size.width * 0.15f, size.height * 0.1f),
- size = androidx.compose.ui.geometry.Size(size.width * 0.7f, size.height * 0.62f),
- style = stroke
- )
- }
-
- // Timer Badge Overlay
- Surface(
- color = Color.White.copy(alpha = 0.75f),
- shape = RoundedCornerShape(16.dp),
- modifier = Modifier
- .align(Alignment.TopEnd)
- .padding(16.dp)
- ) {
- Text(
- text = "00:00 / 01:00",
- color = Color(0xFF222222),
- fontSize = 12.sp,
- fontWeight = FontWeight.SemiBold,
- modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp)
- )
- }
-
- // Bottom Controls Inside Preview Frame
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .align(Alignment.BottomCenter)
- .padding(bottom = 20.dp),
- horizontalArrangement = Arrangement.SpaceEvenly,
- verticalAlignment = Alignment.CenterVertically
- ) {
- // Upload File Button
- IconButton(
- onClick = onSelectFileClick,
- modifier = Modifier
- .size(48.dp)
- .background(Color.White.copy(alpha = 0.65f), CircleShape)
- ) {
- Icon(
- imageVector = Icons.Default.FileUpload,
- contentDescription = "Upload File",
- tint = Color.DarkGray
- )
- }
-
- // Main Record Button
- IconButton(
- onClick = onRecordClick,
- modifier = Modifier
- .size(68.dp)
- .border(3.dp, Color.White.copy(alpha = 0.8f), CircleShape)
- .background(Color(0xFFC2003B), CircleShape)
- ) {
- Icon(
- imageVector = Icons.Default.Videocam,
- contentDescription = "Record Video",
- tint = Color.White,
- modifier = Modifier.size(32.dp)
- )
- }
-
- // Switch Camera / Rotate Button
- IconButton(
- onClick = onSwitchCameraClick,
- modifier = Modifier
- .size(48.dp)
- .background(Color.White.copy(alpha = 0.65f), CircleShape)
- ) {
- Icon(
- imageVector = Icons.Default.Cameraswitch,
- contentDescription = "Switch Camera",
- tint = Color.DarkGray
- )
- }
- }
- }
-
- Spacer(modifier = Modifier.height(12.dp))
- }
- }
-}
-
-@Preview(showBackground = true, device = "spec:width=411dp,height=891dp")
-@Composable
-fun VideoIntroScreenPreview() {
- DattingAPPTheme {
- VideoIntroScreen(
- onBackClick = {
- // TODO: Navigate back
- },
- onSkipClick = {
- // TODO: Skip video intro step
- },
- onUploadClick = {
- // TODO: Process final upload or proceed to next screen
- },
- onRecordClick = {
- // TODO: Start or stop recording video
- },
- onSelectFileClick = {
- // TODO: Launch video picker from gallery
- },
- onSwitchCameraClick = {
- // TODO: Switch between front and rear cameras
- }
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/Login/ui/theme/Color.kt b/app/src/main/java/com/example/dattingapp/Login/ui/theme/Color.kt
new file mode 100644
index 0000000..7b2bd68
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/Login/ui/theme/Color.kt
@@ -0,0 +1,11 @@
+package com.example.dattingapp.Login.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/Login/ui/theme/Theme.kt b/app/src/main/java/com/example/dattingapp/Login/ui/theme/Theme.kt
new file mode 100644
index 0000000..2ca16c6
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/Login/ui/theme/Theme.kt
@@ -0,0 +1,58 @@
+package com.example.dattingapp.Login.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+
+private val DarkColorScheme = darkColorScheme(
+ primary = Purple80,
+ secondary = PurpleGrey80,
+ tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+ primary = Purple40,
+ secondary = PurpleGrey40,
+ tertiary = Pink40
+
+ /* Other default colors to override
+ background = Color(0xFFFFFBFE),
+ surface = Color(0xFFFFFBFE),
+ onPrimary = Color.White,
+ onSecondary = Color.White,
+ onTertiary = Color.White,
+ onBackground = Color(0xFF1C1B1F),
+ onSurface = Color(0xFF1C1B1F),
+ */
+)
+
+@Composable
+fun DattingAPPTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ // Dynamic color is available on Android 12+
+ dynamicColor: Boolean = true,
+ content: @Composable () -> Unit
+) {
+ val colorScheme = when {
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ }
+
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ typography = Typography,
+ content = content
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/Login/ui/theme/Type.kt b/app/src/main/java/com/example/dattingapp/Login/ui/theme/Type.kt
new file mode 100644
index 0000000..a47b73f
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/Login/ui/theme/Type.kt
@@ -0,0 +1,34 @@
+package com.example.dattingapp.Login.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ bodyLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ )
+ /* Other default text styles to override
+ titleLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.sp
+ ),
+ labelSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 11.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp
+ )
+ */
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/MainActivity.kt b/app/src/main/java/com/example/dattingapp/MainActivity.kt
index ea8da14..2ecae70 100644
--- a/app/src/main/java/com/example/dattingapp/MainActivity.kt
+++ b/app/src/main/java/com/example/dattingapp/MainActivity.kt
@@ -6,8 +6,6 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.ui.platform.LocalContext
-import com.example.dattingapp.HomeActivity
-import com.example.dattingapp.permission.LocationPermision
import com.example.dattingapp.permission.NotificationPermission
import com.example.dattingapp.ui.theme.DattingAPPTheme
@@ -18,7 +16,7 @@ class MainActivity : ComponentActivity() {
setContent {
DattingAPPTheme {
val context = LocalContext.current
- val intent = Intent(context, HomeActivity::class.java)
+ val intent = Intent(context, NotificationPermission::class.java)
context.startActivity(intent)
finish()
}
diff --git a/app/src/main/java/com/example/dattingapp/MeetActivity.kt b/app/src/main/java/com/example/dattingapp/MeetActivity.kt
new file mode 100644
index 0000000..662b725
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/MeetActivity.kt
@@ -0,0 +1,479 @@
+package com.example.dattingapp
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.*
+import androidx.compose.material.icons.outlined.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+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.text.font.FontWeight
+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.DattingAPPTheme
+import com.example.dattingapp.ui.theme.lightChipBg
+import com.example.dattingapp.ui.theme.primaryColor
+
+class MeetActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContent {
+ DattingAPPTheme {
+ VibeCheckHomeScreen()
+ }
+ }
+ }
+}
+
+// Data Models
+data class VibeCategory(
+ val id: String,
+ val name: String,
+ val isSelected: Boolean = false
+)
+
+data class EventCardData(
+ val id: String,
+ val title: String,
+ val description: String,
+ val imageUrl: String,
+ val tagLabel: String,
+ val isTrending: Boolean = false,
+ val tags: List = emptyList()
+)
+
+data class UserMatch(
+ val id: String,
+ val name: String,
+ val matchPercentage: Int,
+ val avatarUrl: String
+)
+
+@Composable
+fun VibeCheckHomeScreen(
+ modifier: Modifier = Modifier
+) {
+ Scaffold(
+ modifier = modifier.fillMaxSize(),
+ topBar = {
+ VibeCheckTopAppBar(primaryColor)
+ }
+ ) { innerPadding ->
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding)
+ .padding(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ item {
+ SearchBarSection()
+ }
+
+ item {
+ VibeFilterChipsSection(
+ selectedColor = primaryColor,
+ unselectedBg = lightChipBg
+ )
+ }
+
+ item {
+ FeaturedEventCard(
+ data = EventCardData(
+ id = "1",
+ title = "Coffee & Pottery",
+ description = "Get your hands dirty and your caffeine fix. Perfect for a low-pressure first date.",
+ imageUrl = "",
+ tagLabel = "Live",
+ isTrending = false,
+ tags = listOf("Creative", "Chill")
+ ),
+ badgeColor = primaryColor
+ )
+ }
+
+ item {
+ MostMatchingSection(primaryColor)
+ }
+
+ item {
+ FeaturedEventCard(
+ data = EventCardData(
+ id = "2",
+ title = "Sunset Beach Volley",
+ description = "Join a casual pickup game down at the pier. Bring sunscreen and good vibes.",
+ imageUrl = "",
+ tagLabel = "TRENDING",
+ isTrending = true
+ ),
+ badgeColor = Color(0xFF7C4DFF)
+ )
+ }
+
+ item {
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+ }
+ }
+}
+
+@Composable
+private fun VibeCheckTopAppBar(
+ primaryColor: Color
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 12.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ IconButton(onClick = { }) {
+ Icon(
+ imageVector = Icons.Default.Add,
+ contentDescription = "Add"
+ )
+ }
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Box(
+ modifier = Modifier
+ .size(32.dp)
+ .clip(CircleShape)
+ .background(Color.Gray)
+ )
+
+ Spacer(modifier = Modifier.width(8.dp))
+
+ Text(
+ text = "VibeCheck",
+ color = primaryColor,
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
+
+ IconButton(onClick = { }) {
+ Icon(
+ imageVector = Icons.Default.Search,
+ contentDescription = "Search"
+ )
+ }
+ }
+}
+
+@Composable
+private fun SearchBarSection() {
+ var searchText by remember { mutableStateOf("") }
+
+ OutlinedTextField(
+ value = searchText,
+ onValueChange = { searchText = it },
+ placeholder = {
+ Text(text = "Search", color = Color.Gray)
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Default.Search,
+ contentDescription = null,
+ tint = Color.Gray
+ )
+ },
+ trailingIcon = {
+ Row(modifier = Modifier.padding(end = 8.dp)) {
+ Icon(
+ imageVector = Icons.Outlined.Image,
+ contentDescription = null,
+ tint = Color.Gray
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Icon(
+ imageVector = Icons.Outlined.LocationOn,
+ contentDescription = null,
+ tint = Color.Gray
+ )
+ }
+ },
+ shape = RoundedCornerShape(24.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ unfocusedContainerColor = Color(0xFFF5F5F5),
+ focusedContainerColor = Color(0xFFF5F5F5),
+ unfocusedBorderColor = Color.Transparent,
+ focusedBorderColor = Color.Transparent
+ ),
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(50.dp)
+ )
+}
+
+@Composable
+private fun VibeFilterChipsSection(
+ selectedColor: Color,
+ unselectedBg: Color
+) {
+ val categories = remember {
+ listOf(
+ VibeCategory(id = "1", name = "All Vibes", isSelected = true),
+ VibeCategory(id = "2", name = "Chill"),
+ VibeCategory(id = "3", name = "Active"),
+ VibeCategory(id = "4", name = "Creative"),
+ VibeCategory(id = "5", name = "Nightlife")
+ )
+ }
+
+ LazyRow(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ items(categories, key = { it.id }) { category ->
+ Surface(
+ shape = RoundedCornerShape(16.dp),
+ color = if (category.isSelected) selectedColor else unselectedBg,
+ modifier = Modifier.height(32.dp)
+ ) {
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier.padding(horizontal = 14.dp)
+ ) {
+ Text(
+ text = category.name,
+ color = if (category.isSelected) Color.White else selectedColor,
+ fontSize = 12.sp,
+ fontWeight = FontWeight.SemiBold
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun FeaturedEventCard(
+ data: EventCardData,
+ badgeColor: Color
+) {
+ Card(
+ shape = RoundedCornerShape(16.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(360.dp)
+ ) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.DarkGray)
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ Brush.verticalGradient(
+ colors = listOf(
+ Color.Transparent,
+ Color.Black.copy(alpha = 0.85f)
+ ),
+ startY = 200f
+ )
+ )
+ )
+
+ Surface(
+ color = badgeColor,
+ shape = RoundedCornerShape(12.dp),
+ modifier = Modifier
+ .padding(12.dp)
+ .align(Alignment.TopStart)
+ ) {
+ Row(
+ modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ imageVector = if (!data.isTrending) Icons.Default.RadioButtonChecked else Icons.Default.Bolt,
+ contentDescription = null,
+ tint = Color.White,
+ modifier = Modifier.size(12.dp)
+ )
+ Spacer(modifier = Modifier.width(4.dp))
+ Text(
+ text = data.tagLabel,
+ color = Color.White,
+ fontSize = 10.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+
+ if (!data.isTrending) {
+ Column(
+ modifier = Modifier
+ .align(Alignment.CenterEnd)
+ .padding(end = 12.dp),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ IconButton(onClick = { }) {
+ Icon(
+ imageVector = Icons.Outlined.ThumbUp,
+ contentDescription = "Like",
+ tint = Color.White
+ )
+ }
+ IconButton(onClick = { }) {
+ Icon(
+ imageVector = Icons.Outlined.ChatBubbleOutline,
+ contentDescription = "Comment",
+ tint = Color.White
+ )
+ }
+ IconButton(onClick = { }) {
+ Icon(
+ imageVector = Icons.Outlined.BookmarkBorder,
+ contentDescription = "Save",
+ tint = Color.White
+ )
+ }
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .align(Alignment.BottomStart)
+ .padding(16.dp)
+ ) {
+ Text(
+ text = data.title,
+ color = Color.White,
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Bold
+ )
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ Text(
+ text = data.description,
+ color = Color.LightGray,
+ fontSize = 12.sp,
+ lineHeight = 16.sp
+ )
+
+ if (data.tags.isNotEmpty()) {
+ Spacer(modifier = Modifier.height(8.dp))
+ Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
+ data.tags.forEach { tag ->
+ Surface(
+ color = Color.White.copy(alpha = 0.2f),
+ shape = RoundedCornerShape(6.dp)
+ ) {
+ Text(
+ text = tag,
+ color = Color.White,
+ fontSize = 10.sp,
+ modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun MostMatchingSection(
+ primaryColor: Color
+) {
+ val matches = remember {
+ listOf(
+ UserMatch("1", "Sarah", 98, ""),
+ UserMatch("2", "James", 94, ""),
+ UserMatch("3", "Elena", 92, ""),
+ UserMatch("4", "Marcus", 88, ""),
+ UserMatch("5", "Maya", 82, "")
+ )
+ }
+
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Text(
+ text = "Most matching",
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier.padding(bottom = 12.dp)
+ )
+
+ LazyRow(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
+ items(matches, key = { it.id }) { user ->
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Box(contentAlignment = Alignment.BottomEnd) {
+ Box(
+ modifier = Modifier
+ .size(56.dp)
+ .clip(CircleShape)
+ .border(2.dp, primaryColor, CircleShape)
+ .background(Color.LightGray)
+ )
+
+ Surface(
+ color = primaryColor,
+ shape = RoundedCornerShape(8.dp),
+ modifier = Modifier.offset(x = 4.dp, y = 4.dp)
+ ) {
+ Text(
+ text = "${user.matchPercentage}%",
+ color = Color.White,
+ fontSize = 8.sp,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier.padding(horizontal = 4.dp, vertical = 1.dp)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(6.dp))
+
+ Text(
+ text = user.name,
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Medium
+ )
+ }
+ }
+ }
+ }
+}
+
+@Preview(
+ showBackground = true,
+ showSystemUi = true,
+ widthDp = 360,
+ heightDp = 800
+)
+@Composable
+fun VibeCheckHomeScreenPreview() {
+ MaterialTheme {
+ Surface(modifier = Modifier.fillMaxSize()) {
+ VibeCheckHomeScreen(
+ modifier = Modifier.fillMaxSize()
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/RegistrationScreen.kt b/app/src/main/java/com/example/dattingapp/RegistrationScreen.kt
new file mode 100644
index 0000000..c8377a8
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/RegistrationScreen.kt
@@ -0,0 +1,944 @@
+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.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+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.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+// 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()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/UploadIntroVideoActivity.kt b/app/src/main/java/com/example/dattingapp/UploadIntroVideoActivity.kt
new file mode 100644
index 0000000..f1d17a7
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/UploadIntroVideoActivity.kt
@@ -0,0 +1,439 @@
+package com.example.dattingapp
+
+import android.content.Context
+import android.net.Uri
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.filled.FileUpload
+import androidx.compose.material.icons.filled.PhotoCamera
+import androidx.compose.material.icons.filled.Videocam
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+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.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.content.FileProvider
+import androidx.media3.common.MediaItem
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.ui.PlayerView
+import coil.compose.rememberAsyncImagePainter
+import com.example.dattingapp.ui.theme.DattingAPPTheme
+import java.io.File
+
+enum class DisplayMode {
+ IMAGE,
+ VIDEO,
+ BIO
+}
+
+class UploadIntroVideoActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+
+ // Retrieve registration data from previous step
+ val isPhone = intent.getBooleanExtra("EXTRA_IS_PHONE", false)
+ val countryCode = intent.getStringExtra("EXTRA_COUNTRY_CODE") ?: ""
+ val contactValue = intent.getStringExtra("EXTRA_CONTACT_VALUE") ?: ""
+ val password = intent.getStringExtra("EXTRA_PASSWORD") ?: ""
+ val name = intent.getStringExtra("EXTRA_NAME") ?: ""
+ val dob = intent.getStringExtra("EXTRA_DOB") ?: ""
+ val gender = intent.getStringExtra("EXTRA_GENDER") ?: ""
+ val interests = intent.getStringArrayListExtra("EXTRA_INTERESTS") ?: arrayListOf()
+ val vibe = intent.getStringExtra("EXTRA_VIBE") ?: ""
+
+ // Retrieve Profile Image URI string
+ val profileImageUriString = intent.getStringExtra("EXTRA_PROFILE_IMAGE_URI")
+ val profileImageUri: Uri? = profileImageUriString?.let { Uri.parse(it) }
+
+ setContent {
+ DattingAPPTheme {
+ UploadIntroVideoScreen(
+ profileImageUri = profileImageUri,
+ onBackClick = { finish() },
+ onSkipClick = { /* Handle skip */ },
+ onSubmit = { videoUri, bioText ->
+ // Final registration submission logic
+ Toast.makeText(this, "Registration Data Ready!", Toast.LENGTH_SHORT).show()
+ }
+ )
+ }
+ }
+ }
+}
+
+
+@Composable
+fun UploadIntroVideoScreen(
+ profileImageUri: Uri?,
+ onBackClick: () -> Unit = {},
+ onSkipClick: () -> Unit = {},
+ onSubmit: (videoUri: Uri?, bioText: String) -> Unit
+) {
+ val context = LocalContext.current
+ var displayMode by remember { mutableStateOf(DisplayMode.IMAGE) }
+ var recordedVideoUri by remember { mutableStateOf(null) }
+ var bioText by remember { mutableStateOf("") }
+
+ // Launchers remain the same...
+ val videoPickerLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.GetContent()
+ ) { uri: Uri? ->
+ if (uri != null) {
+ recordedVideoUri = uri
+ displayMode = DisplayMode.VIDEO
+ }
+ }
+
+ val recordVideoLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.CaptureVideo()
+ ) { success ->
+ if (success && recordedVideoUri != null) {
+ displayMode = DisplayMode.VIDEO
+ }
+ }
+
+ var tempCameraPhotoUri by remember { mutableStateOf(null) }
+ var capturedPhotoUri by remember { mutableStateOf(null) }
+ val takePhotoLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.TakePicture()
+ ) { success ->
+ if (success && tempCameraPhotoUri != null) {
+ capturedPhotoUri = tempCameraPhotoUri
+ displayMode = DisplayMode.IMAGE
+ }
+ }
+
+ val buttonGradient = Brush.horizontalGradient(
+ colors = listOf(Color(0xFFB70054), Color(0xFF6700C7))
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color(0xFFF9F9F9))
+ .systemBarsPadding()
+ // 👇 ADD imePadding() HERE TO RESIZE WHEN KEYBOARD OPENS 👇
+ .imePadding()
+ .padding(horizontal = 20.dp, vertical = 12.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+ // --- Header Section ---
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ IconButton(onClick = onBackClick) {
+ Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", tint = Color.Black)
+ }
+ Text(
+ text = "VibeCheck",
+ fontSize = 22.sp,
+ fontWeight = FontWeight.Bold,
+ color = Color(0xFF9C27B0)
+ )
+ TextButton(onClick = onSkipClick) {
+ Text(text = "SKIP", color = Color.Gray, fontWeight = FontWeight.Bold, fontSize = 12.sp)
+ }
+ }
+
+ // --- Header Title ---
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Text(
+ text = if (displayMode == DisplayMode.BIO) "Your Bio" else "Video Introduction",
+ fontSize = 22.sp,
+ fontWeight = FontWeight.Bold,
+ color = Color(0xFF1E293B)
+ )
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(
+ text = if (displayMode == DisplayMode.BIO)
+ "Write a short intro about yourself (up to 200 words)."
+ else
+ "Say hello! Share a bit about yourself in 60 seconds.",
+ fontSize = 13.sp,
+ color = Color.Gray
+ )
+ }
+
+ // --- Central Media / Text Container ---
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f)
+ .padding(vertical = 12.dp)
+ .clip(RoundedCornerShape(20.dp))
+ .background(Color.LightGray),
+ contentAlignment = Alignment.Center
+ ) {
+ when (displayMode) {
+ DisplayMode.IMAGE -> {
+ if (capturedPhotoUri != null || profileImageUri != null) {
+ Image(
+ painter = rememberAsyncImagePainter(model = capturedPhotoUri ?: profileImageUri),
+ contentDescription = "Profile Image Preview",
+ modifier = Modifier.fillMaxSize(),
+ contentScale = ContentScale.Crop
+ )
+ }
+ }
+
+ DisplayMode.VIDEO -> {
+ recordedVideoUri?.let { uri ->
+ VideoPlayer(videoUri = uri)
+ }
+ }
+
+ DisplayMode.BIO -> {
+ OutlinedTextField(
+ value = bioText,
+ onValueChange = { input ->
+ val words = input.trim().split("\\s+".toRegex())
+ if (input.isEmpty() || words.size <= 200) bioText = input
+ },
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.White)
+ .padding(12.dp),
+ placeholder = { Text("Write something about yourself (max 200 words)...") },
+ colors = TextFieldDefaults.colors(
+ unfocusedContainerColor = Color.White,
+ focusedContainerColor = Color.White
+ )
+ )
+ }
+ }
+
+ // Overlay timer & action icons (hidden during Bio input)
+ if (displayMode != DisplayMode.BIO) {
+ Box(
+ modifier = Modifier
+ .padding(12.dp)
+ .align(Alignment.TopEnd)
+ .clip(RoundedCornerShape(12.dp))
+ .background(Color.Black.copy(alpha = 0.5f))
+ .padding(horizontal = 10.dp, vertical = 4.dp)
+ ) {
+ Text(
+ text = "00:00 / 01:00",
+ color = Color.White,
+ fontSize = 11.sp,
+ fontWeight = FontWeight.SemiBold
+ )
+ }
+
+ Row(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .padding(bottom = 20.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Box(
+ modifier = Modifier
+ .size(48.dp)
+ .clip(CircleShape)
+ .background(Color.White.copy(alpha = 0.85f))
+ .clickable { videoPickerLauncher.launch("video/*") },
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.FileUpload,
+ contentDescription = "Upload Gallery Video",
+ tint = Color(0xFF475569),
+ modifier = Modifier.size(24.dp)
+ )
+ }
+
+ Box(
+ modifier = Modifier
+ .size(64.dp)
+ .clip(CircleShape)
+ .background(Color.White.copy(alpha = 0.9f))
+ .border(2.dp, Color(0xFFE11D48), CircleShape)
+ .clickable {
+ val tempUri = createVideoUri(context)
+ recordedVideoUri = tempUri
+ recordVideoLauncher.launch(tempUri)
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .size(48.dp)
+ .clip(CircleShape)
+ .background(Color(0xFFE11D48)),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.Videocam,
+ contentDescription = "Record Video",
+ tint = Color.White,
+ modifier = Modifier.size(26.dp)
+ )
+ }
+ }
+
+ Box(
+ modifier = Modifier
+ .size(48.dp)
+ .clip(CircleShape)
+ .background(Color.White.copy(alpha = 0.85f))
+ .clickable {
+ val tempUri = createImageUri(context)
+ tempCameraPhotoUri = tempUri
+ takePhotoLauncher.launch(tempUri)
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.PhotoCamera,
+ contentDescription = "Take Photo",
+ tint = Color(0xFF475569),
+ modifier = Modifier.size(24.dp)
+ )
+ }
+ }
+ }
+ }
+
+ // --- Bottom Buttons ---
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ Button(
+ onClick = { onSubmit(recordedVideoUri, bioText) },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(50.dp)
+ .background(buttonGradient, shape = RoundedCornerShape(12.dp)),
+ colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),
+ shape = RoundedCornerShape(12.dp)
+ ) {
+ Text(text = "UPLOAD", color = Color.White, fontWeight = FontWeight.Bold, fontSize = 16.sp)
+ }
+
+ OutlinedButton(
+ onClick = {
+ displayMode = if (displayMode == DisplayMode.BIO) {
+ if (recordedVideoUri != null) DisplayMode.VIDEO else DisplayMode.IMAGE
+ } else {
+ DisplayMode.BIO
+ }
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(50.dp),
+ shape = RoundedCornerShape(12.dp),
+ border = ButtonDefaults.outlinedButtonBorder.copy(
+ brush = Brush.horizontalGradient(listOf(Color(0xFFE11D48), Color(0xFFE11D48)))
+ )
+ ) {
+ Text(
+ text = if (displayMode == DisplayMode.BIO) "VIDEO INTRO" else "ADD BIO",
+ color = Color(0xFFE11D48),
+ fontWeight = FontWeight.Bold,
+ fontSize = 14.sp
+ )
+ }
+ }
+ }
+}
+
+// Media3 ExoPlayer composable
+@Composable
+fun VideoPlayer(videoUri: Uri) {
+ val context = LocalContext.current
+ val exoPlayer = remember(videoUri) {
+ ExoPlayer.Builder(context).build().apply {
+ setMediaItem(MediaItem.fromUri(videoUri))
+ prepare()
+ playWhenReady = true
+ }
+ }
+
+ DisposableEffect(Unit) {
+ onDispose {
+ exoPlayer.release()
+ }
+ }
+
+ AndroidView(
+ factory = { ctx ->
+ PlayerView(ctx).apply {
+ player = exoPlayer
+ useController = true
+ }
+ },
+ modifier = Modifier.fillMaxSize()
+ )
+}
+
+// Helper functions to create temporary File Uris for Video and Image captures
+fun createVideoUri(context: Context): Uri {
+ val tempFile = File.createTempFile("intro_video_", ".mp4", context.cacheDir).apply {
+ createNewFile()
+ deleteOnExit()
+ }
+ return FileProvider.getUriForFile(
+ context,
+ "${context.packageName}.provider",
+ tempFile
+ )
+}
+
+fun createImageUrl(context: Context): Uri {
+ val tempFile = File.createTempFile("intro_photo_", ".jpg", context.cacheDir).apply {
+ createNewFile()
+ deleteOnExit()
+ }
+ return FileProvider.getUriForFile(
+ context,
+ "${context.packageName}.provider",
+ tempFile
+ )
+}
+
+// --- Composable Previews ---
+@Preview(
+ name = "Upload Intro Screen - Full Preview",
+ showBackground = true,
+ showSystemUi = true
+)
+@Composable
+fun UploadIntroVideoScreenPreview() {
+ DattingAPPTheme {
+ UploadIntroVideoScreen(
+ profileImageUri = null,
+ onSubmit = { _, _ -> }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/UploadProfileImageActivity.kt b/app/src/main/java/com/example/dattingapp/UploadProfileImageActivity.kt
new file mode 100644
index 0000000..668f6a6
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/UploadProfileImageActivity.kt
@@ -0,0 +1,417 @@
+package com.example.dattingapp
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import android.widget.Toast
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.outlined.AddAPhoto
+import androidx.compose.material.icons.outlined.CameraAlt
+import androidx.compose.material.icons.outlined.PhotoLibrary
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathEffect
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.core.content.FileProvider
+import coil.compose.rememberAsyncImagePainter
+import java.io.File
+
+class UploadProfileImageActivity : ComponentActivity() {
+
+ private val TAG = "kk"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+
+ // Retrieve intent extras passed from previous registration steps
+ val isPhone = intent.getBooleanExtra("EXTRA_IS_PHONE", false)
+ val countryCode = intent.getStringExtra("EXTRA_COUNTRY_CODE") ?: ""
+ val contactValue = intent.getStringExtra("EXTRA_CONTACT_VALUE") ?: ""
+ val password = intent.getStringExtra("EXTRA_PASSWORD") ?: ""
+ val name = intent.getStringExtra("EXTRA_NAME") ?: ""
+ val dob = intent.getStringExtra("EXTRA_DOB") ?: ""
+ val gender = intent.getStringExtra("EXTRA_GENDER") ?: ""
+ val interests = intent.getStringArrayListExtra("EXTRA_INTERESTS") ?: arrayListOf()
+ val vibe = intent.getStringExtra("EXTRA_VIBE") ?: ""
+
+ Log.d(TAG, "=== Received Registration Data ===")
+ Log.d(TAG, "Is Phone: $isPhone")
+ Log.d(TAG, "Country Code: $countryCode")
+ Log.d(TAG, "Contact Value: $contactValue")
+ Log.d(TAG, "Password: $password")
+ Log.d(TAG, "Name: $name")
+ Log.d(TAG, "Date of Birth: $dob")
+ Log.d(TAG, "Gender: $gender")
+ Log.d(TAG, "Interests: $interests")
+ Log.d(TAG, "Vibe: $vibe")
+ Log.d(TAG, "=================================")
+
+ setContent {
+ MaterialTheme {
+ UploadProfileImageScreen(
+ onBackClick = { finish() },
+ onContinueClick = { selectedUri ->
+ if (selectedUri == null) {
+ Toast.makeText(this@UploadProfileImageActivity, "Please select or take a photo first", Toast.LENGTH_SHORT).show()
+ return@UploadProfileImageScreen
+ }
+
+ val intent = Intent(
+ this@UploadProfileImageActivity,
+ UploadIntroVideoActivity::class.java
+ ).apply {
+ // 1. Pass down all data received from previous steps
+ putExtra("EXTRA_IS_PHONE", isPhone)
+ putExtra("EXTRA_COUNTRY_CODE", countryCode)
+ putExtra("EXTRA_CONTACT_VALUE", contactValue)
+ putExtra("EXTRA_PASSWORD", password)
+ putExtra("EXTRA_NAME", name)
+ putExtra("EXTRA_DOB", dob)
+ putExtra("EXTRA_GENDER", gender)
+ putStringArrayListExtra("EXTRA_INTERESTS", interests)
+ putExtra("EXTRA_VIBE", vibe)
+
+ // 2. Pass the selected Profile Image Uri as a String
+ putExtra("EXTRA_PROFILE_IMAGE_URI", selectedUri.toString())
+ }
+
+ startActivity(intent)
+ }
+ )
+ }
+ }
+ }
+}
+
+// Helper function to create temporary File & Uri for Camera launcher
+fun createImageUri(context: Context): Uri {
+ val tempFile = File.createTempFile("profile_camera_", ".jpg", context.cacheDir).apply {
+ createNewFile()
+ deleteOnExit()
+ }
+ return FileProvider.getUriForFile(
+ context,
+ "${context.packageName}.provider",
+ tempFile
+ )
+}
+
+@Composable
+fun UploadProfileImageScreen(
+ onBackClick: () -> Unit,
+ onContinueClick: (Uri?) -> Unit
+) {
+ val context = LocalContext.current
+
+ // Single state variable to store image source (from either Gallery OR Camera)
+ var selectedImageUri by remember { mutableStateOf(null) }
+ var tempCameraUri by remember { mutableStateOf(null) }
+
+ // 1. Gallery Launcher
+ val galleryLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.GetContent()
+ ) { uri: Uri? ->
+ if (uri != null) {
+ selectedImageUri = uri
+ }
+ }
+
+ // 2. Camera Launcher
+ val cameraLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.TakePicture()
+ ) { success: Boolean ->
+ if (success && tempCameraUri != null) {
+ selectedImageUri = tempCameraUri
+ }
+ }
+
+ // Design Gradients
+ val backgroundGradient = Brush.verticalGradient(
+ colors = listOf(
+ Color(0xFFFFF5F5),
+ Color(0xFFFAFAFA),
+ Color(0xFFF3E8FF)
+ )
+ )
+
+ val buttonGradient = Brush.horizontalGradient(
+ colors = listOf(
+ Color(0xFFC2185B),
+ Color(0xFF7B1FA2)
+ )
+ )
+
+ val circleBorderGradient = Brush.sweepGradient(
+ colors = listOf(
+ Color(0xFF7B1FA2),
+ Color(0xFFC2185B),
+ Color(0xFFE91E63),
+ Color(0xFF7B1FA2)
+ )
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(backgroundGradient)
+ .systemBarsPadding()
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 24.dp, vertical = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+ // Header Bar
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 8.dp)
+ ) {
+ IconButton(
+ onClick = onBackClick,
+ modifier = Modifier.align(Alignment.CenterStart)
+ ) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = "Back",
+ tint = Color.Black
+ )
+ }
+
+ Text(
+ text = "VibeCheck",
+ fontSize = 22.sp,
+ fontWeight = FontWeight.Bold,
+ color = Color(0xFFC2185B),
+ modifier = Modifier.align(Alignment.Center)
+ )
+ }
+
+ // Central Content Area
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(
+ text = "Upload profile photo",
+ fontSize = 24.sp,
+ fontWeight = FontWeight.Bold,
+ color = Color.Black
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = "Choose a photo that shows your best self. You can always change this later.",
+ fontSize = 13.sp,
+ color = Color.Gray,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(horizontal = 20.dp)
+ )
+
+ Spacer(modifier = Modifier.height(32.dp))
+
+ // Profile Circular Container
+ Box(
+ modifier = Modifier
+ .size(190.dp)
+ .clip(CircleShape)
+ .background(Color.White)
+ .border(3.dp, circleBorderGradient, CircleShape),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .size(165.dp)
+ .clip(CircleShape)
+ .drawWithContent {
+ drawContent()
+ drawCircle(
+ color = Color(0xFFF48FB1),
+ style = Stroke(
+ width = 3f,
+ pathEffect = PathEffect.dashPathEffect(floatArrayOf(12f, 12f), 0f)
+ )
+ )
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ if (selectedImageUri != null) {
+ Image(
+ painter = rememberAsyncImagePainter(selectedImageUri),
+ contentDescription = "Profile Photo",
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(CircleShape),
+ contentScale = ContentScale.Crop
+ )
+ } else {
+ Icon(
+ imageVector = Icons.Outlined.AddAPhoto,
+ contentDescription = "Camera Icon",
+ tint = Color(0xFFE57373),
+ modifier = Modifier.size(44.dp)
+ )
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(36.dp))
+
+ // Upload from gallery Button
+ Card(
+ shape = RoundedCornerShape(16.dp),
+ colors = CardDefaults.cardColors(containerColor = Color(0xFFEDE7F6)),
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(52.dp)
+ .clickable { galleryLauncher.launch("image/*") }
+ ) {
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.PhotoLibrary,
+ contentDescription = "Gallery",
+ tint = Color(0xFF673AB7),
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.width(10.dp))
+ Text(
+ text = "Upload from gallery",
+ color = Color(0xFF673AB7),
+ fontSize = 14.sp,
+ fontWeight = FontWeight.SemiBold
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ // Take a photo Button
+ Card(
+ shape = RoundedCornerShape(16.dp),
+ colors = CardDefaults.cardColors(containerColor = Color(0xFFEDE7F6)),
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(52.dp)
+ .clickable {
+ val uri = createImageUri(context)
+ tempCameraUri = uri
+ cameraLauncher.launch(uri)
+ }
+ ) {
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.CameraAlt,
+ contentDescription = "Camera",
+ tint = Color(0xFF673AB7),
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.width(10.dp))
+ Text(
+ text = "Take a photo",
+ color = Color(0xFF673AB7),
+ fontSize = 14.sp,
+ fontWeight = FontWeight.SemiBold
+ )
+ }
+ }
+ }
+
+ // Bottom Continue Section
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Button(
+ onClick = { onContinueClick(selectedImageUri) },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(52.dp)
+ .background(buttonGradient, shape = RoundedCornerShape(26.dp)),
+ colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),
+ shape = RoundedCornerShape(26.dp),
+ contentPadding = PaddingValues()
+ ) {
+ Text(
+ text = "Continue",
+ color = Color.White,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
+
+ Spacer(modifier = Modifier.height(14.dp))
+
+ Text(
+ text = buildAnnotatedString {
+ append("Need help? ")
+ withStyle(style = SpanStyle(color = Color(0xFFC2185B), fontWeight = FontWeight.Bold)) {
+ append("Contact support")
+ }
+ },
+ fontSize = 12.sp,
+ color = Color.Gray,
+ modifier = Modifier.clickable { }
+ )
+ }
+ }
+ }
+}
+
+// 1. Preview default state (No photo selected yet)
+@Preview(
+ name = "Upload Photo - Empty State",
+ showBackground = true,
+ showSystemUi = true
+)
+@Composable
+fun UploadProfileImageScreenPreview() {
+ MaterialTheme {
+ UploadProfileImageScreen(
+ onBackClick = {},
+ onContinueClick = {}
+ )
+ }
+}
diff --git a/app/src/main/java/com/example/dattingapp/registration/ApiService.kt b/app/src/main/java/com/example/dattingapp/registration/ApiService.kt
new file mode 100644
index 0000000..20e81cc
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/registration/ApiService.kt
@@ -0,0 +1,44 @@
+package com.example.dattingapp.registration
+
+import okhttp3.MultipartBody
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.Header
+import retrofit2.http.Multipart
+import retrofit2.http.POST
+import retrofit2.http.Part
+
+interface ApiService {
+
+ // ==========================================
+ // Upload Profile Image
+ // ==========================================
+
+ @Multipart
+ @POST("api/v1/users/me/image")
+ suspend fun uploadProfileImage(
+ @Header("Authorization") authorization: String,
+ @Part image: MultipartBody.Part
+ ): Response
+
+ // ==========================================
+ // Upload Video
+ // ==========================================
+
+ @Multipart
+ @POST("api/v1/users/me/video")
+ suspend fun uploadProfileVideo(
+ @Header("Authorization") authorization: String,
+ @Part video: MultipartBody.Part
+ ): Response
+
+ // ==========================================
+ // Final Registration
+ // ==========================================
+
+ @POST("api/v1/auth/register")
+ suspend fun register(
+ @Body request: RegisterRequest
+ ): Response
+}
+
diff --git a/app/src/main/java/com/example/dattingapp/registration/RegistrationFileUtils.kt b/app/src/main/java/com/example/dattingapp/registration/RegistrationFileUtils.kt
new file mode 100644
index 0000000..fcddb65
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/registration/RegistrationFileUtils.kt
@@ -0,0 +1,46 @@
+package com.example.dattingapp.registration
+
+
+import android.content.Context
+import android.net.Uri
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
+import okhttp3.RequestBody.Companion.asRequestBody
+import java.io.File
+
+fun uriToMultipart(
+ context: Context,
+ uri: Uri,
+ fieldName: String,
+ fileName: String
+): MultipartBody.Part {
+
+ val inputStream = context.contentResolver.openInputStream(uri)
+ ?: throw Exception("Cannot open selected file")
+
+ val tempFile = File(
+ context.cacheDir,
+ fileName
+ )
+
+ inputStream.use { input ->
+ tempFile.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+
+ val mimeType =
+ context.contentResolver.getType(uri)
+ ?: "application/octet-stream"
+
+ val requestBody =
+ tempFile.asRequestBody(
+ mimeType.toMediaTypeOrNull()
+ )
+
+ return MultipartBody.Part.createFormData(
+ fieldName,
+ tempFile.name,
+ requestBody
+ )
+}
diff --git a/app/src/main/java/com/example/dattingapp/registration/RegistrationModels.kt b/app/src/main/java/com/example/dattingapp/registration/RegistrationModels.kt
new file mode 100644
index 0000000..70e54bd
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/registration/RegistrationModels.kt
@@ -0,0 +1,96 @@
+package com.example.dattingapp.registration
+
+
+import com.google.gson.annotations.SerializedName
+
+// ===============================
+// Upload response
+// ===============================
+
+data class UploadResponse(
+ @SerializedName("url")
+ val url: String?,
+
+ @SerializedName("image_url")
+ val imageUrl: String?,
+
+ @SerializedName("video_url")
+ val videoUrl: String?
+) {
+ fun getReturnedUrl(): String? {
+ return url ?: imageUrl ?: videoUrl
+ }
+}
+
+
+// ===============================
+// Registration request
+// ===============================
+
+data class RegisterRequest(
+
+ @SerializedName("email_or_phone")
+ val emailOrPhone: String,
+
+ @SerializedName("password")
+ val password: String,
+
+ @SerializedName("confirm_password")
+ val confirmPassword: String,
+
+ @SerializedName("full_name")
+ val fullName: String,
+
+ @SerializedName("gender")
+ val gender: String,
+
+ @SerializedName("birth_date")
+ val birthDate: String,
+
+ @SerializedName("city")
+ val city: String,
+
+ @SerializedName("bio")
+ val bio: String,
+
+ @SerializedName("video_intro")
+ val videoIntro: String?,
+
+ @SerializedName("profile_image")
+ val profileImage: String?,
+
+ @SerializedName("interests")
+ val interests: List
+)
+
+
+// ===============================
+// Registration response
+// ===============================
+
+data class RegisterResponse(
+ @SerializedName("access_token")
+ val accessToken: String?,
+
+ @SerializedName("refresh_token")
+ val refreshToken: String?,
+
+ @SerializedName("token_type")
+ val tokenType: String?,
+
+ @SerializedName("user")
+ val user: User?
+)
+
+data class User(
+ @SerializedName("id")
+ val id: Int?,
+
+ @SerializedName("email_or_phone")
+ val emailOrPhone: String?,
+
+ @SerializedName("full_name")
+ val fullName: String?
+)
+
+
diff --git a/app/src/main/java/com/example/dattingapp/registration/RegistrationModule.kt b/app/src/main/java/com/example/dattingapp/registration/RegistrationModule.kt
new file mode 100644
index 0000000..9529c51
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/registration/RegistrationModule.kt
@@ -0,0 +1,54 @@
+package com.example.dattingapp.registration
+
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import java.util.concurrent.TimeUnit
+
+object NetworkModule {
+
+ private const val AUTH_BASE_URL =
+ "https://counts-indicates-stud-addressing.trycloudflare.com"
+
+ private const val UPLOAD_BASE_URL =
+ "https://counts-indicates-stud-addressing.trycloudflare.com/"
+
+ private fun createClient(): OkHttpClient {
+
+ val logging = HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }
+
+ return OkHttpClient.Builder()
+ .addInterceptor(logging)
+ .connectTimeout(60, TimeUnit.SECONDS)
+ .readTimeout(120, TimeUnit.SECONDS)
+ .writeTimeout(120, TimeUnit.SECONDS)
+ .build()
+ }
+
+ private fun createRetrofit(baseUrl: String): Retrofit {
+
+ return Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .client(createClient())
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ }
+
+ // Registration API
+ val authApi: ApiService by lazy {
+
+ createRetrofit(AUTH_BASE_URL)
+ .create(ApiService::class.java)
+ }
+
+ // Upload API
+ val uploadApi: ApiService by lazy {
+
+ createRetrofit(UPLOAD_BASE_URL)
+ .create(ApiService::class.java)
+ }
+}
+
diff --git a/app/src/main/java/com/example/dattingapp/registration/RegistrationRepository.kt b/app/src/main/java/com/example/dattingapp/registration/RegistrationRepository.kt
new file mode 100644
index 0000000..6fbfebc
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/registration/RegistrationRepository.kt
@@ -0,0 +1,187 @@
+package com.example.dattingapp.registration
+
+
+import android.content.Context
+import android.net.Uri
+import android.util.Log
+
+class RegistrationRepository(
+ private val context: Context
+) {
+
+ private val uploadApi =
+ NetworkModule.uploadApi
+
+ private val authApi =
+ NetworkModule.authApi
+
+
+ // ==========================================
+ // Upload Image
+ // ==========================================
+
+ suspend fun uploadImage(
+ token: String,
+ uri: Uri
+ ): Result {
+
+ return try {
+
+ val multipart = uriToMultipart(
+ context = context,
+ uri = uri,
+ fieldName = "image",
+ fileName = "profile_image.jpg"
+ )
+
+ val response =
+ uploadApi.uploadProfileImage(
+ authorization = token,
+ image = multipart
+ )
+
+ if (response.isSuccessful) {
+
+ val body = response.body()
+
+ val url = body?.getReturnedUrl()
+
+ if (!url.isNullOrEmpty()) {
+
+ Result.success(url)
+
+ } else {
+
+ Result.failure(
+ Exception(
+ "Image uploaded but backend did not return URL"
+ )
+ )
+ }
+
+ } else {
+
+ Result.failure(
+ Exception(
+ "Image upload failed: ${response.code()} ${response.message()}"
+ )
+ )
+ }
+
+ } catch (e: Exception) {
+
+ Result.failure(e)
+ }
+ }
+
+
+ // ==========================================
+ // Upload Video
+ // ==========================================
+
+ suspend fun uploadVideo(
+ token: String,
+ uri: Uri
+ ): Result {
+
+ return try {
+
+ val multipart = uriToMultipart(
+ context = context,
+ uri = uri,
+ fieldName = "video",
+ fileName = "intro_video.mp4"
+ )
+
+ val response = uploadApi.uploadProfileVideo(
+ authorization = token,
+ video = multipart
+ )
+
+ if (response.isSuccessful) {
+
+ val body = response.body()
+
+ val url = body?.getReturnedUrl()
+
+ if (!url.isNullOrEmpty()) {
+
+ Result.success(url)
+
+ } else {
+
+ Result.failure(
+ Exception(
+ "Video uploaded but backend did not return URL"
+ )
+ )
+ }
+
+ } else {
+
+ Result.failure(
+ Exception(
+ "Video upload failed: ${response.code()} ${response.message()}"
+ )
+ )
+ }
+
+ } catch (e: Exception) {
+
+ Log.e(
+ "UPLOAD_VIDEO",
+ "Exception while uploading video",
+ e
+ )
+
+ Result.failure(e)
+ }
+ }
+
+
+ // ==========================================
+ // Final Registration
+ // ==========================================
+
+ suspend fun register(
+ request: RegisterRequest
+ ): Result {
+
+ return try {
+
+ val response =
+ authApi.register(request)
+
+ if (response.isSuccessful) {
+
+ val body = response.body()
+
+ if (body != null) {
+
+ Result.success(body)
+
+ } else {
+
+ Result.failure(
+ Exception(
+ "Registration response is empty"
+ )
+ )
+ }
+
+ } else {
+
+ Result.failure(
+ Exception(
+ "Registration failed: ${response.code()} ${response.message()}"
+ )
+ )
+ }
+
+ } catch (e: Exception) {
+
+ Result.failure(e)
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/example/dattingapp/registration/RegistrationScreen.kt b/app/src/main/java/com/example/dattingapp/registration/RegistrationScreen.kt
new file mode 100644
index 0000000..563ffd8
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/registration/RegistrationScreen.kt
@@ -0,0 +1,937 @@
+package com.example.dattingapp.registration
+
+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
+
+// 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 = 3 }
+ }
+ } 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 = 3 }
+ }
+ }
+
+ }
+ 3 -> {
+
+ when {
+ passwordInput.length < 6 -> passwordError = "Password must be at least 6 characters"
+ passwordInput != confirmPasswordInput -> passwordError = "Passwords do not match"
+ else -> { passwordError = null; currentStep = 4 }
+ }
+ }
+
+ 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()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/registration/UploadIntroVideoActivity.kt b/app/src/main/java/com/example/dattingapp/registration/UploadIntroVideoActivity.kt
new file mode 100644
index 0000000..3d3dd42
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/registration/UploadIntroVideoActivity.kt
@@ -0,0 +1,1255 @@
+package com.example.dattingapp.registration
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import android.widget.Toast
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.filled.Error
+import androidx.compose.material.icons.filled.FileUpload
+import androidx.compose.material.icons.filled.PhotoCamera
+import androidx.compose.material.icons.filled.Videocam
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+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.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.font.FontWeight
+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 androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.content.FileProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.media3.common.MediaItem
+import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.ui.PlayerView
+import coil.compose.rememberAsyncImagePainter
+import com.example.dattingapp.HomeActivity
+import com.example.dattingapp.ui.theme.BackgroundGradientBottom
+import com.example.dattingapp.ui.theme.BackgroundGradientTop
+import com.example.dattingapp.ui.theme.CardBackground
+import com.example.dattingapp.ui.theme.DarkText
+import com.example.dattingapp.ui.theme.DattingAPPTheme
+import com.example.dattingapp.ui.theme.LightBlueIconBg
+import com.example.dattingapp.ui.theme.MutedText
+import com.example.dattingapp.ui.theme.PrimaryBlue
+import kotlinx.coroutines.launch
+import java.io.File
+
+
+// ==========================================================
+// DISPLAY MODE
+// ==========================================================
+
+enum class DisplayMode {
+ IMAGE,
+ VIDEO,
+ BIO
+}
+
+
+// ==========================================================
+// ACTIVITY
+// ==========================================================
+
+class UploadIntroVideoActivity : ComponentActivity() {
+
+ private var isRegistrationLoading by mutableStateOf(false)
+ private var registrationError by mutableStateOf(null)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+
+
+
+ super.onCreate(savedInstanceState)
+
+ enableEdgeToEdge()
+
+ // =====================================================
+ // Get previous registration information
+ // =====================================================
+
+ val isPhone = intent.getBooleanExtra("EXTRA_IS_PHONE", false)
+ val countryCode = intent.getStringExtra("EXTRA_COUNTRY_CODE") ?: ""
+ val contactValue = intent.getStringExtra("EXTRA_CONTACT_VALUE") ?: ""
+ val password = intent.getStringExtra("EXTRA_PASSWORD") ?: ""
+ val name = intent.getStringExtra("EXTRA_NAME") ?: ""
+ val dob = intent.getStringExtra("EXTRA_DOB") ?: ""
+ val gender = intent.getStringExtra("EXTRA_GENDER") ?: ""
+ val interests = intent.getStringArrayListExtra("EXTRA_INTERESTS") ?: arrayListOf()
+ val profileImageUriString = intent.getStringExtra("EXTRA_PROFILE_IMAGE_URI")
+ val profileImageUri = profileImageUriString?.let { Uri.parse(it) }
+
+ // =====================================================
+ // COMPOSE
+ // =====================================================
+
+ setContent {
+ DattingAPPTheme {
+ Box(modifier = Modifier.fillMaxSize()) {
+ UploadIntroVideoScreen(
+ profileImageUri = profileImageUri,
+ onBackClick = { finish() },
+ onSkipClick = { /* TODO: handle skip */ },
+ onSubmit = { videoUri, bioText ->
+ lifecycleScope.launch {
+ submitRegistration(
+ isPhone = isPhone,
+ countryCode = countryCode,
+ contactValue = contactValue,
+ password = password,
+ name = name,
+ dob = dob,
+ gender = gender,
+ interests = interests,
+ profileImageUri = profileImageUri,
+ videoUri = videoUri,
+ bio = bioText
+ )
+ }
+ }
+ )
+
+ // Overlay: shows loading spinner, or error state if registrationError is set
+ if (isRegistrationLoading) {
+ VideoUploadLoadingScreen(
+ errorMessage = registrationError,
+ onRetry = {
+ isRegistrationLoading = false
+ registrationError = null
+ }
+ )
+ }
+ }
+ }
+ }
+ }
+
+ // ==========================================================
+ // COMPLETE REGISTRATION FLOW
+ // ==========================================================
+
+ private suspend fun submitRegistration(
+ isPhone: Boolean,
+ countryCode: String,
+ contactValue: String,
+ password: String,
+ name: String,
+ dob: String,
+ gender: String,
+ interests: ArrayList,
+ profileImageUri: Uri?,
+ videoUri: Uri?,
+ bio: String
+ ) {
+ isRegistrationLoading = true
+ registrationError = null
+
+ val repository = RegistrationRepository(this)
+
+ try {
+ // STEP 1: Prepare email / phone
+ val emailOrPhone = if (isPhone) {
+ countryCode + contactValue
+ } else {
+ contactValue
+ }
+
+ // STEP 2: REGISTER USER
+ Toast.makeText(this, "Creating your account...", Toast.LENGTH_SHORT).show()
+ val registerRequest = RegisterRequest(
+ emailOrPhone = emailOrPhone,
+ password = password,
+ confirmPassword = password,
+ fullName = name,
+ gender = gender,
+ birthDate = dob,
+ city = "",
+ bio = bio,
+ videoIntro = null,
+ profileImage = null,
+ interests = interests
+ )
+ val registrationResult = repository.register(registerRequest)
+
+ // STEP 3: CHECK REGISTRATION
+ if (registrationResult.isFailure) {
+ val error = registrationResult.exceptionOrNull()?.message ?: "Registration failed"
+ Log.e("REGISTRATION", "Registration failed", registrationResult.exceptionOrNull())
+ registrationError = "Registration failed: $error"
+ return
+ }
+
+ // STEP 4: GET RESPONSE
+ val response = registrationResult.getOrNull()
+ if (response == null) {
+ registrationError = "Empty registration response"
+ return
+ }
+
+ // STEP 5: GET ACCESS TOKEN
+ val accessToken = response.accessToken
+ if (accessToken.isNullOrBlank()) {
+ Log.e("REGISTRATION", "Access token is empty")
+ registrationError = "Access token was not received"
+ return
+ }
+
+ Log.d("REGISTRATION", "Registration successful. Token received.")
+ Toast.makeText(this, "Account created successfully!", Toast.LENGTH_SHORT).show()
+
+ // STEP 6: UPLOAD PROFILE IMAGE
+ if (profileImageUri != null) {
+ Toast.makeText(this, "Uploading profile image...", Toast.LENGTH_SHORT).show()
+ val imageResult = repository.uploadImage(token = "Bearer $accessToken", uri = profileImageUri)
+ if (imageResult.isFailure) {
+ val error = imageResult.exceptionOrNull()?.message ?: "Image upload failed"
+ Log.e("UPLOAD_IMAGE", "Image upload failed", imageResult.exceptionOrNull())
+ registrationError = "Image upload failed: $error"
+ return
+ }
+ Log.d("UPLOAD_IMAGE", "Profile Image URL = ${imageResult.getOrNull()}")
+ Toast.makeText(this, "Profile image uploaded", Toast.LENGTH_SHORT).show()
+ }
+
+ // STEP 7: UPLOAD INTRO VIDEO
+ if (videoUri != null) {
+ Toast.makeText(this, "Uploading intro video...", Toast.LENGTH_SHORT).show()
+ val videoResult = repository.uploadVideo(token = "Bearer $accessToken", uri = videoUri)
+ if (videoResult.isFailure) {
+ val error = videoResult.exceptionOrNull()?.message ?: "Video upload failed"
+ Log.e("UPLOAD_VIDEO", "Video upload failed", videoResult.exceptionOrNull())
+ registrationError = "Video upload failed: $error"
+ return
+ }
+ Log.d("UPLOAD_VIDEO", "Video URL = ${videoResult.getOrNull()}")
+ Toast.makeText(this, "Video uploaded", Toast.LENGTH_SHORT).show()
+ }
+
+ // STEP 8: SUCCESS — GO TO HOME
+ Toast.makeText(this, "Registration completed successfully!", Toast.LENGTH_LONG).show()
+ startActivity(Intent(this, HomeActivity::class.java))
+ finish()
+
+ } catch (e: Exception) {
+ Log.e("REGISTRATION", "Unexpected registration error", e)
+ registrationError = e.message ?: "Something went wrong"
+ }
+ }
+}
+
+
+// ==========================================================
+// COMPOSE UI
+// ==========================================================
+
+@Composable
+fun UploadIntroVideoScreen(
+ profileImageUri: Uri?,
+ onBackClick: () -> Unit = {},
+ onSkipClick: () -> Unit = {},
+ onSubmit: (videoUri: Uri?, bioText: String) -> Unit
+) {
+
+ val context =
+ LocalContext.current
+
+ var displayMode by remember {
+ mutableStateOf(
+ DisplayMode.IMAGE
+ )
+ }
+
+ var recordedVideoUri by remember {
+ mutableStateOf(null)
+ }
+
+ var bioText by remember {
+ mutableStateOf("")
+ }
+
+
+ var tempCameraPhotoUri by remember {
+ mutableStateOf(null)
+ }
+
+ var capturedPhotoUri by remember {
+ mutableStateOf(null)
+ }
+
+
+ // ==========================================================
+ // Gallery video picker
+ // ==========================================================
+
+ val videoPickerLauncher =
+ rememberLauncherForActivityResult(
+ contract =
+ ActivityResultContracts.GetContent()
+ ) { uri ->
+
+ if (uri != null) {
+
+ recordedVideoUri =
+ uri
+
+ displayMode =
+ DisplayMode.VIDEO
+ }
+ }
+
+
+ // ==========================================================
+ // Camera video
+ // ==========================================================
+
+ val recordVideoLauncher =
+ rememberLauncherForActivityResult(
+ contract =
+ ActivityResultContracts.CaptureVideo()
+ ) { success ->
+
+ if (
+ success &&
+ recordedVideoUri != null
+ ) {
+
+ displayMode =
+ DisplayMode.VIDEO
+ }
+ }
+
+
+ // ==========================================================
+ // Camera image
+ // ==========================================================
+
+ val takePhotoLauncher =
+ rememberLauncherForActivityResult(
+ contract =
+ ActivityResultContracts.TakePicture()
+ ) { success ->
+
+ if (
+ success &&
+ tempCameraPhotoUri != null
+ ) {
+
+ capturedPhotoUri =
+ tempCameraPhotoUri
+
+ displayMode =
+ DisplayMode.IMAGE
+ }
+ }
+
+
+ val buttonGradient =
+ Brush.horizontalGradient(
+ colors =
+ listOf(
+ Color(0xFFB70054),
+ Color(0xFF6700C7)
+ )
+ )
+
+
+ // ==========================================================
+ // MAIN COLUMN
+ // ==========================================================
+
+ Column(
+
+ modifier =
+ Modifier
+ .fillMaxSize()
+ .background(
+ Color(0xFFF9F9F9)
+ )
+ .systemBarsPadding()
+ .imePadding()
+ .padding(
+ horizontal = 20.dp,
+ vertical = 12.dp
+ ),
+
+ horizontalAlignment =
+ Alignment.CenterHorizontally,
+
+ verticalArrangement =
+ Arrangement.SpaceBetween
+ ) {
+
+
+ // ======================================================
+ // HEADER
+ // ======================================================
+
+ Row(
+
+ modifier =
+ Modifier.fillMaxWidth(),
+
+ horizontalArrangement =
+ Arrangement.SpaceBetween,
+
+ verticalAlignment =
+ Alignment.CenterVertically
+ ) {
+
+ IconButton(
+ onClick =
+ onBackClick
+ ) {
+
+ Icon(
+ Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = "Back",
+ tint = Color.Black
+ )
+ }
+
+
+ Text(
+ text = "VibeCheck",
+ fontSize = 22.sp,
+ fontWeight = FontWeight.Bold,
+ color = Color(0xFF9C27B0)
+ )
+
+
+ TextButton(
+ onClick =
+ onSkipClick
+ ) {
+
+ Text(
+ text = "SKIP",
+ color = Color.Gray,
+ fontWeight = FontWeight.Bold,
+ fontSize = 12.sp
+ )
+ }
+ }
+
+
+ // ======================================================
+ // TITLE
+ // ======================================================
+
+ Column(
+ horizontalAlignment =
+ Alignment.CenterHorizontally
+ ) {
+
+ Text(
+ text =
+ if (
+ displayMode ==
+ DisplayMode.BIO
+ ) {
+ "Your Bio"
+ } else {
+ "Video Introduction"
+ },
+
+ fontSize = 22.sp,
+
+ fontWeight =
+ FontWeight.Bold,
+
+ color =
+ Color(0xFF1E293B)
+ )
+
+
+ Spacer(
+ modifier =
+ Modifier.height(4.dp)
+ )
+
+
+ Text(
+ text =
+ if (
+ displayMode ==
+ DisplayMode.BIO
+ ) {
+ "Write a short intro about yourself."
+ } else {
+ "Say hello! Share a bit about yourself."
+ },
+
+ fontSize = 13.sp,
+
+ color =
+ Color.Gray
+ )
+ }
+
+
+ // ======================================================
+ // MAIN CONTAINER
+ // ======================================================
+
+ Box(
+
+ modifier =
+ Modifier
+ .fillMaxWidth()
+ .weight(1f)
+ .padding(
+ vertical = 12.dp
+ )
+ .clip(
+ RoundedCornerShape(
+ 20.dp
+ )
+ )
+ .background(
+ Color.LightGray
+ ),
+
+ contentAlignment =
+ Alignment.Center
+ ) {
+
+ when (displayMode) {
+
+
+ // =================================================
+ // IMAGE
+ // =================================================
+
+ DisplayMode.IMAGE -> {
+
+ if (
+ capturedPhotoUri != null ||
+ profileImageUri != null
+ ) {
+
+ Image(
+
+ painter =
+ rememberAsyncImagePainter(
+ model =
+ capturedPhotoUri
+ ?: profileImageUri
+ ),
+
+ contentDescription =
+ "Profile Image",
+
+ modifier =
+ Modifier.fillMaxSize(),
+
+ contentScale =
+ ContentScale.Crop
+ )
+ }
+ }
+
+
+ // =================================================
+ // VIDEO
+ // =================================================
+
+ DisplayMode.VIDEO -> {
+
+ recordedVideoUri?.let {
+
+ VideoPlayer(
+ videoUri = it
+ )
+ }
+ }
+
+
+ // =================================================
+ // BIO
+ // =================================================
+
+ DisplayMode.BIO -> {
+
+ OutlinedTextField(
+
+ value =
+ bioText,
+
+ onValueChange = {
+
+ val words =
+ it.trim()
+ .split(
+ "\\s+".toRegex()
+ )
+
+ if (
+ it.isEmpty() ||
+ words.size <= 200
+ ) {
+
+ bioText =
+ it
+ }
+ },
+
+ modifier =
+ Modifier
+ .fillMaxSize()
+ .background(
+ Color.White
+ )
+ .padding(12.dp),
+
+ placeholder = {
+ Text(
+ "Write something about yourself..."
+ )
+ },
+
+ colors =
+ TextFieldDefaults.colors(
+
+ unfocusedContainerColor =
+ Color.White,
+
+ focusedContainerColor =
+ Color.White
+ )
+ )
+ }
+ }
+
+
+ // =====================================================
+ // MEDIA BUTTONS
+ // =====================================================
+
+ if (
+ displayMode !=
+ DisplayMode.BIO
+ ) {
+
+ Row(
+
+ modifier =
+ Modifier
+ .align(
+ Alignment.BottomCenter
+ )
+ .padding(
+ bottom = 20.dp
+ ),
+
+ horizontalArrangement =
+ Arrangement.spacedBy(
+ 16.dp
+ ),
+
+ verticalAlignment =
+ Alignment.CenterVertically
+ ) {
+
+
+ // =================================================
+ // GALLERY VIDEO
+ // =================================================
+
+ Box(
+
+ modifier =
+ Modifier
+ .size(48.dp)
+ .clip(
+ CircleShape
+ )
+ .background(
+ Color.White.copy(
+ alpha = 0.85f
+ )
+ )
+ .clickable {
+
+ videoPickerLauncher.launch(
+ "video/*"
+ )
+ },
+
+ contentAlignment =
+ Alignment.Center
+ ) {
+
+ Icon(
+
+ Icons.Default.FileUpload,
+
+ contentDescription =
+ "Upload Video",
+
+ tint =
+ Color(0xFF475569)
+ )
+ }
+
+
+ // =================================================
+ // RECORD VIDEO
+ // =================================================
+
+ Box(
+
+ modifier =
+ Modifier
+ .size(64.dp)
+ .clip(
+ CircleShape
+ )
+ .background(
+ Color.White
+ )
+ .border(
+ 2.dp,
+ Color(0xFFE11D48),
+ CircleShape
+ )
+ .clickable {
+
+ val uri =
+ createVideoUri(
+ context
+ )
+
+ recordedVideoUri =
+ uri
+
+ recordVideoLauncher.launch(
+ uri
+ )
+ },
+
+ contentAlignment =
+ Alignment.Center
+ ) {
+
+ Box(
+
+ modifier =
+ Modifier
+ .size(48.dp)
+ .clip(
+ CircleShape
+ )
+ .background(
+ Color(0xFFE11D48)
+ ),
+
+ contentAlignment =
+ Alignment.Center
+ ) {
+
+ Icon(
+
+ Icons.Default.Videocam,
+
+ contentDescription =
+ "Record Video",
+
+ tint =
+ Color.White,
+
+ modifier =
+ Modifier.size(
+ 26.dp
+ )
+ )
+ }
+ }
+
+
+ // =================================================
+ // TAKE PHOTO
+ // =================================================
+
+ Box(
+
+ modifier =
+ Modifier
+ .size(48.dp)
+ .clip(
+ CircleShape
+ )
+ .background(
+ Color.White.copy(
+ alpha = 0.85f
+ )
+ )
+ .clickable {
+
+ val uri =
+ createIntroImageUri(
+ context
+ )
+
+ tempCameraPhotoUri =
+ uri
+
+ takePhotoLauncher.launch(
+ uri
+ )
+ },
+
+ contentAlignment =
+ Alignment.Center
+ ) {
+
+ Icon(
+
+ Icons.Default.PhotoCamera,
+
+ contentDescription =
+ "Take Photo",
+
+ tint =
+ Color(0xFF475569)
+ )
+ }
+ }
+ }
+ }
+
+
+ // ==========================================================
+ // BOTTOM BUTTONS
+ // ==========================================================
+
+ Column(
+
+ modifier =
+ Modifier.fillMaxWidth(),
+
+ verticalArrangement =
+ Arrangement.spacedBy(
+ 10.dp
+ )
+ ) {
+
+ Button(
+
+ onClick = {
+
+ onSubmit(
+ recordedVideoUri,
+ bioText
+ )
+ },
+
+ modifier =
+ Modifier
+ .fillMaxWidth()
+ .height(50.dp)
+ .background(
+ buttonGradient,
+ RoundedCornerShape(
+ 12.dp
+ )
+ ),
+
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor =
+ Color.Transparent
+ ),
+
+ shape =
+ RoundedCornerShape(
+ 12.dp
+ )
+ ) {
+
+ Text(
+
+ text = "UPLOAD",
+
+ color =
+ Color.White,
+
+ fontWeight =
+ FontWeight.Bold,
+
+ fontSize =
+ 16.sp
+ )
+ }
+
+
+ OutlinedButton(
+
+ onClick = {
+
+ displayMode =
+ if (
+ displayMode ==
+ DisplayMode.BIO
+ ) {
+
+ if (
+ recordedVideoUri !=
+ null
+ ) {
+
+ DisplayMode.VIDEO
+
+ } else {
+
+ DisplayMode.IMAGE
+ }
+
+ } else {
+
+ DisplayMode.BIO
+ }
+ },
+
+ modifier =
+ Modifier
+ .fillMaxWidth()
+ .height(50.dp),
+
+ shape =
+ RoundedCornerShape(
+ 12.dp
+ )
+ ) {
+
+ Text(
+
+ text =
+ if (
+ displayMode ==
+ DisplayMode.BIO
+ ) {
+ "VIDEO INTRO"
+ } else {
+ "ADD BIO"
+ },
+
+ color =
+ Color(0xFFE11D48),
+
+ fontWeight =
+ FontWeight.Bold,
+
+ fontSize =
+ 14.sp
+ )
+ }
+ }
+ }
+}
+
+
+// ==========================================================
+// VIDEO PLAYER
+// ==========================================================
+
+@Composable
+fun VideoPlayer(
+ videoUri: Uri
+) {
+
+ val context =
+ LocalContext.current
+
+
+ val exoPlayer =
+ remember(videoUri) {
+
+ ExoPlayer
+ .Builder(context)
+ .build()
+ .apply {
+
+ setMediaItem(
+ MediaItem.fromUri(
+ videoUri
+ )
+ )
+
+ prepare()
+
+ playWhenReady =
+ true
+ }
+ }
+
+
+ DisposableEffect(
+ exoPlayer
+ ) {
+
+ onDispose {
+
+ exoPlayer.release()
+ }
+ }
+
+
+ AndroidView(
+
+ factory = { ctx ->
+
+ PlayerView(ctx).apply {
+
+ player =
+ exoPlayer
+
+ useController =
+ true
+ }
+ },
+
+ modifier =
+ Modifier.fillMaxSize()
+ )
+}
+
+
+// ==========================================================
+// CREATE VIDEO URI
+// ==========================================================
+
+fun createVideoUri(
+ context: Context
+): Uri {
+
+ val file =
+ File.createTempFile(
+ "intro_video_",
+ ".mp4",
+ context.cacheDir
+ )
+
+
+ return FileProvider.getUriForFile(
+
+ context,
+
+ "${context.packageName}.provider",
+
+ file
+ )
+}
+
+
+// ==========================================================
+// CREATE IMAGE URI
+// ==========================================================
+
+fun createIntroImageUri(
+ context: Context
+): Uri {
+
+ val file =
+ File.createTempFile(
+ "profile_image_",
+ ".jpg",
+ context.cacheDir
+ )
+
+
+ return FileProvider.getUriForFile(
+
+ context,
+
+ "${context.packageName}.provider",
+
+ file
+ )
+}
+
+
+@Composable
+fun VideoUploadLoadingScreen(
+ errorMessage: String? = null,
+ onRetry: () -> Unit = {}
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ brush = Brush.verticalGradient(
+ colors = listOf(
+ BackgroundGradientTop,
+ BackgroundGradientBottom
+ )
+ )
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Card(
+ modifier = Modifier
+ .fillMaxWidth(0.85f)
+ .wrapContentHeight(),
+ shape = RoundedCornerShape(24.dp),
+ colors = CardDefaults.cardColors(containerColor = CardBackground),
+ elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 24.dp, vertical = 36.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ if (errorMessage == null) {
+ // ==================== LOADING STATE ====================
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier.size(80.dp)
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier.fillMaxSize(),
+ color = PrimaryBlue,
+ strokeWidth = 3.dp
+ )
+ Box(
+ modifier = Modifier
+ .size(56.dp)
+ .clip(RoundedCornerShape(16.dp))
+ .background(LightBlueIconBg),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.Videocam,
+ contentDescription = "Video Icon",
+ tint = Color.White,
+ modifier = Modifier.size(32.dp)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(28.dp))
+
+ Text(
+ text = "Uploading your vibe...",
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Bold,
+ color = DarkText,
+ textAlign = TextAlign.Center
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = "This will only take a moment.",
+ fontSize = 13.sp,
+ color = MutedText,
+ textAlign = TextAlign.Center
+ )
+
+ } else {
+ // ==================== ERROR STATE ====================
+ Box(
+ contentAlignment = Alignment.Center,
+ modifier = Modifier.size(80.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .size(64.dp)
+ .clip(RoundedCornerShape(16.dp))
+ .background(Color(0xFFFFEBEE)),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = Icons.Filled.Error,
+ contentDescription = "Error",
+ tint = Color(0xFFE53935),
+ modifier = Modifier.size(32.dp)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(28.dp))
+
+ Text(
+ text = "Upload Failed",
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Bold,
+ color = DarkText,
+ textAlign = TextAlign.Center
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = errorMessage,
+ fontSize = 13.sp,
+ color = MutedText,
+ textAlign = TextAlign.Center
+ )
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ Button(
+ onClick = onRetry,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(48.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color(0xFFE53935)
+ ),
+ shape = RoundedCornerShape(12.dp)
+ ) {
+ Text(
+ text = "Try Again",
+ color = Color.White,
+ fontWeight = FontWeight.Bold,
+ fontSize = 16.sp
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true, widthDp = 360, heightDp = 800)
+@Composable
+fun VideoUploadLoadingScreenPreview() {
+ MaterialTheme {
+ VideoUploadLoadingScreen()
+ }
+}
+
+@Preview(showBackground = true, widthDp = 360, heightDp = 800)
+@Composable
+fun VideoUploadErrorScreenPreview() {
+ MaterialTheme {
+ VideoUploadLoadingScreen(
+ errorMessage = "Network connection failed. Please try again.",
+ onRetry = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/registration/UploadProfileImageActivity.kt b/app/src/main/java/com/example/dattingapp/registration/UploadProfileImageActivity.kt
new file mode 100644
index 0000000..33794d8
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/registration/UploadProfileImageActivity.kt
@@ -0,0 +1,417 @@
+package com.example.dattingapp.registration
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import android.widget.Toast
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.outlined.AddAPhoto
+import androidx.compose.material.icons.outlined.CameraAlt
+import androidx.compose.material.icons.outlined.PhotoLibrary
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathEffect
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.withStyle
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.core.content.FileProvider
+import coil.compose.rememberAsyncImagePainter
+import java.io.File
+
+class UploadProfileImageActivity : ComponentActivity() {
+
+ private val TAG = "kk"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+
+ // Retrieve intent extras passed from previous registration steps
+ val isPhone = intent.getBooleanExtra("EXTRA_IS_PHONE", false)
+ val countryCode = intent.getStringExtra("EXTRA_COUNTRY_CODE") ?: ""
+ val contactValue = intent.getStringExtra("EXTRA_CONTACT_VALUE") ?: ""
+ val password = intent.getStringExtra("EXTRA_PASSWORD") ?: ""
+ val name = intent.getStringExtra("EXTRA_NAME") ?: ""
+ val dob = intent.getStringExtra("EXTRA_DOB") ?: ""
+ val gender = intent.getStringExtra("EXTRA_GENDER") ?: ""
+ val interests = intent.getStringArrayListExtra("EXTRA_INTERESTS") ?: arrayListOf()
+ val vibe = intent.getStringExtra("EXTRA_VIBE") ?: ""
+
+ Log.d(TAG, "=== Received Registration Data ===")
+ Log.d(TAG, "Is Phone: $isPhone")
+ Log.d(TAG, "Country Code: $countryCode")
+ Log.d(TAG, "Contact Value: $contactValue")
+ Log.d(TAG, "Password: $password")
+ Log.d(TAG, "Name: $name")
+ Log.d(TAG, "Date of Birth: $dob")
+ Log.d(TAG, "Gender: $gender")
+ Log.d(TAG, "Interests: $interests")
+ Log.d(TAG, "Vibe: $vibe")
+ Log.d(TAG, "=================================")
+
+ setContent {
+ MaterialTheme {
+ UploadProfileImageScreen(
+ onBackClick = { finish() },
+ onContinueClick = { selectedUri ->
+ if (selectedUri == null) {
+ Toast.makeText(this@UploadProfileImageActivity, "Please select or take a photo first", Toast.LENGTH_SHORT).show()
+ return@UploadProfileImageScreen
+ }
+
+ val intent = Intent(
+ this@UploadProfileImageActivity,
+ UploadIntroVideoActivity::class.java
+ ).apply {
+ // 1. Pass down all data received from previous steps
+ putExtra("EXTRA_IS_PHONE", isPhone)
+ putExtra("EXTRA_COUNTRY_CODE", countryCode)
+ putExtra("EXTRA_CONTACT_VALUE", contactValue)
+ putExtra("EXTRA_PASSWORD", password)
+ putExtra("EXTRA_NAME", name)
+ putExtra("EXTRA_DOB", dob)
+ putExtra("EXTRA_GENDER", gender)
+ putStringArrayListExtra("EXTRA_INTERESTS", interests)
+ putExtra("EXTRA_VIBE", vibe)
+
+ // 2. Pass the selected Profile Image Uri as a String
+ putExtra("EXTRA_PROFILE_IMAGE_URI", selectedUri.toString())
+ }
+
+ startActivity(intent)
+ }
+ )
+ }
+ }
+ }
+}
+
+// Helper function to create temporary File & Uri for Camera launcher
+fun createImageUri(context: Context): Uri {
+ val tempFile = File.createTempFile("profile_camera_", ".jpg", context.cacheDir).apply {
+ createNewFile()
+ deleteOnExit()
+ }
+ return FileProvider.getUriForFile(
+ context,
+ "${context.packageName}.provider",
+ tempFile
+ )
+}
+
+@Composable
+fun UploadProfileImageScreen(
+ onBackClick: () -> Unit,
+ onContinueClick: (Uri?) -> Unit
+) {
+ val context = LocalContext.current
+
+ // Single state variable to store image source (from either Gallery OR Camera)
+ var selectedImageUri by remember { mutableStateOf(null) }
+ var tempCameraUri by remember { mutableStateOf(null) }
+
+ // 1. Gallery Launcher
+ val galleryLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.GetContent()
+ ) { uri: Uri? ->
+ if (uri != null) {
+ selectedImageUri = uri
+ }
+ }
+
+ // 2. Camera Launcher
+ val cameraLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.TakePicture()
+ ) { success: Boolean ->
+ if (success && tempCameraUri != null) {
+ selectedImageUri = tempCameraUri
+ }
+ }
+
+ // Design Gradients
+ val backgroundGradient = Brush.verticalGradient(
+ colors = listOf(
+ Color(0xFFFFF5F5),
+ Color(0xFFFAFAFA),
+ Color(0xFFF3E8FF)
+ )
+ )
+
+ val buttonGradient = Brush.horizontalGradient(
+ colors = listOf(
+ Color(0xFFC2185B),
+ Color(0xFF7B1FA2)
+ )
+ )
+
+ val circleBorderGradient = Brush.sweepGradient(
+ colors = listOf(
+ Color(0xFF7B1FA2),
+ Color(0xFFC2185B),
+ Color(0xFFE91E63),
+ Color(0xFF7B1FA2)
+ )
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(backgroundGradient)
+ .systemBarsPadding()
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 24.dp, vertical = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+ // Header Bar
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 8.dp)
+ ) {
+ IconButton(
+ onClick = onBackClick,
+ modifier = Modifier.align(Alignment.CenterStart)
+ ) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = "Back",
+ tint = Color.Black
+ )
+ }
+
+ Text(
+ text = "VibeCheck",
+ fontSize = 22.sp,
+ fontWeight = FontWeight.Bold,
+ color = Color(0xFFC2185B),
+ modifier = Modifier.align(Alignment.Center)
+ )
+ }
+
+ // Central Content Area
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(
+ text = "Upload profile photo",
+ fontSize = 24.sp,
+ fontWeight = FontWeight.Bold,
+ color = Color.Black
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = "Choose a photo that shows your best self. You can always change this later.",
+ fontSize = 13.sp,
+ color = Color.Gray,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(horizontal = 20.dp)
+ )
+
+ Spacer(modifier = Modifier.height(32.dp))
+
+ // Profile Circular Container
+ Box(
+ modifier = Modifier
+ .size(190.dp)
+ .clip(CircleShape)
+ .background(Color.White)
+ .border(3.dp, circleBorderGradient, CircleShape),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .size(165.dp)
+ .clip(CircleShape)
+ .drawWithContent {
+ drawContent()
+ drawCircle(
+ color = Color(0xFFF48FB1),
+ style = Stroke(
+ width = 3f,
+ pathEffect = PathEffect.dashPathEffect(floatArrayOf(12f, 12f), 0f)
+ )
+ )
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ if (selectedImageUri != null) {
+ Image(
+ painter = rememberAsyncImagePainter(selectedImageUri),
+ contentDescription = "Profile Photo",
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(CircleShape),
+ contentScale = ContentScale.Crop
+ )
+ } else {
+ Icon(
+ imageVector = Icons.Outlined.AddAPhoto,
+ contentDescription = "Camera Icon",
+ tint = Color(0xFFE57373),
+ modifier = Modifier.size(44.dp)
+ )
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(36.dp))
+
+ // Upload from gallery Button
+ Card(
+ shape = RoundedCornerShape(16.dp),
+ colors = CardDefaults.cardColors(containerColor = Color(0xFFEDE7F6)),
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(52.dp)
+ .clickable { galleryLauncher.launch("image/*") }
+ ) {
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.PhotoLibrary,
+ contentDescription = "Gallery",
+ tint = Color(0xFF673AB7),
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.width(10.dp))
+ Text(
+ text = "Upload from gallery",
+ color = Color(0xFF673AB7),
+ fontSize = 14.sp,
+ fontWeight = FontWeight.SemiBold
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ // Take a photo Button
+ Card(
+ shape = RoundedCornerShape(16.dp),
+ colors = CardDefaults.cardColors(containerColor = Color(0xFFEDE7F6)),
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(52.dp)
+ .clickable {
+ val uri = createImageUri(context)
+ tempCameraUri = uri
+ cameraLauncher.launch(uri)
+ }
+ ) {
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.CameraAlt,
+ contentDescription = "Camera",
+ tint = Color(0xFF673AB7),
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.width(10.dp))
+ Text(
+ text = "Take a photo",
+ color = Color(0xFF673AB7),
+ fontSize = 14.sp,
+ fontWeight = FontWeight.SemiBold
+ )
+ }
+ }
+ }
+
+ // Bottom Continue Section
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Button(
+ onClick = { onContinueClick(selectedImageUri) },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(52.dp)
+ .background(buttonGradient, shape = RoundedCornerShape(26.dp)),
+ colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),
+ shape = RoundedCornerShape(26.dp),
+ contentPadding = PaddingValues()
+ ) {
+ Text(
+ text = "Continue",
+ color = Color.White,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
+
+ Spacer(modifier = Modifier.height(14.dp))
+
+ Text(
+ text = buildAnnotatedString {
+ append("Need help? ")
+ withStyle(style = SpanStyle(color = Color(0xFFC2185B), fontWeight = FontWeight.Bold)) {
+ append("Contact support")
+ }
+ },
+ fontSize = 12.sp,
+ color = Color.Gray,
+ modifier = Modifier.clickable { }
+ )
+ }
+ }
+ }
+}
+
+// 1. Preview default state (No photo selected yet)
+@Preview(
+ name = "Upload Photo - Empty State",
+ showBackground = true,
+ showSystemUi = true
+)
+@Composable
+fun UploadProfileImageScreenPreview() {
+ MaterialTheme {
+ UploadProfileImageScreen(
+ onBackClick = {},
+ onContinueClick = {}
+ )
+ }
+}
diff --git a/app/src/main/java/com/example/dattingapp/ui/theme/Color.kt b/app/src/main/java/com/example/dattingapp/ui/theme/Color.kt
index 952c8b1..c6425a2 100644
--- a/app/src/main/java/com/example/dattingapp/ui/theme/Color.kt
+++ b/app/src/main/java/com/example/dattingapp/ui/theme/Color.kt
@@ -1,5 +1,6 @@
package com.example.dattingapp.ui.theme
+import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
@@ -35,3 +36,40 @@ val AccentPurple = Color(0xFF6C11D9)
val BadgePink = Color(0xFFFF2A7A)
val ChipBgPink = Color(0xFFFDE8EF)
val ChipTextPink = Color(0xFFD81B60)
+val primaryColor = Color(0xFFC2185B)
+val lightChipBg = Color(0xFFFCE4EC)
+val CrimsonPrimary = Color(0xFFC2185B) // Primary brand accent color used for toggles, icons, & titles
+val LightCardBg = Color(0xFFF9F9F9) // Background color for rounded card sections
+
+val backgroundGradient = Brush.verticalGradient(
+ colors = listOf(
+ Color(0xFFFFF7F8),
+ Color(0xFFFFFFFF),
+ Color(0xFFF7EFFC)
+ )
+)
+
+val buttonGradient = Brush.horizontalGradient(
+ colors = listOf(
+ Color(0xFFD84B75),
+ Color(0xFF8E54C1)
+ )
+)
+
+val avatarGradientBorder = Brush.sweepGradient(
+ colors = listOf(
+ Color(0xFFB00020),
+ Color(0xFF7B1FA2),
+ Color(0xFFB00020)
+ )
+)
+
+
+// Color definitions matching the design mockup
+val BackgroundGradientTop = Color(0xFF916A8C)
+val BackgroundGradientBottom = Color(0xFF635270)
+val PrimaryBlue = Color(0xFF0066FF)
+val LightBlueIconBg = Color(0xFF8CAEF8)
+val DarkText = Color(0xFF1E232A)
+val MutedText = Color(0xFF757575)
+
diff --git a/app/src/main/res/drawable/loverobot.png b/app/src/main/res/drawable/loverobot.png
new file mode 100644
index 0000000..ae85040
Binary files /dev/null and b/app/src/main/res/drawable/loverobot.png differ
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4be336d..81f8ef8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -85,6 +85,10 @@
ExploreScreen
ChatScreen
FriendProfile
+ MeetActivity
+ SettingScreen
+ UploadImageActivity
+ UploadIntroVideoActivity
\ No newline at end of file
diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..08f6445
--- /dev/null
+++ b/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ef7459c..d7053ab 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -12,6 +12,8 @@ material = "1.10.0"
appcompat = "1.7.1"
constraintlayout = "2.1.4"
activityKtx = "1.8.0"
+media3ExoplayerVersion = "1.5.1"
+media3UiVersion = "1.5.1"
navigationFragmentKtx = "2.6.0"
navigationUiKtx = "2.6.0"
material3 = "1.4.0"
@@ -38,6 +40,8 @@ androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", ve
androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
+media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3UiVersion" }
+media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3ExoplayerVersion" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }