Compare commits

...

2 Commits

Author SHA1 Message Date
osman160128 a19e1891a7 create post 4 weeks ago
osman160128 5b6ce5f751 update meet 4 weeks ago
  1. 1043
      app/src/main/java/com/example/dattingapp/CreatePost.kt
  2. 938
      app/src/main/java/com/example/dattingapp/RegistrationScreen.kt
  3. 692
      app/src/main/java/com/example/dattingapp/homes/CreatePostScreen.kt
  4. 1
      app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt
  5. 3
      app/src/main/java/com/example/dattingapp/homes/HomeActivity.kt
  6. 50
      app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt
  7. 58
      app/src/main/java/com/example/dattingapp/homes/MyProfile.kt
  8. 7
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt
  9. 54
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeDataClass.kt
  10. 71
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt
  11. 4
      app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt
  12. 9
      app/src/main/java/com/example/dattingapp/ui/theme/Color.kt

1043
app/src/main/java/com/example/dattingapp/CreatePost.kt

File diff suppressed because it is too large

938
app/src/main/java/com/example/dattingapp/RegistrationScreen.kt

@ -1,938 +0,0 @@
package com.example.dattingapp
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.*
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import java.text.SimpleDateFormat
import java.util.*
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Explore
import androidx.compose.material.icons.outlined.FavoriteBorder
import androidx.compose.material.icons.outlined.Group
import androidx.compose.material.icons.outlined.LocalBar
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import com.example.dattingapp.registration.UploadProfileImageActivity
// Global Email Validation Regex
val EMAIL_PATTERN_REGEX = Regex("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")
data class VibeOption(
val title: String,
val subtitle: String,
val icon: ImageVector,
val iconBgColor: Color,
val iconTintColor: Color
)
class RegistrationScreen : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
FullscreenRegistrationWizard()
}
}
}
}
@Composable
fun FullscreenRegistrationWizard() {
// Active step state
var currentStep by remember { mutableIntStateOf(1) }
val totalSteps = 7
// Form Field States
var isPhoneSelected by remember { mutableStateOf(false) }
var countryCode by remember { mutableStateOf("+880") }
var contactInput by remember { mutableStateOf("") }
var contactError by remember { mutableStateOf<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()
}
}

692
app/src/main/java/com/example/dattingapp/homes/CreatePostScreen.kt

@ -0,0 +1,692 @@
package com.example.dattingapp.homes
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
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.lazy.LazyRow
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
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.graphics.vector.ImageVector
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.withStyle
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.homes.networkandapi.VibeFeeling
import com.example.dattingapp.homes.networkandapi.vibeFeelingsList
import java.io.File
import com.example.dattingapp.ui.theme.LightPinkBg
import com.example.dattingapp.ui.theme.LightPurpleBg
import com.example.dattingapp.ui.theme.DarkOverlay
import com.example.dattingapp.ui.theme.LightBlueBg
import androidx.compose.foundation.lazy.grid.items
// Sealed class representing selectable media types (Photo or Video)
sealed class MediaContentItem(val uri: Uri) {
class Photo(uri: Uri) : MediaContentItem(uri)
class Video(uri: Uri) : MediaContentItem(uri)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CreateVibePostScreen(
onCloseClick: () -> Unit = {},
onShareClick: () -> Unit = {}
) {
val context = LocalContext.current
var textState by remember { mutableStateOf("") }
val maxChar = 500
// Holds selected media items (photos, videos, or both)
val selectedMediaList = remember { mutableStateListOf<MediaContentItem>() }
var tempCameraImageUri by remember { mutableStateOf<Uri?>(null) }
var tempCameraVideoUri by remember { mutableStateOf<Uri?>(null) }
var showImagePickerDialog by remember { mutableStateOf(false) }
var showFeelingSheet by remember { mutableStateOf(false) }
var selectedFeeling by remember { mutableStateOf<VibeFeeling?>(null) }
// Launcher for Multi-Media Gallery Picker (Supports both Photos and Videos simultaneously)
val galleryLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.PickMultipleVisualMedia()
) { uris: List<Uri> ->
uris.forEach { uri ->
val mimeType = context.contentResolver.getType(uri)
if (mimeType?.startsWith("video") == true) {
selectedMediaList.add(MediaContentItem.Video(uri))
} else {
selectedMediaList.add(MediaContentItem.Photo(uri))
}
}
}
// Temporary file helper for Camera captures
fun createTempFileUri(extension: String, prefix: String): Uri {
val tempFile = File.createTempFile(prefix, extension, context.cacheDir).apply {
createNewFile()
deleteOnExit()
}
return FileProvider.getUriForFile(
context,
"${context.packageName}.provider",
tempFile
)
}
// Camera Launchers for taking photos or recording videos
val cameraPhotoLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.TakePicture()
) { success: Boolean ->
if (success && tempCameraImageUri != null) {
selectedMediaList.add(MediaContentItem.Photo(tempCameraImageUri!!))
}
}
val cameraVideoLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CaptureVideo()
) { success: Boolean ->
if (success && tempCameraVideoUri != null) {
selectedMediaList.add(MediaContentItem.Video(tempCameraVideoUri!!))
}
}
Scaffold(
bottomBar = {
Row(
modifier = Modifier
.fillMaxWidth()
.background(Color.White)
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
// 1. Scrollable option chips on the left
LazyRow(
modifier = Modifier.weight(1f),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
item {
VibeOptionChip(
icon = Icons.Outlined.PhotoLibrary,
backgroundColor = LightPinkBg,
contentColor = Color(0xFFE91E63),
onClick = { showImagePickerDialog = true }
)
}
item {
VibeOptionChip(
icon = Icons.Outlined.MyLocation,
backgroundColor = LightBlueBg,
contentColor = Color(0xFF1E88E5)
)
}
item {
VibeOptionChip(
icon = Icons.Outlined.Mood,
backgroundColor = LightPinkBg,
contentColor = Color(0xFFE91E63),
onClick = { showFeelingSheet = true } // Opens bottom sheet
)
}
item {
VibeOptionChip(
icon = Icons.Outlined.MusicNote,
backgroundColor = LightPurpleBg,
contentColor = Color(0xFF7B1FA2)
)
}
}
Spacer(modifier = Modifier.width(8.dp))
// 2. Share button on the right
Button(
onClick = onShareClick,
modifier = Modifier
.wrapContentWidth()
.height(40.dp),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent),
contentPadding = PaddingValues(0.dp)
) {
Box(
modifier = Modifier
.background(
brush = Brush.horizontalGradient(
colors = listOf(Color(0xFFC2185B), Color(0xFF7B1FA2))
),
shape = RoundedCornerShape(12.dp)
)
.padding(horizontal = 12.dp, vertical = 8.dp),
contentAlignment = Alignment.Center
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = "Share",
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 14.sp
)
Spacer(modifier = Modifier.width(6.dp))
Icon(
imageVector = Icons.Default.Send,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(16.dp)
)
}
}
}
}
},
containerColor = Color.White
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding)
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState())
) {
// User Profile Header
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Box(modifier = Modifier.size(48.dp)) {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(Color.Gray)
)
Box(
modifier = Modifier
.size(12.dp)
.clip(CircleShape)
.background(Color(0xFFE91E63))
.align(Alignment.BottomEnd)
.border(2.dp, Color.White, CircleShape)
)
}
Spacer(modifier = Modifier.width(12.dp))
Column(modifier = Modifier.weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = buildAnnotatedString {
// "Alex Rivera " - Bold
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("Alex Rivera ")
}
// "- feeling " - Normal weight (also fixed "felling" to "feeling")
selectedFeeling?.let { feeling ->
withStyle(style = SpanStyle(fontWeight = FontWeight.Normal)) {
append(" — feeling ")
}
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
// Change feeling.name to feeling.label (or feeling.title depending on your data class)
append("${feeling.name} ${feeling.emoji}")
}
}
},
fontSize = 16.sp
)
Spacer(modifier = Modifier.width(4.dp))
//this is for vibe
}
Spacer(modifier = Modifier.height(4.dp))
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Outlined.LocationOn,
contentDescription = null,
tint = Color(0xFFE91E63),
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Blue Bottle Coffee, Soho NYC",
fontWeight = FontWeight.Medium,
fontSize = 13.sp
)
}
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Remove location",
tint = Color.Gray,
modifier = Modifier.size(16.dp)
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// Text Area
TextField(
value = textState,
onValueChange = { if (it.length <= maxChar) textState = it },
placeholder = {
Text(
"What's your vibe today? Share a moment, date idea, or story...",
color = Color.Gray,
fontSize = 15.sp
)
},
modifier = Modifier.fillMaxWidth(),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
)
)
Spacer(modifier = Modifier.height(8.dp))
// Formatting & Character Counter Bar
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Text("B", fontWeight = FontWeight.Bold, fontSize = 16.sp, color = Color.Gray)
Text("I", fontWeight = FontWeight.Bold, fontSize = 16.sp, color = Color.Gray)
}
}
Spacer(modifier = Modifier.height(16.dp))
// Horizontal Scrollable Media Row (Contains Photos and Videos)
if (selectedMediaList.isNotEmpty()) {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth()
) {
itemsIndexed(selectedMediaList) { index, item ->
Card(
shape = RoundedCornerShape(20.dp),
modifier = Modifier
.width(280.dp)
.height(220.dp)
) {
Box(modifier = Modifier.fillMaxSize()) {
when (item) {
is MediaContentItem.Photo -> {
Image(
painter = rememberAsyncImagePainter(item.uri),
contentDescription = "Photo Preview",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
}
is MediaContentItem.Video -> {
VideoPlayerItem(videoUri = item.uri)
}
}
// Delete Card Button
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(10.dp)
) {
OverlayIconButton(
icon = Icons.Default.Close,
onClick = { selectedMediaList.removeAt(index) }
)
}
// Media Type Badge Overlay (Top Left)
Surface(
color = DarkOverlay,
shape = RoundedCornerShape(8.dp),
modifier = Modifier
.align(Alignment.TopStart)
.padding(10.dp)
) {
Row(
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = if (item is MediaContentItem.Video) Icons.Default.Videocam else Icons.Default.Image,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(14.dp)
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = if (item is MediaContentItem.Video) "Video" else "Photo ${index + 1}",
color = Color.White,
fontSize = 11.sp
)
}
}
}
}
}
}
Spacer(modifier = Modifier.height(16.dp))
}
// Location Tag Bar
Spacer(modifier = Modifier.height(20.dp))
Spacer(modifier = Modifier.height(10.dp))
Spacer(modifier = Modifier.height(24.dp))
}
// Camera / Gallery Choice Bottom Sheet Dialog
if (showImagePickerDialog) {
ModalBottomSheet(
onDismissRequest = { showImagePickerDialog = false },
containerColor = Color.White
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp)
) {
Text(
text = "Add Photos or Videos",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 16.dp)
)
// Pick from Gallery (Photo + Video combined option)
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
showImagePickerDialog = false
galleryLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)
)
}
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.PhotoLibrary,
contentDescription = "Gallery",
tint = Color(0xFF7B1FA2),
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Text(text = "Choose Photos & Videos from Gallery", fontSize = 16.sp)
}
// Take Photo
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
showImagePickerDialog = false
val uri = createTempFileUri(".jpg", "camera_photo_")
tempCameraImageUri = uri
cameraPhotoLauncher.launch(uri)
}
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.CameraAlt,
contentDescription = "Take Photo",
tint = Color(0xFFE91E63),
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Text(text = "Take Photo", fontSize = 16.sp)
}
// Record Video
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
showImagePickerDialog = false
val uri = createTempFileUri(".mp4", "camera_video_")
tempCameraVideoUri = uri
cameraVideoLauncher.launch(uri)
}
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Videocam,
contentDescription = "Record Video",
tint = Color(0xFF1E88E5),
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Text(text = "Record Video", fontSize = 16.sp)
}
Spacer(modifier = Modifier.height(16.dp))
}
}
}
if (showFeelingSheet) {
FeelingPickerBottomSheet(
onDismiss = { showFeelingSheet = false },
onFeelingSelected = { feeling ->
selectedFeeling = feeling
}
)
}
}
}
@Composable
fun VideoPlayerItem(videoUri: Uri) {
val context = LocalContext.current
val exoPlayer = remember(videoUri) {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(videoUri))
prepare()
playWhenReady = false
}
}
DisposableEffect(Unit) {
onDispose {
exoPlayer.release()
}
}
AndroidView(
factory = { ctx ->
PlayerView(ctx).apply {
player = exoPlayer
useController = true
}
},
modifier = Modifier.fillMaxSize()
)
}
@Composable
fun OverlayIconButton(icon: ImageVector, onClick: () -> Unit = {}) {
Box(
modifier = Modifier
.size(32.dp)
.background(DarkOverlay, CircleShape)
.clickable { onClick() },
contentAlignment = Alignment.Center
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(16.dp)
)
}
}
@Composable
fun VibeOptionChip(
icon: ImageVector,
backgroundColor: Color,
contentColor: Color,
onClick: () -> Unit = {}
) {
Surface(
shape = RoundedCornerShape(12.dp),
color = backgroundColor,
modifier = Modifier
.height(40.dp)
.clickable { onClick() }
) {
Row(
modifier = Modifier.padding(horizontal = 14.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = contentColor,
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(6.dp))
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FeelingPickerBottomSheet(
onDismiss: () -> Unit,
onFeelingSelected: (VibeFeeling) -> Unit
) {
ModalBottomSheet(
onDismissRequest = onDismiss,
containerColor = Color.White
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Text(
text = "How are you feeling?",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = Color.Black,
modifier = Modifier.padding(bottom = 16.dp)
)
LazyVerticalGrid(
columns = GridCells.Fixed(2),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.padding(bottom = 24.dp)
) {
items(vibeFeelingsList) { feeling ->
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFFF8F9FA))
.clickable {
onFeelingSelected(feeling)
onDismiss()
}
.padding(horizontal = 12.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = feeling.emoji,
fontSize = 20.sp
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = feeling.name,
fontSize = 13.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFF212121),
maxLines = 1
)
}
}
}
}
}
}
@Preview(
name = "Create Vibe Post Screen",
showBackground = true,
showSystemUi = true
)
@Composable
fun CreateVibePostScreenPreview() {
MaterialTheme {
CreateVibePostScreen(
onCloseClick = {},
onShareClick = {}
)
}
}

1
app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt

@ -126,7 +126,6 @@ fun VibeCheckHomeScreen(
badgeColor = Color(0xFF7C4DFF) badgeColor = Color(0xFF7C4DFF)
) )
} }
item { item {
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
} }

3
app/src/main/java/com/example/dattingapp/homes/HomeActivity.kt

@ -30,7 +30,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.dattingapp.ChatScreen import com.example.dattingapp.ChatScreen
import com.example.dattingapp.CreatePost
import com.example.dattingapp.homes.networkandapi.HomeNetworkModule import com.example.dattingapp.homes.networkandapi.HomeNetworkModule
import com.example.dattingapp.homes.networkandapi.MeetUiState import com.example.dattingapp.homes.networkandapi.MeetUiState
import com.example.dattingapp.homes.networkandapi.MeetUserProfile import com.example.dattingapp.homes.networkandapi.MeetUserProfile
@ -214,7 +213,7 @@ fun HomeScreen() {
// CREATE POST // CREATE POST
// ========================================= // =========================================
2 -> { 2 -> {
CreatePost() CreateVibePostScreen()
} }
// ========================================= // =========================================
// CHAT // CHAT

50
app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt

@ -1,6 +1,8 @@
package com.example.dattingapp.homes package com.example.dattingapp.homes
import android.content.Context
import android.util.Log import android.util.Log
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
@ -21,11 +23,15 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.example.dattingapp.homes.networkandapi.MeetUserProfile import com.example.dattingapp.homes.networkandapi.MeetUserProfile
import com.example.dattingapp.homes.networkandapi.SwipeRequest
import com.example.dattingapp.homes.networkandapi.SwipeViewModel
import com.example.dattingapp.homes.networkandapi.TokenManager import com.example.dattingapp.homes.networkandapi.TokenManager
import com.example.dattingapp.ui.theme.BrandPink import com.example.dattingapp.ui.theme.BrandPink
import com.example.dattingapp.ui.theme.BrandPurple import com.example.dattingapp.ui.theme.BrandPurple
@ -43,9 +49,10 @@ fun MeetDiscoveryScreen(
onSearchClick: () -> Unit = {}, onSearchClick: () -> Unit = {},
onSuperLikeClick: (MeetUserProfile) -> Unit = {}, onSuperLikeClick: (MeetUserProfile) -> Unit = {},
onRejectClick: (MeetUserProfile) -> Unit = {}, onRejectClick: (MeetUserProfile) -> Unit = {},
onLikeClick: (MeetUserProfile) -> Unit = {} onLikeClick: (MeetUserProfile) -> Unit = {},
swipeViewModel: SwipeViewModel = viewModel()
) { ) {
// Index tracking current active user profile (starts at 0) val context = LocalContext.current
var currentIndex by remember { mutableIntStateOf(0) } var currentIndex by remember { mutableIntStateOf(0) }
Box( Box(
@ -121,7 +128,6 @@ fun MeetDiscoveryScreen(
} }
profiles.isNotEmpty() && currentIndex < profiles.size -> { profiles.isNotEmpty() && currentIndex < profiles.size -> {
// Current profile starting at index 0
val currentProfile = profiles[currentIndex] val currentProfile = profiles[currentIndex]
ProfileCardItem( ProfileCardItem(
@ -131,10 +137,12 @@ fun MeetDiscoveryScreen(
currentIndex++ currentIndex++
}, },
onReject = { onReject = {
handleSwipeAction(context,direction="pass", currentProfile, swipeViewModel)
onRejectClick(currentProfile) onRejectClick(currentProfile)
currentIndex++ currentIndex++
}, },
onLike = { onLike = {
handleSwipeAction(context,direction="like", currentProfile, swipeViewModel)
onLikeClick(currentProfile) onLikeClick(currentProfile)
currentIndex++ currentIndex++
} }
@ -161,20 +169,22 @@ private fun ProfileCardItem(
onReject: () -> Unit, onReject: () -> Unit,
onLike: () -> Unit onLike: () -> Unit
) { ) {
// Extract primary photo URL from media list val context = LocalContext.current
val scope = rememberCoroutineScope()
// Reset drag offset whenever the profile changes
val offsetX = remember(profile) { Animatable(0f) }
val photoUrl = remember(profile) { val photoUrl = remember(profile) {
profile.media profile.media
?.firstOrNull { it.mediaType.equals("image", ignoreCase = true) || it.url != null } ?.firstOrNull { it.mediaType.equals("image", ignoreCase = true) || it.url != null }
?.fullMediaUrl ?.fullMediaUrl
} }
Log.d("photourl", "$photoUrl")
// Helper to calculate age from birth_date string (formatted e.g. "YYYY-MM-DD")
val age = remember(profile.birthDate) { val age = remember(profile.birthDate) {
calculateAge(profile.birthDate) calculateAge(profile.birthDate)
} }
// Location display formatting
val locationText = remember(profile) { val locationText = remember(profile) {
val loc = listOfNotNull(profile.city, profile.country).joinToString(", ") val loc = listOfNotNull(profile.city, profile.country).joinToString(", ")
val dist = profile.distanceKm?.let { "${it.toInt()} km away" } val dist = profile.distanceKm?.let { "${it.toInt()} km away" }
@ -186,8 +196,6 @@ private fun ProfileCardItem(
.fillMaxSize() .fillMaxSize()
.clip(RoundedCornerShape(20.dp)) .clip(RoundedCornerShape(20.dp))
) { ) {
// BACKEND PROFILE IMAGE
val context = LocalContext.current
val token = remember(context) { val token = remember(context) {
TokenManager.getInstance(context).getAccessToken() TokenManager.getInstance(context).getAccessToken()
} }
@ -197,7 +205,6 @@ private fun ProfileCardItem(
.data(photoUrl) .data(photoUrl)
.apply { .apply {
if (!token.isNullOrBlank()) { if (!token.isNullOrBlank()) {
// Formatting matches "Bearer <token>" or raw token based on backend preference
val authHeader = if (token.startsWith("Bearer ", ignoreCase = true)) token else "Bearer $token" val authHeader = if (token.startsWith("Bearer ", ignoreCase = true)) token else "Bearer $token"
addHeader("Authorization", authHeader) addHeader("Authorization", authHeader)
} }
@ -295,7 +302,6 @@ private fun ProfileCardItem(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
// Name & Age
val displayName = profile.fullName ?: "User" val displayName = profile.fullName ?: "User"
val nameAndAge = if (age != null) "$displayName, $age" else displayName val nameAndAge = if (age != null) "$displayName, $age" else displayName
@ -306,7 +312,6 @@ private fun ProfileCardItem(
color = Color.White color = Color.White
) )
// Compatibility / Match Percentage Badge
profile.compatibilityPercentage?.let { match -> profile.compatibilityPercentage?.let { match ->
Surface( Surface(
shape = RoundedCornerShape(10.dp), shape = RoundedCornerShape(10.dp),
@ -336,7 +341,6 @@ private fun ProfileCardItem(
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
// Location & Distance
if (locationText.isNotBlank()) { if (locationText.isNotBlank()) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@ -356,7 +360,6 @@ private fun ProfileCardItem(
} }
} }
// Bio
profile.bio?.takeIf { it.isNotBlank() }?.let { bio -> profile.bio?.takeIf { it.isNotBlank() }?.let { bio ->
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
@ -370,16 +373,27 @@ private fun ProfileCardItem(
} }
} }
// Utility to calculate age from date string (e.g. "1999-05-15") //Helper to calculate age from birth date string (e.g., "1999-05-15")
private fun calculateAge(birthDateStr: String?): Int? { private fun calculateAge(birthDateStr: String?): Int? {
if (birthDateStr.isNull_or_blank()) return null if (birthDateStr.isNullOrBlank()) return null
return try { return try {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val birthDate = LocalDate.parse(birthDateStr?.take(10), formatter) val birthDate = LocalDate.parse(birthDateStr.take(10), formatter)
Period.between(birthDate, LocalDate.now()).years Period.between(birthDate, LocalDate.now()).years
} catch (e: Exception) { } catch (e: Exception) {
null null
} }
} }
private fun String?.isNull_or_blank(): Boolean = this == null || this.trim().isEmpty() // Helper method for reject swipe network call
private fun handleSwipeAction(
context: Context,
direction:String,
profile: MeetUserProfile,
swipeViewModel: SwipeViewModel
) {
val targetUserId = profile.id
val swipeRequest = SwipeRequest(targetUserId, direction)
swipeViewModel.sendSwipeResponse(context, swipeRequest)
}

58
app/src/main/java/com/example/dattingapp/homes/MyProfile.kt

@ -47,7 +47,6 @@ import com.example.dattingapp.ui.theme.TextMuted
@Composable @Composable
fun MyProfileScreen( fun MyProfileScreen(
profile: MyProfileResponse?, // Change from MyProfileData? to MyProfileResponse? profile: MyProfileResponse?, // Change from MyProfileData? to MyProfileResponse?
isLoading: Boolean, isLoading: Boolean,
errorMessage: String?, errorMessage: String?,
@ -57,39 +56,25 @@ fun MyProfileScreen(
onSettingsClick: () -> Unit = {} onSettingsClick: () -> Unit = {}
) { ) {
// ======================================================== // ========================================================
// FULL SCREEN SCROLL // FULL SCREEN SCROLL
// ======================================================== // ========================================================
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
Column( Column(
modifier = Modifier.fillMaxSize().background(Color.White).verticalScroll(scrollState)) { modifier = Modifier.fillMaxSize().background(Color.White).verticalScroll(scrollState)) {
// ==================================================== // ====================================================
// HEADER // HEADER
// ==================================================== // ====================================================
Box( Box(
modifier = Modifier.fillMaxWidth().height(260.dp) modifier = Modifier.fillMaxWidth().height(260.dp)
) { ) {
// ================================================= // =================================================
// COVER IMAGE // COVER IMAGE
// ================================================= // =================================================
Image( Image(
painter = painterResource( painter = painterResource(id = R.drawable.locationpermissionbg),
id = R.drawable.locationpermissionbg
),
contentDescription = "Cover Banner", contentDescription = "Cover Banner",
modifier = Modifier.fillMaxWidth().height(200.dp), modifier = Modifier.fillMaxWidth().height(200.dp),
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
@ -179,7 +164,7 @@ fun MyProfileScreen(
) { ) {
Text( Text(
text = "Alex, 28", text = profile?.profile?.fullName?:"",
fontSize = 22.sp, fontSize = 22.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = BrandMagenta color = BrandMagenta
@ -222,7 +207,7 @@ fun MyProfileScreen(
Text( Text(
text = "Adventure seeker, dog lover, and amateur chef. Let's find a vibe!", text = profile?.profile?.bio?:"",
fontSize = 13.sp, fontSize = 13.sp,
color = TextMuted, color = TextMuted,
lineHeight = 18.sp lineHeight = 18.sp
@ -230,9 +215,7 @@ fun MyProfileScreen(
} }
Spacer( Spacer(modifier = Modifier.height(12.dp))
modifier = Modifier.height(12.dp)
)
// ==================================================== // ====================================================
// PREMIUM BANNER // PREMIUM BANNER
// ==================================================== // ====================================================
@ -443,3 +426,36 @@ private fun StatCard(
// ============================================================ // ============================================================
// PREVIEW // PREVIEW
// ============================================================ // ============================================================
// 1. Success State Preview (With Mock User Profile Data)
@Preview(showBackground = true, showSystemUi = true)
@Composable
fun MyProfileScreenSuccessPreview() {
DattingAPPTheme {
// Create dummy profile response matching your backend structure
val mockProfileResponse = MyProfileResponse(
id = 1,
emailOrPhone = "alex.dev@example.com",
status = "active",
profile = com.example.dattingapp.homes.networkandapi.UserProfileDetails(
fullName = "Alex",
gender = "Male",
city = "New York",
country = "USA",
bio = "Adventure seeker, dog lover, and amateur chef. Let's find a vibe!",
profileImageUrl = "https://picsum.photos/300/300", // Sample remote URL
videoUrl = null
),
createdAt = "2026-01-01"
)
MyProfileScreen(
profile = mockProfileResponse,
isLoading = false,
errorMessage = null,
onRetry = {},
onEditClick = {},
onUpgradeClick = {},
onSettingsClick = {}
)
}
}

7
app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt

@ -1,7 +1,9 @@
package com.example.dattingapp.homes.networkandapi package com.example.dattingapp.homes.networkandapi
import retrofit2.Response import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query import retrofit2.http.Query
interface HomeApiService { interface HomeApiService {
@ -14,4 +16,9 @@ interface HomeApiService {
@GET("api/v1/users/me") @GET("api/v1/users/me")
suspend fun getCurrentUserProfile(): Response<MyProfileResponse> suspend fun getCurrentUserProfile(): Response<MyProfileResponse>
@POST("api/v1/meet/swipes")
suspend fun recordSwipe(
@Body request: SwipeRequest
): Response<SwipeResponse>
} }

54
app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeDataClass.kt

@ -91,3 +91,57 @@ data class MediaItem(
val fullMediaUrl: String? val fullMediaUrl: String?
get() = resolveMediaUrl(url) get() = resolveMediaUrl(url)
} }
//swife data
// Request payload
data class SwipeRequest(
@SerializedName("target_user_id")
val targetUserId: Int,
@SerializedName("direction")
val direction: String // "like", "dislike", "super_like"
)
data class SwipeResponse(
@SerializedName("direction")
val direction: String?,
@SerializedName("matched")
val matched: Boolean = false,
@SerializedName("match_id")
val matchId: Int? = null
)
//feeling data for create post
data class VibeFeeling(
val name: String,
val emoji: String,
val category: String
)
val vibeFeelingsList = listOf(
// Feelings
VibeFeeling("Happy", "😊", "Feelings"),
VibeFeeling("Blessed", "😇", "Feelings"),
VibeFeeling("Loved", "🥰", "Feelings"),
VibeFeeling("Excited", "🤩", "Feelings"),
VibeFeeling("Crazy", "🤪", "Feelings"),
VibeFeeling("Sad", "😢", "Feelings"),
VibeFeeling("Angry", "😡", "Feelings"),
VibeFeeling("Relaxed", "😌", "Feelings"),
VibeFeeling("Tired", "😴", "Feelings"),
VibeFeeling("Anxious", "😰", "Feelings"),
VibeFeeling("Motivated", "🔥", "Feelings"),
VibeFeeling("Grateful", "🙏", "Feelings"),
// Activities
VibeFeeling("Eating", "🍔", "Activities"),
VibeFeeling("Drinking", "☕", "Activities"),
VibeFeeling("Traveling", "✈️", "Activities"),
VibeFeeling("Listening to Music", "🎧", "Activities"),
VibeFeeling("Watching TV", "📺", "Activities"),
VibeFeeling("Working Out", "🏋️‍♂️", "Activities"),
VibeFeeling("Coding", "💻", "Activities"),
VibeFeeling("Gaming", "🎮", "Activities")
)

71
app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt

@ -93,6 +93,8 @@ class MeetViewModel : ViewModel() {
// Myprofle ViewModel---------------------------------------------------------------------------------------
sealed interface MyProfileUiState { sealed interface MyProfileUiState {
object Idle : MyProfileUiState object Idle : MyProfileUiState
object Loading : MyProfileUiState object Loading : MyProfileUiState
@ -153,3 +155,72 @@ class MyProfileViewModel : ViewModel() {
} }
} }
} }
// Swife ViewModel---------------------------------------------------------------------------------------
sealed interface SwipeUiState {
object Idle : SwipeUiState
object Loading : SwipeUiState
data class Success(val swipedata: SwipeResponse) : SwipeUiState // Ensure this is MyProfileData, not MyProfileResponse
data class Error(val message: String) : SwipeUiState
}
class SwipeViewModel : ViewModel() {
private val _swipeUiState = MutableStateFlow<SwipeUiState>(SwipeUiState.Idle)
val swipeUiState: StateFlow<SwipeUiState> = _swipeUiState.asStateFlow()
fun sendSwipeResponse(context: Context,swipeRequest: SwipeRequest) {
if (_swipeUiState.value is SwipeUiState.Loading) return
val appContext = context.applicationContext
viewModelScope.launch {
_swipeUiState.value = SwipeUiState.Loading
if (TokenManager.getInstance(appContext).getAccessToken().isNullOrBlank()) {
Log.e("MeetApi", "No access token in user_tokens preferences")
_swipeUiState.value = SwipeUiState.Error("No access token found. Please log in again.")
return@launch
}
try {
val response = HomeNetworkModule
.getApiService(appContext)
.recordSwipe(swipeRequest)
val statusCode = response.code()
Log.d("swipe code", "Response Code: $statusCode")
if (!response.isSuccessful) {
response.errorBody()?.close()
val message = when (response.code()) {
401 -> "Authentication failed. Please log in again."
403 -> "The server refused access to these profiles."
404 -> "Meet endpoint was not found."
else -> "Request failed: HTTP ${response.code()}."
}
_swipeUiState.value = SwipeUiState.Error(message)
return@launch
}
// FIX: Use response.body() directly as profileData
val swipeData = response.body()
if ( swipeData!= null) {
_swipeUiState.value = SwipeUiState.Success(swipeData)
} else {
_swipeUiState.value = SwipeUiState.Error("Profile data is empty.")
}
} catch (e: Exception) {
Log.e("MeetApi", "Unexpected request failure", e)
_swipeUiState.value = SwipeUiState.Error("Request failed: ${e.javaClass.simpleName}")
}
}
}
}

4
app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt

@ -9,7 +9,9 @@ import retrofit2.http.Part
interface AuthApiService { interface AuthApiService {
@POST("/api/v1/auth/register") @POST("/api/v1/auth/register")
suspend fun registerUser(@Body request: RegisterRequest): Response<RegisterResponse> suspend fun registerUser(
@Body request: RegisterRequest
): Response<RegisterResponse>
@Multipart @Multipart
@POST("api/v1/users/me/image") @POST("api/v1/users/me/image")

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

@ -87,3 +87,12 @@ val PinkGradientStart = Color(0xFFE91E63)
val PurpleGradientEnd = Color(0xFF8E24AA) val PurpleGradientEnd = Color(0xFF8E24AA)
val LightBorderColor = Color(0xFFFFD1DC) val LightBorderColor = Color(0xFFFFD1DC)
val InputBg = Color(0xFFF9FAFB) val InputBg = Color(0xFFF9FAFB)
// Custom Colors matching the UI design
val PurplePrimary = Color(0xFF9C27B0)
val RedPrimary = Color(0xFFE91E63)
val LightPinkBg = Color(0xFFFDE8E8)
val LightBlueBg = Color(0xFFEEF5FF)
val LightPurpleBg = Color(0xFFF5EEFF)
val DarkOverlay = Color(0xCC212529)

Loading…
Cancel
Save