From 16900acfccea3e0021efaf8bb4d4ccf90ac85d1a Mon Sep 17 00:00:00 2001 From: osman160128 Date: Thu, 22 Oct 2026 13:11:13 +0600 Subject: [PATCH] 10/22/2026 --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 18 +- .../example/dattingapp/CreateReelActivity.kt | 441 ++++++++++ .../dattingapp/EditMyProfileActivty.kt | 805 ++++++++++++++++++ .../example/dattingapp/homes/ExploreScreen.kt | 24 +- .../example/dattingapp/homes/HomeActivity.kt | 216 +++-- .../com/example/dattingapp/homes/MyProfile.kt | 2 + .../registration/RegisterRequest.kt | 4 - app/src/main/res/values/strings.xml | 1 + gradle/libs.versions.toml | 2 + 10 files changed, 1418 insertions(+), 96 deletions(-) create mode 100644 app/src/main/java/com/example/dattingapp/CreateReelActivity.kt 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" }