diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 977ce7e..8b98f91 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -42,6 +42,7 @@ dependencies {
implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.compose.foundation)
+ implementation(libs.androidx.compose.foundation.layout)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fa7834b..bebfaaf 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -146,15 +146,27 @@
android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" />
+
+
diff --git a/app/src/main/java/com/example/dattingapp/CreateReelActivity.kt b/app/src/main/java/com/example/dattingapp/CreateReelActivity.kt
new file mode 100644
index 0000000..45d40ee
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/CreateReelActivity.kt
@@ -0,0 +1,441 @@
+package com.example.dattingapp
+
+import android.content.Context
+import android.media.MediaMetadataRetriever
+import android.net.Uri
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.BackHandler
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.result.PickVisualMediaRequest
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.PhotoLibrary
+import androidx.compose.material.icons.filled.Send
+import androidx.compose.material.icons.filled.SwapHoriz
+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.graphics.SolidColor
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.TextStyle
+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.core.content.FileProvider
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.example.dattingapp.homes.VideoPlayerItem
+import com.example.dattingapp.homes.networkandapi.CreatePostRequest
+import com.example.dattingapp.homes.networkandapi.CreatePostUiState
+import com.example.dattingapp.homes.networkandapi.CreatePostViewModel
+import com.example.dattingapp.homes.networkandapi.PostMediaViewModel
+import com.example.dattingapp.ui.theme.DattingAPPTheme
+import kotlinx.coroutines.async
+import java.io.File
+
+private const val MAX_REEL_SECONDS = 60
+private const val MAX_CAPTION = 300
+
+class CreateReelActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContent {
+ DattingAPPTheme {
+ CreateReelScreen(onClose = { finish() })
+ }
+ }
+ }
+}
+
+@Composable
+fun CreateReelScreen(onClose: () -> Unit) {
+ val context = LocalContext.current
+ val createVm: CreatePostViewModel = viewModel()
+ val mediaVm: PostMediaViewModel = viewModel()
+ val uiState by createVm.cretePpostUiState.collectAsState()
+
+ var videoUri by remember { mutableStateOf(null) }
+ var caption by remember { mutableStateOf("") }
+ var cameraUri by remember { mutableStateOf(null) }
+ var askDiscard by remember { mutableStateOf(false) }
+
+ val isPosting = uiState is CreatePostUiState.Loading || uiState is CreatePostUiState.Success
+
+ // Accept a video only if it is within the length limit
+ fun accept(uri: Uri) {
+ if (reelDurationSeconds(context, uri) > MAX_REEL_SECONDS) {
+ Toast.makeText(context, "Reels can be up to $MAX_REEL_SECONDS seconds", Toast.LENGTH_LONG).show()
+ } else {
+ videoUri = uri
+ }
+ }
+
+ val pickLauncher = rememberLauncherForActivityResult(
+ ActivityResultContracts.PickVisualMedia()
+ ) { uri -> if (uri != null) accept(uri) }
+
+ val recordLauncher = rememberLauncherForActivityResult(
+ ActivityResultContracts.CaptureVideo()
+ ) { ok -> if (ok) cameraUri?.let { accept(it) } }
+
+ fun startRecording() {
+ val file = File.createTempFile("reel_", ".mp4", context.cacheDir)
+ val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
+ cameraUri = uri
+ recordLauncher.launch(uri)
+ }
+
+ // Create the post, then upload the video (same flow as CreateVibePostScreen)
+ LaunchedEffect(uiState) {
+ when (val s = uiState) {
+ is CreatePostUiState.Success -> {
+ try {
+ val uri = videoUri
+ if (uri != null) {
+ val err = mediaVm.uploadMedia(context, s.postData.id, 0, uri)
+ if (err != null) Toast.makeText(context, err, Toast.LENGTH_LONG).show()
+ }
+ onClose()
+ } finally {
+ createVm.reset()
+ }
+ }
+ is CreatePostUiState.Error -> {
+ Toast.makeText(context, s.message, Toast.LENGTH_LONG).show()
+ createVm.reset()
+ }
+ else -> {}
+ }
+ }
+
+ BackHandler(enabled = videoUri != null && !isPosting) { askDiscard = true }
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.Black)
+ ) {
+ val uri = videoUri
+ if (uri == null) {
+ // ---------------- STEP 1: CAPTURE ----------------
+ CaptureStep(
+ onClose = onClose,
+ onRecord = { startRecording() },
+ onUpload = {
+ pickLauncher.launch(
+ PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)
+ )
+ }
+ )
+ } else {
+ // ---------------- STEP 2: PREVIEW + DETAILS ----------------
+ VideoPlayerItem(videoUri = uri) // fills the screen
+
+ // Bottom scrim
+ Box(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .fillMaxWidth()
+ .height(300.dp)
+ .background(
+ Brush.verticalGradient(listOf(Color.Transparent, Color.Black.copy(alpha = 0.85f)))
+ )
+ )
+
+ // Top bar
+ Row(
+ modifier = Modifier
+ .align(Alignment.TopCenter)
+ .fillMaxWidth()
+ .statusBarsPadding()
+ .padding(12.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ RoundIconButton(Icons.Default.Close, "Discard", enabled = !isPosting) { askDiscard = true }
+ Surface(
+ onClick = {
+ if (!isPosting) pickLauncher.launch(
+ PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly)
+ )
+ },
+ shape = RoundedCornerShape(20.dp),
+ color = Color.Black.copy(alpha = 0.45f)
+ ) {
+ Row(
+ modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(Icons.Default.SwapHoriz, null, tint = Color.White, modifier = Modifier.size(16.dp))
+ Spacer(Modifier.width(6.dp))
+ Text("Change video", color = Color.White, fontSize = 12.sp)
+ }
+ }
+ }
+
+ // Caption + Share
+ Column(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .fillMaxWidth()
+ .navigationBarsPadding()
+ .imePadding()
+ .padding(16.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(16.dp))
+ .background(Color.White.copy(alpha = 0.14f))
+ .border(1.dp, Color.White.copy(alpha = 0.25f), RoundedCornerShape(16.dp))
+ .padding(14.dp)
+ ) {
+ if (caption.isEmpty()) {
+ Text("Write a caption...", color = Color.White.copy(alpha = 0.6f), fontSize = 15.sp)
+ }
+ BasicTextField(
+ value = caption,
+ onValueChange = { if (it.length <= MAX_CAPTION) caption = it },
+ enabled = !isPosting,
+ textStyle = TextStyle(color = Color.White, fontSize = 15.sp),
+ cursorBrush = SolidColor(Color.White),
+ maxLines = 4,
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ Text(
+ text = "${caption.length}/$MAX_CAPTION",
+ color = Color.White.copy(alpha = 0.6f),
+ fontSize = 11.sp,
+ modifier = Modifier
+ .align(Alignment.End)
+ .padding(top = 4.dp, bottom = 10.dp)
+ )
+
+ Button(
+ enabled = !isPosting,
+ onClick = {
+ createVm.createPost(
+ context,
+ CreatePostRequest(
+ content = caption.trim().ifBlank { " " },
+ post_type = "video", // change to "reel" if your backend has it
+ content_format = "plain",
+ feeling = null,
+ location = null
+ )
+ )
+ },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(50.dp),
+ shape = RoundedCornerShape(25.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color(0xFFE91E63),
+ disabledContainerColor = Color(0xFFE91E63).copy(alpha = 0.5f)
+ )
+ ) {
+ Text(
+ if (isPosting) "Posting..." else "Share Reel",
+ fontWeight = FontWeight.Bold,
+ fontSize = 16.sp,
+ color = Color.White
+ )
+ Spacer(Modifier.width(8.dp))
+ Icon(Icons.Default.Send, null, tint = Color.White, modifier = Modifier.size(18.dp))
+ }
+ }
+
+ // ---------------- STEP 3: POSTING PROGRESS ----------------
+ if (isPosting) {
+ LinearProgressIndicator(
+ modifier = Modifier
+ .align(Alignment.TopCenter)
+ .fillMaxWidth()
+ .statusBarsPadding()
+ .height(3.dp),
+ color = Color(0xFFE91E63),
+ trackColor = Color.White.copy(alpha = 0.2f)
+ )
+ }
+ }
+ }
+
+ if (askDiscard) {
+ AlertDialog(
+ onDismissRequest = { askDiscard = false },
+ title = { Text("Discard reel?") },
+ text = { Text("Your video and caption will be lost.") },
+ confirmButton = {
+ TextButton(onClick = {
+ askDiscard = false
+ videoUri = null
+ caption = ""
+ }) { Text("Discard", color = Color.Red) }
+ },
+ dismissButton = { TextButton(onClick = { askDiscard = false }) { Text("Keep editing") } }
+ )
+ }
+}
+
+@Composable
+private fun CaptureStep(
+ onClose: () -> Unit,
+ onRecord: () -> Unit,
+ onUpload: () -> Unit
+) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ RoundIconButton(
+ icon = Icons.Default.Close,
+ description = "Close",
+ modifier = Modifier
+ .align(Alignment.TopStart)
+ .statusBarsPadding()
+ .padding(12.dp),
+ onClick = onClose
+ )
+
+ Column(
+ modifier = Modifier
+ .align(Alignment.Center)
+ .padding(horizontal = 32.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Box(
+ modifier = Modifier
+ .size(88.dp)
+ .clip(CircleShape)
+ .background(
+ Brush.linearGradient(listOf(Color(0xFFC2185B), Color(0xFF7B1FA2)))
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(Icons.Default.Videocam, null, tint = Color.White, modifier = Modifier.size(40.dp))
+ }
+ Spacer(Modifier.height(20.dp))
+ Text("Create a reel", color = Color.White, fontSize = 24.sp, fontWeight = FontWeight.Bold)
+ Spacer(Modifier.height(6.dp))
+ Text(
+ "Share a short video up to $MAX_REEL_SECONDS seconds",
+ color = Color.White.copy(alpha = 0.7f),
+ fontSize = 14.sp
+ )
+ Spacer(Modifier.height(32.dp))
+
+ Button(
+ onClick = onRecord,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(52.dp),
+ shape = RoundedCornerShape(26.dp),
+ colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE91E63))
+ ) {
+ Icon(Icons.Default.Videocam, null, tint = Color.White)
+ Spacer(Modifier.width(8.dp))
+ Text("Record", fontWeight = FontWeight.Bold, fontSize = 16.sp, color = Color.White)
+ }
+ Spacer(Modifier.height(12.dp))
+ OutlinedButton(
+ onClick = onUpload,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(52.dp),
+ shape = RoundedCornerShape(26.dp),
+ border = androidx.compose.foundation.BorderStroke(1.dp, Color.White.copy(alpha = 0.5f))
+ ) {
+ Icon(Icons.Default.PhotoLibrary, null, tint = Color.White)
+ Spacer(Modifier.width(8.dp))
+ Text("Upload from gallery", fontSize = 16.sp, color = Color.White)
+ }
+ }
+ }
+}
+
+@Composable
+private fun RoundIconButton(
+ icon: androidx.compose.ui.graphics.vector.ImageVector,
+ description: String,
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ onClick: () -> Unit
+) {
+ IconButton(
+ onClick = onClick,
+ enabled = enabled,
+ modifier = modifier
+ .size(40.dp)
+ .clip(CircleShape)
+ .background(Color.Black.copy(alpha = 0.45f))
+ ) {
+ Icon(icon, description, tint = Color.White, modifier = Modifier.size(20.dp))
+ }
+}
+
+private fun reelDurationSeconds(context: Context, uri: Uri): Long {
+ return try {
+ val r = MediaMetadataRetriever()
+ r.setDataSource(context, uri)
+ val ms = r.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() ?: 0L
+ r.release()
+ ms / 1000
+ } catch (e: Exception) {
+ 0L
+ }
+}
+
+
+// 1. Preview for the initial Capture step (No video selected yet)
+@Preview(showBackground = true, backgroundColor = 0xFF000000)
+@Composable
+fun CaptureStepPreview() {
+ DattingAPPTheme {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.Black)
+ ) {
+ CaptureStep(
+ onClose = {},
+ onRecord = {},
+ onUpload = {}
+ )
+ }
+ }
+}
+
+// 2. Preview for the Capture step using a Dark/Light device frame
+@Preview(
+ name = "Phone Preview",
+ showBackground = true,
+ backgroundColor = 0xFF000000
+)
+@Composable
+fun CreateReelScreenCapturePreview() {
+ DattingAPPTheme {
+ // Since the real screen uses ViewModels that require a live backend/context,
+ // you can preview the CaptureStep layout directly.
+ CaptureStep(
+ onClose = {},
+ onRecord = {},
+ onUpload = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/EditMyProfileActivty.kt b/app/src/main/java/com/example/dattingapp/EditMyProfileActivty.kt
index e69de29..abe92d0 100644
--- a/app/src/main/java/com/example/dattingapp/EditMyProfileActivty.kt
+++ b/app/src/main/java/com/example/dattingapp/EditMyProfileActivty.kt
@@ -0,0 +1,805 @@
+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.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import com.example.dattingapp.ui.theme.DattingAPPTheme
+import android.content.Intent
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.KeyboardArrowDown
+import androidx.compose.material.icons.filled.KeyboardArrowUp
+import androidx.compose.material.icons.filled.Videocam
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.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 coil.compose.AsyncImage
+import com.example.dattingapp.R
+import com.example.dattingapp.ui.theme.*
+import android.widget.Toast
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.example.dattingapp.homes.networkandapi.EditProfileUiState
+import com.example.dattingapp.homes.networkandapi.EditProfileViewModel
+import com.example.dattingapp.homes.networkandapi.ProfileUiState
+import com.example.dattingapp.homes.networkandapi.ProfileViewModel
+import com.example.dattingapp.homes.profile.ProfileData
+import com.example.dattingapp.homes.profile.UpdateProfileRequest
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+class EditMyProfileActivty : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContent {
+ DattingAPPTheme {
+ val context = LocalContext.current
+ val profileVm: ProfileViewModel = viewModel()
+ val editVm: EditProfileViewModel = viewModel()
+ val profileState by profileVm.profileUiState.collectAsState()
+ val saveState by editVm.state.collectAsState()
+
+ LaunchedEffect(Unit) { profileVm.profileResponse(context) }
+
+ LaunchedEffect(saveState) {
+ when (val s = saveState) {
+ is EditProfileUiState.Saved -> {
+ Toast.makeText(context, "Profile updated", Toast.LENGTH_SHORT).show()
+ editVm.reset()
+ finish()
+ }
+ is EditProfileUiState.Error -> {
+ Toast.makeText(context, s.message, Toast.LENGTH_LONG).show()
+ editVm.reset()
+ }
+ else -> {}
+ }
+ }
+
+ when (val s = profileState) {
+ is ProfileUiState.Success -> EditProfileScreen(
+ profile = s.profileData.profile,
+ isSaving = saveState is EditProfileUiState.Saving,
+ onBackClick = { finish() },
+ onSaveClick = { request -> editVm.save(context, request) }
+ )
+ is ProfileUiState.Error -> Box(Modifier.fillMaxSize(), Alignment.Center) {
+ Text(s.message, color = Color.Red)
+ }
+ else -> Box(Modifier.fillMaxSize(), Alignment.Center) {
+ CircularProgressIndicator()
+ }
+ }
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun EditProfileScreen(
+ profile: ProfileData? = null,
+ isSaving: Boolean = false,
+ onBackClick: () -> Unit = {},
+ onSaveClick: (UpdateProfileRequest) -> Unit = {}
+) {
+ val context = LocalContext.current
+ val scrollState = rememberScrollState()
+
+ // Prefilled from the server response
+ var fullName by remember(profile) { mutableStateOf(profile?.full_name.orEmpty()) }
+ var birthDate by remember(profile) { mutableStateOf(profile?.birth_date.orEmpty()) }
+ var gender by remember(profile) { mutableStateOf(profile?.gender.orEmpty()) }
+ var city by remember(profile) { mutableStateOf(profile?.city.orEmpty()) }
+ var country by remember(profile) { mutableStateOf(profile?.country.orEmpty()) }
+ var datingIntent by remember(profile) { mutableStateOf(profile?.relationship_goal.orEmpty()) }
+ var bioText by remember(profile) { mutableStateOf(profile?.bio.orEmpty()) }
+ val interests = remember(profile) {
+ mutableStateListOf().apply { addAll(profile?.interests.orEmpty()) }
+ }
+ var newInterest by remember { mutableStateOf("") }
+
+ var isBeliefsExpanded by rememberSaveable { mutableStateOf(true) }
+ var selectedBelief by rememberSaveable { mutableStateOf("Spiritual / Agnostic") }
+
+ val submit: () -> Unit = {
+ val date = birthDate.trim()
+ val dateOk = date.isEmpty() || try {
+ java.time.LocalDate.parse(date); true
+ } catch (e: Exception) { false }
+
+ if (!dateOk) {
+ Toast.makeText(context, "Birthday must look like 1998-08-12", Toast.LENGTH_LONG).show()
+ } else {
+ onSaveClick(
+ UpdateProfileRequest(
+ full_name = fullName.trim().ifBlank { null },
+ gender = gender.trim().ifBlank { null },
+ birth_date = date.ifBlank { null },
+ city = city.trim().ifBlank { null },
+ country = country.trim().ifBlank { null },
+ relationship_goal = datingIntent.ifBlank { null },
+ bio = bioText.trim().ifBlank { null },
+ interests = interests.toList()
+ )
+ )
+ }
+ }
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text("Edit Profile", fontWeight = FontWeight.Bold, fontSize = 18.sp, color = textDark) },
+ navigationIcon = {
+ IconButton(onClick = onBackClick) {
+ Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back", tint = textDark)
+ }
+ },
+ colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.White)
+ )
+ },
+ bottomBar = {
+ Surface(
+ modifier = Modifier.fillMaxWidth(),
+ color = Color.White,
+ shadowElevation = 8.dp
+ ) {
+ Box(modifier = Modifier.padding(16.dp)) {
+ Button(
+ onClick = submit,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(50.dp),
+ shape = RoundedCornerShape(25.dp),
+ colors = ButtonDefaults.buttonColors(containerColor = BrandMagenta)
+ ) {
+ Icon(imageVector = Icons.Default.Check, contentDescription = null, tint = Color.White)
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(text = "Save Changes", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color.White)
+ }
+ }
+ }
+ },
+ containerColor = Color.White
+ ) { paddingValues ->
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(paddingValues)
+ .verticalScroll(scrollState)
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ // ====================================================
+ // 1. PROFILE COMPLETION CARD
+ // ====================================================
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(16.dp),
+ colors = CardDefaults.cardColors(containerColor = Color(0xFFFFF9FA)),
+ border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp)// color = BrandMagenta.copy(alpha = 0.3f))
+ ) {
+ Column(modifier = Modifier.padding(16.dp)) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically,){//} horizontalAlignment = Arrangement.spacedBy(6.dp)) {
+ Box(
+ modifier = Modifier
+ .size(10.dp)
+ .clip(CircleShape)
+ .background(BrandMagenta)
+ )
+ Text(text = "Profile 92% Complete", fontWeight = FontWeight.Bold, fontSize = 14.sp, color = textDark)
+ }
+ Surface(
+ shape = RoundedCornerShape(12.dp),
+ color = BrandMagenta.copy(alpha = 0.1f)
+ ) {
+ Text(
+ text = "+5% to 100%",
+ fontSize = 10.sp,
+ fontWeight = FontWeight.Bold,
+ color = BrandMagenta,
+ modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)
+ )
+ }
+ }
+ Spacer(modifier = Modifier.height(6.dp))
+ Text(
+ text = "Add a 30s voice intro to unlock the Super Listener verified badge!",
+ fontSize = 12.sp,
+ color = TextMuted
+ )
+ Spacer(modifier = Modifier.height(10.dp))
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically){// horizontalAlignment = Arrangement.spacedBy(4.dp)) {
+ Text(text = "🎙️", fontSize = 12.sp)
+ Text(text = "Voice prompt pending", fontSize = 12.sp, fontWeight = FontWeight.Medium, color = textDark)
+ }
+ TextButton(onClick = {}) {
+ Text(text = "Record now ›", fontSize = 12.sp, fontWeight = FontWeight.Bold, color = BrandMagenta)
+ }
+ }
+ }
+ }
+
+ // ====================================================
+ // 2. PROFILE PHOTO SECTION
+ // ====================================================
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(text = "Profile Photos", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark)
+ Text(text = "4 max", fontSize = 12.sp, color = BrandMagenta, fontWeight = FontWeight.Bold)
+ }
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(text = "Verified is matches-up your main avatar", fontSize = 11.sp, color = TextMuted)
+ Spacer(modifier = Modifier.height(10.dp))
+
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
+ Box(
+ modifier = Modifier
+ .size(90.dp, 120.dp)
+ .clip(RoundedCornerShape(12.dp))
+ ) {
+ AsyncImage(
+ model = "https://picsum.photos/300/400",
+ contentDescription = "Main Photo",
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.fillMaxSize()
+ )
+ Box(
+ modifier = Modifier
+ .size(20.dp)
+ .clip(CircleShape)
+ .background(BrandMagenta)
+ .align(Alignment.BottomEnd)
+ .padding(4.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(imageVector = Icons.Default.Check, contentDescription = null, tint = Color.White, modifier = Modifier.size(12.dp))
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .height(120.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ OutlinedButton(
+ onClick = {},
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f),
+ shape = RoundedCornerShape(12.dp),
+ colors = ButtonDefaults.outlinedButtonColors(contentColor = BrandMagenta)
+ ) {
+ Icon(imageVector = Icons.Default.Add, contentDescription = null, modifier = Modifier.size(16.dp))
+ Spacer(modifier = Modifier.width(6.dp))
+ Text(text = "Upload New Photo", fontSize = 12.sp, fontWeight = FontWeight.Bold)
+ }
+ OutlinedButton(
+ onClick = {},
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f),
+ shape = RoundedCornerShape(12.dp),
+ colors = ButtonDefaults.outlinedButtonColors(contentColor = textDark)
+ ) {
+ Text(text = "AI Camera", fontSize = 12.sp, fontWeight = FontWeight.Bold)
+ }
+ }
+ }
+ Spacer(modifier = Modifier.height(6.dp))
+ Text(text = "⚠️ Cropping only protocol to keep rules & connections.", fontSize = 10.sp, color = Color(0xFFD32F2F))
+ }
+
+ // ====================================================
+ // 3. VIDEO INTRO CARD
+ // ====================================================
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(16.dp),
+ colors = CardDefaults.cardColors(containerColor = Color(0xFFF9F7FB)),
+ border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(12.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
+ Box(
+ modifier = Modifier
+ .size(50.dp, 60.dp)
+ .clip(RoundedCornerShape(8.dp))
+ .background(Color.Black)
+ ) {
+ AsyncImage(
+ model = "https://picsum.photos/200/300",
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.fillMaxSize()
+ )
+ }
+ Column {
+ Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+ Text(text = "Video Intro", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = textDark)
+ Text(text = "💖", fontSize = 11.sp)
+ }
+ Text(text = "0:45sec • 456 total views", fontSize = 11.sp, color = TextMuted)
+ }
+ }
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ Surface(
+ shape = RoundedCornerShape(12.dp),
+ color = Color(0xFFEFE8F7)
+ ) {
+ Text(text = "Replace", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = BrandMagenta, modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp))
+ }
+ IconButton(onClick = {}, modifier = Modifier.size(28.dp)) {
+ Icon(imageVector = Icons.Default.Close, contentDescription = "Delete", tint = TextMuted, modifier = Modifier.size(16.dp))
+ }
+ }
+ }
+ }
+
+ // ====================================================
+ // 4. BASIC INFORMATION FIELDS
+ // ====================================================
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ OutlinedTextField(
+ value = fullName,
+ onValueChange = { if (it.length <= 30) fullName = it },
+ label = { Text("Display Name") },
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(12.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ focusedBorderColor = BrandMagenta, unfocusedBorderColor = Color(0xFFE0E0E0)
+ ),
+ trailingIcon = { Text("${fullName.length} / 30", fontSize = 10.sp, color = TextMuted) }
+ )
+ OutlinedTextField(
+ value = birthDate,
+ onValueChange = { birthDate = it },
+ label = { Text("Birthday (YYYY-MM-DD)") },
+ singleLine = true,
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(12.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ focusedBorderColor = BrandMagenta, unfocusedBorderColor = Color(0xFFE0E0E0)
+ )
+ )
+ OutlinedTextField(
+ value = gender,
+ onValueChange = { gender = it },
+ label = { Text("Gender") },
+ singleLine = true,
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(12.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ focusedBorderColor = BrandMagenta, unfocusedBorderColor = Color(0xFFE0E0E0)
+ )
+ )
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ OutlinedTextField(
+ value = city, onValueChange = { city = it },
+ label = { Text("City") }, singleLine = true,
+ modifier = Modifier.weight(1f), shape = RoundedCornerShape(12.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ focusedBorderColor = BrandMagenta, unfocusedBorderColor = Color(0xFFE0E0E0)
+ )
+ )
+ OutlinedTextField(
+ value = country, onValueChange = { country = it },
+ label = { Text("Country") }, singleLine = true,
+ modifier = Modifier.weight(1f), shape = RoundedCornerShape(12.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ focusedBorderColor = BrandMagenta, unfocusedBorderColor = Color(0xFFE0E0E0)
+ )
+ )
+ }
+ }
+
+ // ====================================================
+ // 5. DATING INTENT
+ // ====================================================
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(text = "Dating Intent", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark)
+ Text(text = "Visible on profile", fontSize = 11.sp, color = TextMuted)
+ }
+
+ DatingIntentOption(
+ title = "Long-term relationship",
+ subtitle = "Looking for a true connection or serious dating",
+ isSelected = true,
+ onClick = { datingIntent = "Long-term relationship" }
+ )
+ DatingIntentOption(
+ title = "Casual dates",
+ subtitle = "Fun dinners, socializing, and great talks",
+ isSelected = false,
+ onClick = { datingIntent = "Casual dates" }
+ )
+ DatingIntentOption(
+ title = "New Friends & Vibes",
+ subtitle = "Expanding my creative circle in the city",
+ isSelected = false,
+ onClick = { datingIntent = "New Friends & Vibes" }
+ )
+ DatingIntentOption(
+ title = "Marriage / Life Partner",
+ subtitle = "Looking for true degree and building a life together",
+ isSelected = false,
+ onClick = { datingIntent = "Marriage / Life Partner" }
+ )
+ }
+
+ // ====================================================
+ // 6. ABOUT BIO SECTION
+ // ====================================================
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(text = "About Alex (Bio)", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark)
+ Text(text = "124 / 500", fontSize = 10.sp, color = TextMuted)
+ }
+ Spacer(modifier = Modifier.height(6.dp))
+ OutlinedTextField(
+ value = bioText,
+ onValueChange = { bioText = it },
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(110.dp),
+ shape = RoundedCornerShape(12.dp),
+ colors = OutlinedTextFieldDefaults.colors(
+ focusedBorderColor = BrandMagenta,
+ unfocusedBorderColor = Color(0xFFE0E0E0)
+ )
+ )
+ Spacer(modifier = Modifier.height(6.dp))
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+ Text(text = "💡", fontSize = 12.sp)
+ Text(text = "Tip: Add a question to get more replies!", fontSize = 11.sp, color = TextMuted)
+ }
+ TextButton(onClick = {}) {
+ Text(text = "Suggest AI • Polish", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = BrandMagenta)
+ }
+ }
+ }
+
+ // ====================================================
+ // 7. PASSIONS & INTERESTS
+ // ====================================================
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(text = "Passions & Interests", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark)
+ Text(text = "8 selected", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = BrandMagenta)
+ }
+ Spacer(modifier = Modifier.height(2.dp))
+ Text(text = "Tap to remove or add more tailored tags", fontSize = 11.sp, color = TextMuted)
+ Spacer(modifier = Modifier.height(8.dp))
+
+ val interestList = listOf(
+ "📷 Photography", "🐶 Dogs", "☕ Coffee Culture", "✈️ Travel",
+ "🎨 Art & Design", "🎵 Live Concerts", "🍷 Natural Wine", "🍳 Cooking"
+ )
+
+ OptFlowRowEdit(interests = interestList)
+
+ Spacer(modifier = Modifier.height(8.dp))
+ OutlinedButton(
+ onClick = {},
+ shape = RoundedCornerShape(16.dp),
+ colors = ButtonDefaults.outlinedButtonColors(contentColor = BrandMagenta),
+ modifier = Modifier.height(36.dp)
+ ) {
+ Icon(imageVector = Icons.Default.Add, contentDescription = null, modifier = Modifier.size(14.dp))
+ Spacer(modifier = Modifier.width(4.dp))
+ Text(text = "Add More Interests", fontSize = 11.sp, fontWeight = FontWeight.Bold)
+ }
+ }
+
+ // ====================================================
+ // 8. KEY STATS & LIFESTYLE
+ // ====================================================
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ Text(text = "Key Stats & Lifestyle", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark)
+
+ StatEditRow(icon = "💼", title = "Work / Occupation", value = "Creative Director at Studio Drift")
+ StatEditRow(icon = "🎓", title = "Education", value = "NYU Gallatin School")
+ StatEditRow(icon = "📏", title = "Height", value = "5'9\" (175 cm)")
+
+ // Beliefs Expandable Section
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(12.dp),
+ colors = CardDefaults.cardColors(containerColor = Color(0xFFFAFAFA)),
+ border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp)
+ ) {
+ Column(modifier = Modifier.padding(12.dp)) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { isBeliefsExpanded = !isBeliefsExpanded },
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
+ Text(text = "🏛️", fontSize = 16.sp)
+ Column {
+ Text(text = "Beliefs / Values", fontSize = 10.sp, color = TextMuted)
+ Text(text = selectedBelief, fontSize = 12.sp, fontWeight = FontWeight.Bold, color = textDark)
+ }
+ }
+ Icon(
+ imageVector = if (isBeliefsExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown,
+ contentDescription = "Toggle",
+ tint = TextMuted
+ )
+ }
+
+ if (isBeliefsExpanded) {
+ Spacer(modifier = Modifier.height(10.dp))
+ Divider(color = Color(0xFFEEEEEE), thickness = 0.5.dp)
+ Spacer(modifier = Modifier.height(8.dp))
+ Text(text = "SELECT BELIEFS", fontSize = 9.sp, fontWeight = FontWeight.Bold, color = TextMuted)
+ Spacer(modifier = Modifier.height(6.dp))
+
+ BeliefOptionItem(title = "Muslim", desc =" Islam", isSelected = selectedBelief == "Muslim", onClick = { selectedBelief = "Muslim" })
+ BeliefOptionItem(title = "Christian", desc = "Christian", isSelected = selectedBelief == "Christian", onClick = { selectedBelief = "Christian" })
+ BeliefOptionItem(title = "Hindu", desc = "Hinduism", isSelected = selectedBelief == "Hindu", onClick = { selectedBelief = "Hindu" })
+ BeliefOptionItem(title = "Buddhist", desc = "Buddhism", isSelected = selectedBelief == "Buddhist", onClick = { selectedBelief = "Buddhist" })
+ BeliefOptionItem(title = "Spiritual / Agnostic", desc = "Open to universal healing & values", isSelected = selectedBelief == "Spiritual / Agnostic", onClick = { selectedBelief = "Spiritual / Agnostic" })
+ BeliefOptionItem(title = "Other", desc = "Specify or leave open", isSelected = selectedBelief == "Other", onClick = { selectedBelief = "Other" })
+ }
+ }
+ }
+
+ // Lifestyle Grid Edit Tiles
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
+ LifestyleEditTile(icon = "🚭", label = "Smoking", detail = "Non-smoker", modifier = Modifier.weight(1f))
+ LifestyleEditTile(icon = "🍷", label = "Drinking", detail = "Socially", modifier = Modifier.weight(1f))
+ }
+ Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) {
+ LifestyleEditTile(icon = "🐾", label = "Pets", detail = "Golden Retriever", modifier = Modifier.weight(1f))
+ LifestyleEditTile(icon = "🏃", label = "Exercise", detail = "Active 4x a week", modifier = Modifier.weight(1f))
+ }
+
+ Spacer(modifier = Modifier.height(4.dp))
+ OutlinedButton(
+ onClick = {},
+ shape = RoundedCornerShape(16.dp),
+ colors = ButtonDefaults.outlinedButtonColors(contentColor = BrandMagenta),
+ modifier = Modifier.height(36.dp)
+ ) {
+ Icon(imageVector = Icons.Default.Add, contentDescription = null, modifier = Modifier.size(14.dp))
+ Spacer(modifier = Modifier.width(4.dp))
+ Text(text = "Add More Lifestyle Details (Hobby, Diet, Sleep)", fontSize = 11.sp, fontWeight = FontWeight.Bold)
+ }
+ }
+
+ Spacer(modifier = Modifier.height(40.dp))
+ }
+ }
+}
+
+// ====================================================
+// SUB-COMPONENTS FOR EDIT PROFILE SCREEN
+// ====================================================
+
+@Composable
+private fun DatingIntentOption(title: String, subtitle: String, isSelected: Boolean, onClick: () -> Unit) {
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { onClick() },
+ shape = RoundedCornerShape(12.dp),
+ colors = CardDefaults.cardColors(containerColor = if (isSelected) Color(0xFFFFF9FA) else Color(0xFFFAFAFA)),
+ border = CardDefaults.outlinedCardBorder().copy(width = if (isSelected) 1.dp else 0.5.dp)// color = if (isSelected) BrandMagenta else Color(0xFFE0E0E0))
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(12.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
+ Box(
+ modifier = Modifier
+ .size(30.dp)
+ .clip(CircleShape)
+ .background(if (isSelected) BrandMagenta.copy(alpha = 0.15f) else Color(0xFFEFEFEF)),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.Favorite,
+ contentDescription = null,
+ tint = if (isSelected) BrandMagenta else TextMuted,
+ modifier = Modifier.size(14.dp)
+ )
+ }
+ Column {
+ Text(text = title, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = textDark)
+ Text(text = subtitle, fontSize = 11.sp, color = TextMuted)
+ }
+ }
+ RadioButton(selected = isSelected, onClick = onClick, colors = RadioButtonDefaults.colors(selectedColor = BrandMagenta))
+ }
+ }
+}
+
+@Composable
+private fun StatEditRow(icon: String, title: String, value: String) {
+ Card(
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(12.dp),
+ colors = CardDefaults.cardColors(containerColor = Color(0xFFFAFAFA)),
+ border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(12.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
+ Text(text = icon, fontSize = 16.sp)
+ Column {
+ Text(text = title, fontSize = 10.sp, color = TextMuted)
+ Text(text = value, fontSize = 12.sp, fontWeight = FontWeight.Bold, color = textDark)
+ }
+ }
+ Text(text = "›", fontSize = 16.sp, color = TextMuted)
+ }
+ }
+}
+
+@Composable
+private fun BeliefOptionItem(title: String, desc: String, isSelected: Boolean, onClick: () -> Unit) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { onClick() }
+ .padding(vertical = 6.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ RadioButton(selected = isSelected, onClick = onClick, colors = RadioButtonDefaults.colors(selectedColor = BrandMagenta))
+ Column {
+ Text(text = title, fontSize = 12.sp, fontWeight = FontWeight.Bold, color = textDark)
+ Text(text = desc, fontSize = 10.sp, color = TextMuted)
+ }
+ }
+ }
+}
+
+@Composable
+private fun LifestyleEditTile(icon: String, label: String, detail: String, modifier: Modifier = Modifier) {
+ Surface(
+ modifier = modifier,
+ shape = RoundedCornerShape(12.dp),
+ color = Color(0xFFFAFAFA),
+ border = BorderStroke(0.5.dp, Color(0xFFE0E0E0))
+ ) {
+ Row(
+ modifier = Modifier.padding(10.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
+ Text(text = icon, fontSize = 16.sp)
+ Column {
+ Text(text = label, fontSize = 9.sp, color = TextMuted)
+ Text(text = detail, fontSize = 11.sp, fontWeight = FontWeight.Bold, color = textDark)
+ }
+ }
+ Text(text = "✎", fontSize = 11.sp, color = TextMuted)
+ }
+ }
+}
+
+@Composable
+private fun OptFlowRowEdit(interests: List) {
+ Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
+ val chunked = interests.chunked(3)
+ chunked.forEach { rowItems ->
+ Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
+ rowItems.forEach { item ->
+ Surface(
+ shape = RoundedCornerShape(16.dp),
+ color = Color(0xFFF4F4F4),
+ border = BorderStroke(0.5.dp, Color(0xFFDDDDDD))
+ ) {
+ Row(
+ modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ Text(text = item, fontSize = 11.sp, fontWeight = FontWeight.Medium, color = textDark)
+ Icon(imageVector = Icons.Default.Close, contentDescription = "Remove", tint = TextMuted, modifier = Modifier.size(12.dp))
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true, showSystemUi = true)
+@Composable
+fun EditProfileScreenPreview() {
+ DattingAPPTheme {
+ EditProfileScreen()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt b/app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt
index ad6055f..2c1846a 100644
--- a/app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt
+++ b/app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt
@@ -176,9 +176,7 @@ private fun VibeCheckTopAppBar(
onSearchClick: () -> Unit
) {
val context = LocalContext.current
- val tokenManager = remember { TokenManager.getInstance(context) }
- val currentUserName = remember { tokenManager.currentUserName() }
- val currentProfileImage = remember { tokenManager.currentProfileImage() }
+
Row(
modifier = Modifier
.fillMaxWidth()
@@ -195,21 +193,17 @@ private fun VibeCheckTopAppBar(
Row(verticalAlignment = Alignment.CenterVertically) {
- AsyncImage(
- model = currentProfileImage,
- contentDescription = "Post author image",
- placeholder = painterResource(id = R.drawable.locationpermissionbg),
- error = painterResource(id = R.drawable.locationpermissionbg),
- modifier = Modifier
- .size(40.dp)
- .clip(CircleShape),
- contentScale = ContentScale.Crop
- )
- Spacer(modifier = Modifier.width(2.dp))
+ Text(
+ text = "Feed",
+ color = primaryColor,
+ fontSize = 20.sp,
+ fontWeight = FontWeight.Bold
+ )
+ Spacer(modifier = Modifier.width(10.dp))
Text(
- text = currentUserName ?: "",
+ text = "Reel",
color = primaryColor,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
diff --git a/app/src/main/java/com/example/dattingapp/homes/HomeActivity.kt b/app/src/main/java/com/example/dattingapp/homes/HomeActivity.kt
index c0796fc..90a2b7a 100644
--- a/app/src/main/java/com/example/dattingapp/homes/HomeActivity.kt
+++ b/app/src/main/java/com/example/dattingapp/homes/HomeActivity.kt
@@ -2,21 +2,27 @@ package com.example.dattingapp.homes
import ChatScreen
import android.os.Bundle
-import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.padding
+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.filled.Add
import androidx.compose.material.icons.filled.ChatBubbleOutline
+import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Explore
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.SwapHoriz
+import androidx.compose.material.icons.filled.Videocam
import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
+import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
@@ -31,19 +37,27 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+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.platform.LocalContext
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
+import com.example.dattingapp.CreateReelScreen
import com.example.dattingapp.homes.networkandapi.ExploreFeedViewModel
import com.example.dattingapp.homes.networkandapi.FeedUiState
import com.example.dattingapp.homes.networkandapi.MeetUiState
import com.example.dattingapp.homes.networkandapi.MeetViewModel
-import com.example.dattingapp.homes.networkandapi.ProfileViewModel
import com.example.dattingapp.homes.networkandapi.ProfileUiState
+import com.example.dattingapp.homes.networkandapi.ProfileViewModel
import com.example.dattingapp.permission.PermissionHelper
-
import com.example.dattingapp.ui.theme.DattingAPPTheme
import kotlinx.coroutines.delay
+
class HomeActivity : ComponentActivity() {
private lateinit var permissionHelper: PermissionHelper
@@ -77,7 +91,7 @@ class HomeActivity : ComponentActivity() {
// 10-second delay trigger inside Compose lifecycle
LaunchedEffect(Unit) {
- delay(10_000) // Wait 10 seconds
+ delay(10_000)
checkAndRequestPermission(PermissionHelper.PermissionType.OVERLAY)
}
@@ -132,9 +146,6 @@ class HomeActivity : ComponentActivity() {
}
}
-
-
-
@Composable
fun PermissionRationaleDialog(
title: String,
@@ -164,16 +175,25 @@ fun HomeScreen() {
val appContext = LocalContext.current.applicationContext
-
var selectedItem by remember { mutableIntStateOf(0) }
+ var showCreateSheet by remember { mutableStateOf(false) }
+ var showReel by remember { mutableStateOf(false) }
+
+ // Reel screen opens directly, full-screen, in place of the tabs and bottom bar
+ if (showReel) {
+ CreateReelScreen(
+ onClose = {
+ showReel = false
+ selectedItem = 1 // back to Explore after sharing or closing
+ }
+ )
+ return
+ }
Scaffold(
bottomBar = {
NavigationBar {
- // =========================================
// MEET
- // =========================================
-
NavigationBarItem(
selected = selectedItem == 0,
onClick = { selectedItem = 0 },
@@ -183,13 +203,10 @@ fun HomeScreen() {
contentDescription = "Meet"
)
},
- label = {
- Text("Meet")
- }
+ label = { Text("Meet") }
)
- // =========================================
+
// EXPLORE
- // =========================================
NavigationBarItem(
selected = selectedItem == 1,
onClick = { selectedItem = 1 },
@@ -199,30 +216,23 @@ fun HomeScreen() {
contentDescription = "Explore"
)
},
-
label = { Text("Explore") }
)
-
- // =========================================
- // POST
- // =========================================
-
+ // POST: opens the Create Post / Create Reel sheet
NavigationBarItem(
selected = selectedItem == 2,
- onClick = { selectedItem = 2 },
- icon = { Icon(
+ onClick = { showCreateSheet = true },
+ icon = {
+ Icon(
imageVector = Icons.Default.Add,
contentDescription = "Post"
)
},
- label = {
- Text("Post")
- }
+ label = { Text("Post") }
)
- // =========================================
+
// CHAT
- // =========================================
NavigationBarItem(
selected = selectedItem == 3,
onClick = { selectedItem = 3 },
@@ -234,9 +244,8 @@ fun HomeScreen() {
},
label = { Text("Chat") }
)
- // =========================================
+
// PROFILE
- // =========================================
NavigationBarItem(
selected = selectedItem == 4,
onClick = { selectedItem = 4 },
@@ -250,35 +259,21 @@ fun HomeScreen() {
)
}
}
-
) { innerPadding ->
- // =========================================
- // CONTENT AREA
- // =========================================
val context = LocalContext.current
- // or via hiltViewModel() / factory
-
Box(modifier = Modifier.padding(innerPadding)) {
when (selectedItem) {
- // =========================================
// MEET
- // =========================================
0 -> {
- // 1. Obtain instance of ViewModel and LocalContext
-
val viewModel: MeetViewModel = viewModel()
-
- // 2. Collect state from ViewModel
val uiState by viewModel.meetUiState.collectAsState()
- // 3. Trigger network call once when screen loads
LaunchedEffect(Unit) {
viewModel.meetUserResponse(context)
}
- // 4. Map UI state to Composable parameters
when (val state = uiState) {
is MeetUiState.Loading, MeetUiState.Idle -> {
MeetDiscoveryScreen(
@@ -303,38 +298,29 @@ fun HomeScreen() {
}
}
}
- // =========================================
+
// EXPLORE
- // =========================================
1 -> {
- // 1. Obtain instance of ViewModel and LocalContext
val feedViewModel: ExploreFeedViewModel = viewModel()
- val context = LocalContext.current
-
- // 2. Collect state from ViewModel
val uiState by feedViewModel.uiState.collectAsState()
- // 3. Trigger network call once when screen loads
LaunchedEffect(Unit) {
feedViewModel.loadFeed(context)
}
- // 4. Map UI state to Composable parameters
-
when (val state = uiState) {
is FeedUiState.Loading, FeedUiState.Idle -> {
VibeCheckHomeScreen(
feedResponse = emptyList(),
isLoading = true,
- errorMessage = null,
-// onRetry = { viewModel.meetUserResponse(context) }
+ errorMessage = null
)
}
is FeedUiState.Success -> {
VibeCheckHomeScreen(
- feedResponse = state.posts, // or state.response depending on your data class naming
+ feedResponse = state.posts,
isLoading = false,
- errorMessage = null,
+ errorMessage = null
)
}
is FeedUiState.Error -> {
@@ -342,39 +328,32 @@ fun HomeScreen() {
feedResponse = emptyList(),
isLoading = false,
errorMessage = state.message,
- onRetry = { feedViewModel.loadFeed(context) }
+ onRetry = { feedViewModel.loadFeed(context) }
)
}
}
}
- // =========================================
+
// CREATE POST
- // =========================================
2 -> {
CreateVibePostScreen(
onShareClick = { selectedItem = 1 } // 1 = Explore tab
)
}
- // =========================================
+
// CHAT
- // =========================================
3 -> {
ChatScreen()
}
- // =========================================
+
// PROFILE
- // =========================================
- // =========================================
-// PROFILE
-// =========================================
4 -> {
val profileViewModel: ProfileViewModel = viewModel()
- val context = LocalContext.current
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
val uiState by profileViewModel.profileUiState.collectAsState()
- // Automatically reload profile data when entering this tab OR resuming from another activity
+ // Reload profile data when entering this tab OR resuming from another activity
DisposableEffect(lifecycleOwner) {
val observer = androidx.lifecycle.LifecycleEventObserver { _, event ->
if (event == androidx.lifecycle.Lifecycle.Event.ON_RESUME) {
@@ -392,14 +371,14 @@ fun HomeScreen() {
MyProfileScreen(
profileResponse = null,
isLoading = true,
- errorMessage = null,
+ errorMessage = null
)
}
is ProfileUiState.Success -> {
MyProfileScreen(
profileResponse = state.profileData,
isLoading = false,
- errorMessage = null,
+ errorMessage = null
)
}
is ProfileUiState.Error -> {
@@ -415,4 +394,93 @@ fun HomeScreen() {
}
}
}
+
+ // Create Post / Create Reel chooser
+ if (showCreateSheet) {
+ CreateChoiceSheet(
+ onDismiss = { showCreateSheet = false },
+ onCreatePost = {
+ showCreateSheet = false
+ selectedItem = 2
+ },
+ onCreateReel = {
+ // opens the reel screen directly
+ }
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun CreateChoiceSheet(
+ onDismiss: () -> Unit,
+ onCreatePost: () -> Unit,
+ onCreateReel: () -> Unit
+) {
+ ModalBottomSheet(
+ onDismissRequest = onDismiss,
+ containerColor = Color.White
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 24.dp)
+ .padding(bottom = 32.dp)
+ ) {
+ Text(
+ text = "Create",
+ fontSize = 18.sp,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier.padding(bottom = 12.dp)
+ )
+
+ CreateChoiceRow(
+ icon = Icons.Default.Edit,
+ title = "Create Post",
+ subtitle = "Share text, photos or videos",
+ tint = Color(0xFFE91E63),
+ onClick = onCreatePost
+ )
+ CreateChoiceRow(
+ icon = Icons.Default.Videocam,
+ title = "Create Reel",
+ subtitle = "Record or upload a short video",
+ tint = Color(0xFF7B1FA2),
+ onClick = onCreateReel
+ )
+ }
+ }
+}
+
+@Composable
+private fun CreateChoiceRow(
+ icon: ImageVector,
+ title: String,
+ subtitle: String,
+ tint: Color,
+ onClick: () -> Unit
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(12.dp))
+ .clickable { onClick() }
+ .padding(vertical = 12.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Box(
+ modifier = Modifier
+ .size(44.dp)
+ .clip(CircleShape)
+ .background(tint.copy(alpha = 0.12f)),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(icon, contentDescription = null, tint = tint)
+ }
+ Spacer(Modifier.width(14.dp))
+ Column {
+ Text(title, fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
+ Text(subtitle, fontSize = 12.sp, color = Color.Gray)
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt b/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt
index 10c83d7..888166c 100644
--- a/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt
+++ b/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt
@@ -42,6 +42,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import com.example.dattingapp.EditMyProfileActivty
+
import com.example.dattingapp.LikeANdMatchActivity
import com.example.dattingapp.MatchesActivity
import com.example.dattingapp.R
@@ -61,6 +62,7 @@ import com.example.dattingapp.ui.theme.textDark
import java.time.LocalDate
import java.time.Period
import java.time.format.DateTimeFormatter
+import kotlin.jvm.java
private fun calculateAge(birthDateStr: String?): Int? {
if (birthDateStr.isNullOrBlank()) return null
diff --git a/app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt b/app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt
index b138364..51c8b5c 100644
--- a/app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt
+++ b/app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt
@@ -40,10 +40,6 @@ data class ProfileImageData(
val url: String? // Adjust field name according to your API response JSON structure
)
-data class ImageUploadResponse(
- val message: String,
- val imageUrl: String?
-)
// If the backend expects "email" instead of "emailOrPhone":
data class LoginRequest(
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cd19f07..9bdbf59 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -94,6 +94,7 @@
MatchesActivity
EditProfileActivity
EditProfileActivty
+ CreateReel
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 4238356..c6212b7 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -19,6 +19,7 @@ navigationUiKtx = "2.6.0"
material3 = "1.4.0"
ui = "1.12.0"
foundation = "1.12.1"
+foundationLayout = "1.12.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -46,6 +47,7 @@ media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3UiVersi
media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3ExoplayerVersion" }
androidx-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "ui" }
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" }
+androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayout" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }