Browse Source

first commit

master
osman160128 2 months ago
parent
commit
fb851a73d9
  1. 22
      app/build.gradle.kts
  2. 45
      app/src/main/AndroidManifest.xml
  3. 1
      app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt
  4. 486
      app/src/main/java/com/example/dattingapp/Login/RegistrationScreen.kt
  5. 300
      app/src/main/java/com/example/dattingapp/Login/UploadProfileImgae.kt
  6. 271
      app/src/main/java/com/example/dattingapp/Login/VibeType.kt
  7. 338
      app/src/main/java/com/example/dattingapp/Login/VideoIntroScreen.kt
  8. 11
      app/src/main/java/com/example/dattingapp/Login/ui/theme/Color.kt
  9. 58
      app/src/main/java/com/example/dattingapp/Login/ui/theme/Theme.kt
  10. 34
      app/src/main/java/com/example/dattingapp/Login/ui/theme/Type.kt
  11. 4
      app/src/main/java/com/example/dattingapp/MainActivity.kt
  12. 479
      app/src/main/java/com/example/dattingapp/MeetActivity.kt
  13. 944
      app/src/main/java/com/example/dattingapp/RegistrationScreen.kt
  14. 439
      app/src/main/java/com/example/dattingapp/UploadIntroVideoActivity.kt
  15. 417
      app/src/main/java/com/example/dattingapp/UploadProfileImageActivity.kt
  16. 44
      app/src/main/java/com/example/dattingapp/registration/ApiService.kt
  17. 46
      app/src/main/java/com/example/dattingapp/registration/RegistrationFileUtils.kt
  18. 96
      app/src/main/java/com/example/dattingapp/registration/RegistrationModels.kt
  19. 54
      app/src/main/java/com/example/dattingapp/registration/RegistrationModule.kt
  20. 187
      app/src/main/java/com/example/dattingapp/registration/RegistrationRepository.kt
  21. 937
      app/src/main/java/com/example/dattingapp/registration/RegistrationScreen.kt
  22. 1255
      app/src/main/java/com/example/dattingapp/registration/UploadIntroVideoActivity.kt
  23. 417
      app/src/main/java/com/example/dattingapp/registration/UploadProfileImageActivity.kt
  24. 38
      app/src/main/java/com/example/dattingapp/ui/theme/Color.kt
  25. BIN
      app/src/main/res/drawable/loverobot.png
  26. 4
      app/src/main/res/values/strings.xml
  27. 5
      app/src/main/res/xml/file_paths.xml
  28. 4
      gradle/libs.versions.toml

22
app/build.gradle.kts

@ -59,7 +59,27 @@ dependencies {
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
debugImplementation(libs.androidx.compose.ui.test.manifest) debugImplementation(libs.androidx.compose.ui.test.manifest)
debugImplementation(libs.androidx.compose.ui.tooling) 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("io.coil-kt:coil-compose:2.6.0")
implementation("androidx.navigation:navigation-compose:2.9.3") 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)
} }

45
app/src/main/AndroidManifest.xml

@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"
@ -57,23 +58,11 @@
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".Login.RegistrationScreen" android:name=".registration.RegistrationScreen"
android:exported="false" android:exported="false"
android:label="@string/title_activity_registration_screen" android:label="@string/title_activity_registration_screen"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity
android:name=".Login.VedioIntroScreen"
android:exported="false"
android:label="@string/title_activity_video_intro_screen"
android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".Login.UploadProfileImgae"
android:exported="false"
android:label="@string/title_activity_upload_profile_image"
android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".Login.LoginActivity" android:name=".Login.LoginActivity"
android:exported="false" android:exported="false"
@ -128,6 +117,36 @@
android:label="@string/title_activity_friend_profile" android:label="@string/title_activity_friend_profile"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity
android:name=".MeetActivity"
android:exported="false"
android:label="@string/title_activity_meet"
android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" />
<!-- FileProvider declaration -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.dattingapp.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<activity
android:name=".registration.UploadProfileImageActivity"
android:exported="false"
android:label="@string/title_activity_upload_profile_image"
android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".registration.UploadIntroVideoActivity"
android:exported="false"
android:label="@string/title_activity_upload_intro_video"
android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" />
</application> </application>
</manifest> </manifest>

1
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 androidx.compose.ui.unit.sp
import com.example.dattingapp.ui.theme.DattingAPPTheme import com.example.dattingapp.ui.theme.DattingAPPTheme
import com.example.dattingapp.R import com.example.dattingapp.R
import com.example.dattingapp.registration.RegistrationScreen
// Renamed Activity to LoginActivity to fix the shadowing collision // Renamed Activity to LoginActivity to fix the shadowing collision
class LoginActivity : ComponentActivity() { class LoginActivity : ComponentActivity() {

486
app/src/main/java/com/example/dattingapp/Login/RegistrationScreen.kt

@ -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<String>
) {
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 = {}
)
}
}

300
app/src/main/java/com/example/dattingapp/Login/UploadProfileImgae.kt

@ -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()
}

271
app/src/main/java/com/example/dattingapp/Login/VibeType.kt

@ -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<VibeOption?>(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()
}

338
app/src/main/java/com/example/dattingapp/Login/VideoIntroScreen.kt

@ -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
}
)
}
}

11
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)

58
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
)
}

34
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
)
*/
)

4
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.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.ui.platform.LocalContext 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.permission.NotificationPermission
import com.example.dattingapp.ui.theme.DattingAPPTheme import com.example.dattingapp.ui.theme.DattingAPPTheme
@ -18,7 +16,7 @@ class MainActivity : ComponentActivity() {
setContent { setContent {
DattingAPPTheme { DattingAPPTheme {
val context = LocalContext.current val context = LocalContext.current
val intent = Intent(context, HomeActivity::class.java) val intent = Intent(context, NotificationPermission::class.java)
context.startActivity(intent) context.startActivity(intent)
finish() finish()
} }

479
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<String> = 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()
)
}
}
}

944
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<String?>(null) }
var passwordInput by remember { mutableStateOf("") }
var confirmPasswordInput by remember { mutableStateOf("") }
var passwordError by remember { mutableStateOf<String?>(null) }
var nameInput by remember { mutableStateOf("") }
var nameError by remember { mutableStateOf<String?>(null) }
var dobInput by remember { mutableStateOf("") }
var dobError by remember { mutableStateOf<String?>(null) }
var genderInput by remember { mutableStateOf("") }
var genderError by remember { mutableStateOf<String?>(null) }
val selectedInterests = remember { mutableStateListOf<String>() }
var interestsError by remember { mutableStateOf<String?>(null) }
var vibeInput by remember { mutableStateOf("") }
var vibeError by remember { mutableStateOf<String?>(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<String>,
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()
}
}

439
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<Uri?>(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<Uri?>(null) }
var capturedPhotoUri by remember { mutableStateOf<Uri?>(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 = { _, _ -> }
)
}
}

417
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<Uri?>(null) }
var tempCameraUri by remember { mutableStateOf<Uri?>(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 = {}
)
}
}

44
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<UploadResponse>
// ==========================================
// Upload Video
// ==========================================
@Multipart
@POST("api/v1/users/me/video")
suspend fun uploadProfileVideo(
@Header("Authorization") authorization: String,
@Part video: MultipartBody.Part
): Response<UploadResponse>
// ==========================================
// Final Registration
// ==========================================
@POST("api/v1/auth/register")
suspend fun register(
@Body request: RegisterRequest
): Response<RegisterResponse>
}

46
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
)
}

96
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<String>
)
// ===============================
// 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?
)

54
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)
}
}

187
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<String> {
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<String> {
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<RegisterResponse> {
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)
}
}
}

937
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<String?>(null) }
var passwordInput by remember { mutableStateOf("") }
var confirmPasswordInput by remember { mutableStateOf("") }
var passwordError by remember { mutableStateOf<String?>(null) }
var nameInput by remember { mutableStateOf("") }
var nameError by remember { mutableStateOf<String?>(null) }
var dobInput by remember { mutableStateOf("") }
var dobError by remember { mutableStateOf<String?>(null) }
var genderInput by remember { mutableStateOf("") }
var genderError by remember { mutableStateOf<String?>(null) }
val selectedInterests = remember { mutableStateListOf<String>() }
var interestsError by remember { mutableStateOf<String?>(null) }
var vibeInput by remember { mutableStateOf("") }
var vibeError by remember { mutableStateOf<String?>(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<String>,
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()
}
}

1255
app/src/main/java/com/example/dattingapp/registration/UploadIntroVideoActivity.kt

File diff suppressed because it is too large

417
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<Uri?>(null) }
var tempCameraUri by remember { mutableStateOf<Uri?>(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 = {}
)
}
}

38
app/src/main/java/com/example/dattingapp/ui/theme/Color.kt

@ -1,5 +1,6 @@
package com.example.dattingapp.ui.theme package com.example.dattingapp.ui.theme
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF) val Purple80 = Color(0xFFD0BCFF)
@ -35,3 +36,40 @@ val AccentPurple = Color(0xFF6C11D9)
val BadgePink = Color(0xFFFF2A7A) val BadgePink = Color(0xFFFF2A7A)
val ChipBgPink = Color(0xFFFDE8EF) val ChipBgPink = Color(0xFFFDE8EF)
val ChipTextPink = Color(0xFFD81B60) 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)

BIN
app/src/main/res/drawable/loverobot.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

4
app/src/main/res/values/strings.xml

@ -85,6 +85,10 @@
<string name="title_activity_explore_screen">ExploreScreen</string> <string name="title_activity_explore_screen">ExploreScreen</string>
<string name="title_activity_chat_screen">ChatScreen</string> <string name="title_activity_chat_screen">ChatScreen</string>
<string name="title_activity_friend_profile">FriendProfile</string> <string name="title_activity_friend_profile">FriendProfile</string>
<string name="title_activity_meet">MeetActivity</string>
<string name="title_activity_setting_screen">SettingScreen</string>
<string name="title_activity_upload_image">UploadImageActivity</string>
<string name="title_activity_upload_intro_video">UploadIntroVideoActivity</string>
</resources> </resources>

5
app/src/main/res/xml/file_paths.xml

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="cache_images" path="." />
<external-cache-path name="external_cache_images" path="." />
</paths>

4
gradle/libs.versions.toml

@ -12,6 +12,8 @@ material = "1.10.0"
appcompat = "1.7.1" appcompat = "1.7.1"
constraintlayout = "2.1.4" constraintlayout = "2.1.4"
activityKtx = "1.8.0" activityKtx = "1.8.0"
media3ExoplayerVersion = "1.5.1"
media3UiVersion = "1.5.1"
navigationFragmentKtx = "2.6.0" navigationFragmentKtx = "2.6.0"
navigationUiKtx = "2.6.0" navigationUiKtx = "2.6.0"
material3 = "1.4.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-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-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } 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] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }

Loading…
Cancel
Save