diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 24f686d..fa7834b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -145,6 +145,18 @@ android:label="@string/title_activity_matches" android:theme="@style/Theme.DattingAPP" android:windowSoftInputMode="adjustResize" /> + + \ 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 new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/java/com/example/dattingapp/FriendProfile.kt b/app/src/main/java/com/example/dattingapp/FriendProfile.kt index 1460bae..2fbec7e 100644 --- a/app/src/main/java/com/example/dattingapp/FriendProfile.kt +++ b/app/src/main/java/com/example/dattingapp/FriendProfile.kt @@ -7,6 +7,11 @@ import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -18,17 +23,15 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.automirrored.filled.TextSnippet +import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.ChatBubble import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Favorite -import androidx.compose.material.icons.filled.FlashOn -import androidx.compose.material.icons.filled.Mood +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.icons.filled.MoreHoriz -import androidx.compose.material.icons.filled.MusicNote +import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Star -import androidx.compose.material.icons.filled.TextSnippet -import androidx.compose.material.icons.outlined.LocationOn import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -48,18 +51,12 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import com.example.dattingapp.homes.networkandapi.* -import com.example.dattingapp.homes.profile.FrienPostMedia -import com.example.dattingapp.homes.profile.FriendAuthor import com.example.dattingapp.homes.profile.FriendPost import com.example.dattingapp.homes.profile.FriendProfileResponse -import com.example.dattingapp.homes.profile.LikedUserResponse import com.example.dattingapp.ui.theme.* import java.time.LocalDate import java.time.Period import java.time.format.DateTimeFormatter -import kotlin.jvm.java -import kotlinx.serialization.json.Json -import kotlinx.serialization.encodeToString class FriendProfile : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -126,9 +123,9 @@ fun FriendProfileScreen( val matchesViewModel: MatchesViewModel = viewModel() // Collect response states - val likesState by likedViewModel.likesUiState.collectAsState() // Users who liked me - val likedState by likedViewModel.likedUiState.collectAsState() // Users I liked - val matchesState by matchesViewModel.state.collectAsState() // Match list from server + val likesState by likedViewModel.likesUiState.collectAsState() + val likedState by likedViewModel.likedUiState.collectAsState() + val matchesState by matchesViewModel.state.collectAsState() // Fetch lists once LaunchedEffect(Unit) { @@ -154,16 +151,12 @@ fun FriendProfileScreen( // Track local tap in current screen session var localLiked by rememberSaveable { mutableStateOf(false) } - // ========================================================================= + // Toggle state for extra profile details (hidden by default, persists across state changes) + var showExtraInfo by rememberSaveable { mutableStateOf(false) } + // 2-STEP MATCH EVALUATION LOGIC - // ========================================================================= - // STEP 1: Check online match list first val isOnlineMatch = friendProfileData.id in matchIds - - // STEP 2: If NOT an online match, check if he liked me + local like action val isLocalMatch = !isOnlineMatch && heLikedMe && localLiked - - // Final match decision val isMatched = isOnlineMatch || isLocalMatch // Derived button flags @@ -175,304 +168,641 @@ fun FriendProfileScreen( calculateAge(friendProfileData.birth_date) } + val scrollState = rememberScrollState() + Column( modifier = Modifier .fillMaxSize() .background(Color.White) - .verticalScroll(rememberScrollState()) + .verticalScroll(scrollState) + .padding(16.dp) ) { - // Header Cover & Profile Avatar - Box( - modifier = Modifier - .fillMaxWidth() - .height(250.dp) + // ==================================================== + // 1. HEADER: Active Now & Action Icons + // ==================================================== + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { - Image( - painter = painterResource(id = R.drawable.gallarypermissionbg), - contentDescription = "cover photo", - modifier = Modifier - .fillMaxWidth() - .height(200.dp) - .clip(RoundedCornerShape(bottomStart = 10.dp, bottomEnd = 20.dp)), - contentScale = ContentScale.Crop - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 25.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + Surface( + shape = RoundedCornerShape(16.dp), + color = Color(0xFFF2EBF9) ) { + Row( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + Box( + modifier = Modifier + .size(8.dp) + .clip(CircleShape) + .background(BrandMagenta) + ) + Text( + text = "Active now", + fontSize = 12.sp, + fontWeight = FontWeight.Bold, + color = BrandMagenta + ) + } + } + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { IconButton( onClick = onBackClick, - modifier = Modifier.size(20.dp).clip(CircleShape) + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .background(Color(0xFFF7F7F7)) ) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "back button" + contentDescription = "Back", + tint = textDark, + modifier = Modifier.size(18.dp) ) } IconButton( onClick = onMoreClick, - modifier = Modifier.size(20.dp).clip(CircleShape) + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .background(Color(0xFFF7F7F7)) ) { Icon( imageVector = Icons.Default.MoreHoriz, - contentDescription = "more options" + contentDescription = "More Options", + tint = textDark, + modifier = Modifier.size(18.dp) ) } } + } - AsyncImage( - model = friendProfileData.profile_image_url, - contentDescription = "Profile Avatar", - placeholder = painterResource(id = R.drawable.locationpermissionbg), - error = painterResource(id = R.drawable.locationpermissionbg), + Spacer(modifier = Modifier.height(16.dp)) + + // ==================================================== + // 2. PROFILE IDENTITY + // ==================================================== + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + Box( modifier = Modifier - .padding(start = 20.dp) - .align(Alignment.BottomStart) - .size(100.dp) + .size(84.dp) .clip(CircleShape) - .border(4.dp, Color.White, CircleShape), - contentScale = ContentScale.Crop - ) + .background(brush = Brush.linearGradient(colors = listOf(BrandMagenta, BrandPurple))) + .padding(3.dp) + ) { + AsyncImage( + model = friendProfileData.profile_image_url ?: "https://picsum.photos/300/300", + contentDescription = "Profile Picture", + contentScale = ContentScale.Crop, + placeholder = painterResource(id = R.drawable.locationpermissionbg), + error = painterResource(id = R.drawable.locationpermissionbg), + modifier = Modifier + .fillMaxSize() + .clip(CircleShape) + ) + } + + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp) + ) { + val displayName = friendProfileData.full_name ?: "Alex Rivera" + Text( + text = displayName, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = textDark + ) + if (age != null) { + Text( + text = "$age", + fontSize = 20.sp, + fontWeight = FontWeight.Normal, + color = textDark + ) + } + Icon( + imageVector = Icons.Default.Check, + contentDescription = "Verified", + tint = BrandMagenta, + modifier = Modifier.size(16.dp) + ) + } + + val city = friendProfileData.city ?: "New York" + Text( + text = "📍 $city, NY • 2 miles away", + fontSize = 12.sp, + color = BrandPurple, + fontWeight = FontWeight.SemiBold + ) + } } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(16.dp)) - // Profile Main Details - Column( + // ==================================================== + // 3. ACTION BUTTON ROW (Message, Like, Super Like, Dislike) + // ==================================================== + Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 20.dp) + .padding(vertical = 4.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // 💬 Message Button + Card( + onClick = onMessageClick, + enabled = isMessageClickable, + modifier = Modifier + .weight(1f) + .height(48.dp), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors( + containerColor = if (isMessageClickable) Color.White else Color(0xFFF0F0F0), + disabledContainerColor = Color(0xFFF0F0F0) + ), + elevation = CardDefaults.cardElevation( + defaultElevation = if (isMessageClickable) 2.dp else 0.dp + ), + border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) + ) { + Row( + modifier = Modifier.fillMaxSize(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Default.ChatBubble, + contentDescription = "Message", + tint = if (isMessageClickable) textDark else Color(0xFFBDBDBD), + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(6.dp)) + Text( + text = "Message", + fontSize = 14.sp, + fontWeight = FontWeight.Bold, + color = if (isMessageClickable) textDark else Color(0xFFBDBDBD) + ) + } + } + + // ❤️ Like & ⭐ Super Like Buttons — Hidden when matched + if (showLikeAndSuperLike) { + Card( + onClick = { + onLikeClick() + handleLikeAndDislikeAction(context, "like", friendProfileData, swipeViewModel) + localLiked = true + matchesViewModel.loadMatches(context) + }, + enabled = !likeDisabled, + modifier = Modifier.size(48.dp), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors( + containerColor = if (heLikedMe) Color(0xFFE53935) else Color.White, + disabledContainerColor = if (heLikedMe) Color(0xFFE53935) else Color(0xFFF0F0F0) + ), + elevation = CardDefaults.cardElevation( + defaultElevation = if (likeDisabled) 0.dp else 2.dp + ), + border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) + ) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Icon( + imageVector = Icons.Default.Favorite, + contentDescription = "Like", + tint = when { + heLikedMe -> Color.White + likeDisabled -> Color(0xFFBDBDBD) + else -> textDark + }, + modifier = Modifier.size(20.dp) + ) + } + } + + Card( + onClick = { + onStarClick() + handleLikeAndDislikeAction(context, "like", friendProfileData, swipeViewModel) + localLiked = true + matchesViewModel.loadMatches(context) + }, + enabled = !likeDisabled, + modifier = Modifier.size(48.dp), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors( + containerColor = Color.White, + disabledContainerColor = Color(0xFFF0F0F0) + ), + elevation = CardDefaults.cardElevation( + defaultElevation = if (likeDisabled) 0.dp else 2.dp + ), + border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) + ) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Icon( + imageVector = Icons.Default.Star, + contentDescription = "Super Like", + tint = if (likeDisabled) Color(0xFFBDBDBD) else textDark, + modifier = Modifier.size(20.dp) + ) + } + } + } + + // ❌ Dislike / Cancel Button + Card( + onClick = { + onCancelClick() + handleLikeAndDislikeAction(context, "pass", friendProfileData, swipeViewModel) + localLiked = false + likedViewModel.likedResponse(context, "Likes") + likedViewModel.likedResponse(context, "Liked") + matchesViewModel.loadMatches(context) + }, + modifier = Modifier.size(48.dp), + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) + ) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Dislike", + tint = textDark, + modifier = Modifier.size(20.dp) + ) + } + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + // ==================================================== + // 4. LOOKING FOR CARD (ALWAYS VISIBLE) + // ==================================================== + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(14.dp), + colors = CardDefaults.cardColors(containerColor = Color(0xFFF7F4F9)), + border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) ) { Row( + modifier = Modifier + .fillMaxWidth() + .padding(14.dp), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) + horizontalArrangement = Arrangement.SpaceBetween ) { - val displayName = friendProfileData.full_name ?: "User" - val nameAndAge = if (age != null) "$displayName, $age" else displayName - Text( - text = nameAndAge, - fontSize = 24.sp, - fontWeight = FontWeight.Bold, - color = textDark - ) - Surface( - shape = RoundedCornerShape(12.dp), - color = BadgePink + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) ) { - Row( - modifier = Modifier.padding(horizontal = 8.dp, vertical = 3.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp) + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(BrandMagenta.copy(alpha = 0.15f)), + contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Default.Favorite, contentDescription = null, - tint = Color.White, - modifier = Modifier.size(10.dp) + tint = BrandMagenta, + modifier = Modifier.size(16.dp) ) + } + Column { Text( - text = "${friendProfileData.county ?: 0}% Match", - color = Color.White, + text = "LOOKING FOR", fontSize = 10.sp, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.Bold, + color = TextMuted + ) + Text( + text = "Long-term relationship / Serious dating", + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + color = textDark ) } } + Text(text = "›", fontSize = 18.sp, color = TextMuted) } + } + + Spacer(modifier = Modifier.height(16.dp)) + // ==================================================== + // 5. VIDEO INTRO (ALWAYS VISIBLE) + // ==================================================== + Column(modifier = Modifier.fillMaxWidth()) { Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp), - modifier = Modifier.padding(vertical = 4.dp) + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically ) { - Icon( - imageVector = Icons.Outlined.LocationOn, - contentDescription = null, - tint = Color.Gray, - modifier = Modifier.size(14.dp) + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null, tint = BrandMagenta, modifier = Modifier.size(18.dp)) + Text(text = "Video Intro", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark) + } + Surface( + shape = RoundedCornerShape(12.dp), + color = Color(0xFFF2EBF9) + ) { + Text( + text = "Say hello! 📹", + fontSize = 11.sp, + color = BrandMagenta, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) + ) + } + } + Spacer(modifier = Modifier.height(8.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + .clip(RoundedCornerShape(16.dp)) + .background(Color.Black) + ) { + AsyncImage( + model = "https://picsum.photos/600/300", + contentDescription = "Video Intro", + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() ) + Box( + modifier = Modifier + .size(48.dp) + .clip(CircleShape) + .background(Color.White.copy(alpha = 0.9f)) + .align(Alignment.Center), + contentAlignment = Alignment.Center + ) { + Icon(imageVector = Icons.Default.PlayArrow, contentDescription = "Play", tint = BrandMagenta, modifier = Modifier.size(28.dp)) + } + Surface( + color = Color.Black.copy(alpha = 0.6f), + shape = RoundedCornerShape(8.dp), + modifier = Modifier + .align(Alignment.BottomStart) + .padding(10.dp) + ) { + Text( + text = "0:45s \"Quick vibe & Sunday routine\"", + color = Color.White, + fontSize = 10.sp, + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) + ) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // ==================================================== + // 6. TOGGLE BUTTON FOR EXTRA PROFILE DETAILS + // ==================================================== + Surface( + modifier = Modifier + .fillMaxWidth() + .clickable { showExtraInfo = !showExtraInfo }, + shape = RoundedCornerShape(12.dp), + color = Color(0xFFF2EBF9) + ) { + Row( + modifier = Modifier.padding(vertical = 12.dp, horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { Text( - text = "${friendProfileData.city ?: "Unknown"}, ${friendProfileData.country ?: ""}", - fontSize = 12.sp, - color = Color.Gray + text = if (showExtraInfo) "Hide Extra Profile Details" else "View All Profile Details", + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + color = BrandMagenta + ) + Icon( + imageVector = if (showExtraInfo) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown, + contentDescription = "Toggle Details", + tint = BrandMagenta ) } + } - Surface( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp), - shape = RoundedCornerShape(12.dp), - color = Color(0xFFF9F9F9), - shadowElevation = 1.dp - ) { + Spacer(modifier = Modifier.height(16.dp)) + + // ==================================================== + // 7. EXTRA PROFILE DETAILS (COLLAPSIBLE / HIDDEN BY DEFAULT) + // ==================================================== + AnimatedVisibility( + visible = showExtraInfo, + enter = expandVertically() + fadeIn(), + exit = shrinkVertically() + fadeOut() + ) { + Column(modifier = Modifier.fillMaxWidth()) { + // About Section + Text( + text = "About ${friendProfileData.full_name?.substringBefore(" ") ?: "Alex"}", + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + color = textDark + ) + Spacer(modifier = Modifier.height(6.dp)) Text( - text = friendProfileData.bio ?: "", + text = friendProfileData.bio ?: "Adventure seeker, dog lover, and amateur chef. Always hunting for the crispiest croissants in Brooklyn.", fontSize = 13.sp, - color = Color(0xFF4A4A4A), - modifier = Modifier.padding(12.dp), - maxLines = 2, + color = Color(0xFF555555), lineHeight = 18.sp ) - } - // ========================================================================= - // ACTION BUTTON ROW WITH EVALUATED MATCH STATES - // ========================================================================= - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp), - verticalAlignment = Alignment.CenterVertically - ) { - // 💬 Message Button — Enabled ONLY when matched + Spacer(modifier = Modifier.height(16.dp)) + + // Dating Prompt Card Card( - onClick = onMessageClick, - enabled = isMessageClickable, - modifier = Modifier - .weight(1f) - .height(48.dp), - shape = RoundedCornerShape(10.dp), - colors = CardDefaults.cardColors( - containerColor = if (isMessageClickable) Color.White else Color(0xFFF0F0F0), - disabledContainerColor = Color(0xFFF0F0F0) - ), - elevation = CardDefaults.cardElevation( - defaultElevation = if (isMessageClickable) 4.dp else 0.dp - ) + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color(0xFFF9F7FB)), + border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) ) { + Column(modifier = Modifier.padding(14.dp)) { + Text(text = "DATING PROMPT", fontSize = 9.sp, fontWeight = FontWeight.Bold, color = BrandMagenta) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "My Ideal weekend is...", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark) + Spacer(modifier = Modifier.height(6.dp)) + Text(text = "Hunting down the crispiest croissants in Brooklyn, visiting a modern art exhibit, and sunset walks by the pier.", fontSize = 12.sp, color = Color(0xFF555555), lineHeight = 16.sp) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // Interests & Passions + Column(modifier = Modifier.fillMaxWidth()) { Row( - modifier = Modifier.fillMaxSize(), - horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Icon( - imageVector = Icons.Default.ChatBubble, - contentDescription = "Message", - tint = if (isMessageClickable) textDark else Color(0xFFBDBDBD), - modifier = Modifier.size(18.dp) + Text( + text = "Interests & Passions", + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + color = textDark ) - Spacer(modifier = Modifier.width(8.dp)) Text( - text = "Message", - fontSize = 16.sp, + text = "${friendProfileData.county ?: 8} Shared", + fontSize = 11.sp, fontWeight = FontWeight.Bold, - color = if (isMessageClickable) textDark else Color(0xFFBDBDBD) + color = BrandMagenta ) } + Spacer(modifier = Modifier.height(8.dp)) + val interestsList = listOf("📷 Photography", "🐶 Dogs", "☕ Coffee Culture", "✈️ Travel", "🎨 Art & Design", "🍷 Wine Tasting") + OptFlowRow(interestsList) } - // ❤️ Like & ⭐ Super Like Buttons — Hidden when matched - if (showLikeAndSuperLike) { - // ❤️ Like Button - Card( - onClick = { - onLikeClick() - handleLikeAndDislikeAction(context, "like", friendProfileData, swipeViewModel) - localLiked = true - matchesViewModel.loadMatches(context) - }, - enabled = !likeDisabled, - modifier = Modifier.size(48.dp), - shape = RoundedCornerShape(10.dp), - colors = CardDefaults.cardColors( - containerColor = if (heLikedMe) Color(0xFFE53935) else Color.White, - disabledContainerColor = if (heLikedMe) Color(0xFFE53935) else Color(0xFFF0F0F0) - ), - elevation = CardDefaults.cardElevation( - defaultElevation = if (likeDisabled) 0.dp else 2.dp - ) - ) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Icon( - imageVector = Icons.Default.Favorite, - contentDescription = "Like", - tint = when { - heLikedMe -> Color.White - likeDisabled -> Color(0xFFBDBDBD) - else -> textDark - }, - modifier = Modifier.size(20.dp) - ) - } - } + Spacer(modifier = Modifier.height(16.dp)) - // ⭐ Super Like (Star) Button - Card( - onClick = { - onStarClick() - handleLikeAndDislikeAction(context, "like", friendProfileData, swipeViewModel) - localLiked = true - matchesViewModel.loadMatches(context) - }, - enabled = !likeDisabled, - modifier = Modifier.size(48.dp), - shape = RoundedCornerShape(10.dp), - colors = CardDefaults.cardColors( - containerColor = Color.White, - disabledContainerColor = Color(0xFFF0F0F0) - ), - elevation = CardDefaults.cardElevation( - defaultElevation = if (likeDisabled) 0.dp else 2.dp - ) - ) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Icon( - imageVector = Icons.Default.Star, - contentDescription = "Super Like", - tint = if (likeDisabled) Color(0xFFBDBDBD) else textDark, - modifier = Modifier.size(20.dp) - ) - } - } + // Key Information + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + Text( + text = "Key Information", + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + color = textDark + ) + + InfoRow(icon = "💼", title = "Occupation", value = "Creative Director at Studio Drift") + InfoRow(icon = "🎓", title = "Education", value = "NYU Gallatin School") + InfoRow(icon = "📏", title = "Height", value = "5'9\" (175 cm)") + InfoRow(icon = "🏛️", title = "Beliefs", value = "Spiritual / Agnostic") } - // ❌ Cancel / Dislike Button — Resets everything back to initial state - Card( - onClick = { - onCancelClick() - handleLikeAndDislikeAction(context, "pass", friendProfileData, swipeViewModel) - localLiked = false + Spacer(modifier = Modifier.height(16.dp)) - likedViewModel.likedResponse(context, "Likes") - likedViewModel.likedResponse(context, "Liked") - matchesViewModel.loadMatches(context) - }, - modifier = Modifier.size(48.dp), - shape = RoundedCornerShape(10.dp), - colors = CardDefaults.cardColors(containerColor = Color.White), - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) - ) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = "Dislike", - tint = textDark, - modifier = Modifier.size(20.dp) - ) + // Lifestyle Details + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = "Lifestyle Details", + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + color = textDark + ) + Spacer(modifier = Modifier.height(8.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) { + LifestyleTile(icon = "🚭", label = "Smoking", detail = "Non-smoker", modifier = Modifier.weight(1f)) + LifestyleTile(icon = "🍷", label = "Drinking", detail = "Socially", modifier = Modifier.weight(1f)) } } + + Spacer(modifier = Modifier.height(20.dp)) } + } - Spacer(modifier = Modifier.height(16.dp)) + // ==================================================== + // 8. USER POSTS / MASONRY GRID SECTION (ALWAYS VISIBLE) + // ==================================================== + Text( + text = "Posts", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + color = textDark, + modifier = Modifier.padding(bottom = 8.dp) + ) - // User Posts Section - Text( - text = "Posts", - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - color = textDark, - modifier = Modifier.padding(bottom = 8.dp) - ) + StaggeredFriendPostsGrid(posts = friendProfileData.posts) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(30.dp)) + } +} - StaggeredFriendPostsGrid(posts = friendProfileData.posts) +// ============================================================ +// HELPER COMPOSABLES +// ============================================================ + +@Composable +private fun InfoRow(icon: String, title: String, value: String) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(Color(0xFFF5F5F5)), + contentAlignment = Alignment.Center + ) { + Text(text = icon, fontSize = 14.sp) + } + Column { + Text(text = title, fontSize = 10.sp, color = TextMuted) + Text(text = value, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = textDark) + } + } +} + +@Composable +private fun LifestyleTile(icon: String, label: String, detail: String, modifier: Modifier = Modifier) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(12.dp), + color = Color(0xFFF9F9F9) + ) { + Row( + modifier = Modifier.padding(10.dp), + 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) + } + } + } +} + +@Composable +private fun OptFlowRow(items: List) { + Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { + val chunked = items.chunked(3) + chunked.forEach { rowItems -> + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + rowItems.forEach { item -> + Surface( + shape = RoundedCornerShape(16.dp), + color = Color(0xFFF4F4F4) + ) { + Text( + text = item, + fontSize = 11.sp, + fontWeight = FontWeight.Medium, + color = textDark, + modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp) + ) + } + } + } } } } @@ -495,15 +825,15 @@ fun StaggeredFriendPostsGrid(posts: List) { Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(3.dp) + horizontalArrangement = Arrangement.spacedBy(6.dp) ) { - Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) { + Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(6.dp)) { columns.first.forEachIndexed { idx, post -> val aspectRatio = if (idx % 2 == 0) 0.8f else 1.2f MasonryPostItem(post = post, aspectRatio = aspectRatio) } } - Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) { + Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(6.dp)) { columns.second.forEachIndexed { idx, post -> val aspectRatio = if (idx % 2 == 0) 1.2f else 0.8f MasonryPostItem(post = post, aspectRatio = aspectRatio) @@ -521,6 +851,7 @@ fun MasonryPostItem(post: FriendPost, aspectRatio: Float) { Card( shape = RoundedCornerShape(12.dp), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp), modifier = Modifier .fillMaxWidth() .aspectRatio(aspectRatio) @@ -556,28 +887,23 @@ fun MasonryPostItem(post: FriendPost, aspectRatio: Float) { Column( modifier = Modifier .align(Alignment.BottomStart) - .padding(2.dp), + .padding(8.dp), verticalArrangement = Arrangement.spacedBy(2.dp) ) { if (!post.feeling.isNullOrBlank()) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center - ) { - Text( - text = post.feeling, - color = Color.White.copy(alpha = 0.9f), - fontSize = 8.sp, - fontWeight = FontWeight.Bold - ) - } + Text( + text = post.feeling, + color = Color.White.copy(alpha = 0.9f), + fontSize = 9.sp, + fontWeight = FontWeight.Bold + ) } if (!post.content.isNullOrBlank()) { Text( text = post.content, color = Color.White, - fontSize = 10.sp, - maxLines = 1, + fontSize = 11.sp, + maxLines = 2, overflow = TextOverflow.Ellipsis ) } @@ -586,62 +912,29 @@ fun MasonryPostItem(post: FriendPost, aspectRatio: Float) { Column( modifier = Modifier .fillMaxSize() + .background(BrandPurple.copy(alpha = 0.8f)) .padding(10.dp), verticalArrangement = Arrangement.SpaceBetween ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(3.dp) - ) { - Icon( - imageVector = when (post.post_type.lowercase()) { - "music" -> Icons.Default.MusicNote - else -> Icons.Default.TextSnippet - }, - contentDescription = null, - tint = Color.White, - modifier = Modifier.size(10.dp) - ) - Text( - text = post.post_type.uppercase(), - color = Color.White.copy(alpha = 0.9f), - fontSize = 7.sp, - fontWeight = FontWeight.ExtraBold - ) - } - } + Text( + text = post.post_type.uppercase(), + color = Color.White.copy(alpha = 0.9f), + fontSize = 8.sp, + fontWeight = FontWeight.ExtraBold + ) Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { - if (!post.feeling.isNullOrBlank()) { - Surface( - shape = RoundedCornerShape(6.dp), - color = Color.White.copy(alpha = 0.2f) - ) { - Text( - text = "feeling ${post.feeling}", - color = Color.White, - fontSize = 7.sp, - modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } + if (!post.content.isNullOrBlank()) { + Text( + text = post.content, + color = Color.White, + fontSize = 11.sp, + fontWeight = FontWeight.SemiBold, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + lineHeight = 14.sp + ) } - - Text( - text = post.content ?: "", - color = Color.White, - fontSize = 11.sp, - fontWeight = FontWeight.SemiBold, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - lineHeight = 14.sp - ) } Row( diff --git a/app/src/main/java/com/example/dattingapp/LikeANdMatchActivity.kt b/app/src/main/java/com/example/dattingapp/LikeANdMatchActivity.kt index 861b429..0455f3b 100644 --- a/app/src/main/java/com/example/dattingapp/LikeANdMatchActivity.kt +++ b/app/src/main/java/com/example/dattingapp/LikeANdMatchActivity.kt @@ -13,14 +13,16 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.LocationOn +import androidx.compose.material.icons.filled.Star import androidx.compose.material3.* import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -35,6 +37,8 @@ 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.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import com.example.dattingapp.homes.networkandapi.LikedUiState @@ -62,7 +66,8 @@ class LikeANdMatchActivity : ComponentActivity() { else -> likedViewModel.matchesUiState.collectAsState() } - LaunchedEffect(Unit) { + // Runs on first open AND every time the user comes back to this screen + LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { likedViewModel.likedResponse(context, type) } @@ -106,14 +111,19 @@ class LikeANdMatchActivity : ComponentActivity() { is LikedUiState.Idle, is LikedUiState.Loading -> { CircularProgressIndicator() } + is LikedUiState.Success -> { ProfileGrid( context = context, likedResponse = state.LikeData, isLoading = false, - errorMessage = null + errorMessage = null, + onSwipe = { profile, direction -> + likedViewModel.swipeUser(context, type, profile.id, direction) + } ) } + is LikedUiState.Error -> { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -144,6 +154,9 @@ fun ProfileCard( likedProfile: LikedUserResponse, modifier: Modifier = Modifier, onNameClick: () -> Unit = {}, + onCancel: (() -> Unit)? = null, + onSuperLike: (() -> Unit)? = null, + onLike: (() -> Unit)? = null ) { Card( shape = RoundedCornerShape(16.dp), @@ -165,7 +178,7 @@ fun ProfileCard( onNameClick() } ) { - // FIX: media can be null from the server, so use ?. here + // media can be null from the server, so use ?. here AsyncImage( model = likedProfile.profile_image_url?.takeIf { it.isNotBlank() } ?: likedProfile.media?.firstOrNull(), @@ -223,8 +236,6 @@ fun ProfileCard( } val nameAndAge = if (age != null) "$displayName, $age" else displayName - Log.d("user", "${likedProfile.id}") - Text( text = nameAndAge, color = Color.White, @@ -249,6 +260,57 @@ fun ProfileCard( } } } + + // Action buttons: Cancel (pass) / Super like / Like + if (onCancel != null && onSuperLike != null && onLike != null) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 8.dp, end = 8.dp, bottom = 10.dp), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + IconButton( + onClick = onCancel, + modifier = Modifier + .size(30.dp) + .clip(CircleShape) + .background(Color(0xFFEBEBEB)) + ) { + Icon( + Icons.Default.Close, "Pass", + tint = Color(0xFF555555), + modifier = Modifier.size(20.dp) + ) + } + IconButton( + onClick = onSuperLike, + modifier = Modifier + .size(30.dp) + .clip(CircleShape) + .background(Color(0xFFEDE7F6)) + ) { + Icon( + Icons.Default.Star, "Super like", + tint = Color(0xFF7B1FA2), + modifier = Modifier.size(20.dp) + ) + } + IconButton( + onClick = onLike, + modifier = Modifier + .size(30.dp) + .clip(CircleShape) + .background(Color(0xFFE91E63)) + ) { + Icon( + Icons.Default.Favorite, "Like", + tint = Color.White, + modifier = Modifier.size(20.dp) + ) + } + } + } } } } @@ -260,7 +322,8 @@ fun ProfileGrid( isLoading: Boolean, errorMessage: String?, onRetry: () -> Unit = {}, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + onSwipe: ((LikedUserResponse, String) -> Unit)? = null ) { if (likedResponse.isEmpty() && !isLoading) { Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { @@ -276,8 +339,14 @@ fun ProfileGrid( verticalArrangement = Arrangement.spacedBy(12.dp), modifier = modifier.fillMaxSize() ) { - items(likedResponse) { profile -> - ProfileCard(context, likedProfile = profile) + items(likedResponse, key = { it.id }) { profile -> + ProfileCard( + context = context, + likedProfile = profile, + onCancel = onSwipe?.let { f -> { f(profile, "pass") } }, + onSuperLike = onSwipe?.let { f -> { f(profile, "superlike") } }, + onLike = onSwipe?.let { f -> { f(profile, "like") } } + ) } } } @@ -321,7 +390,8 @@ fun ProfileGridPreview() { context = context, likedResponse = sampleLikedUsers, isLoading = false, - errorMessage = null + errorMessage = null, + onSwipe = { _, _ -> } ) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/dattingapp/ShowPostingActivity.kt b/app/src/main/java/com/example/dattingapp/ShowPostingActivity.kt index 94534f9..87059e9 100644 --- a/app/src/main/java/com/example/dattingapp/ShowPostingActivity.kt +++ b/app/src/main/java/com/example/dattingapp/ShowPostingActivity.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -171,6 +172,24 @@ fun PostCard( var showPostMenu by remember { mutableStateOf(false) } var showDeleteDialog by remember { mutableStateOf(false) } + // In-place post editing + var isEditingPost by remember { mutableStateOf(false) } + var postEditText by remember { mutableStateOf("") } + + val cancelPostEdit = { + isEditingPost = false + postEditText = "" + } + val savePostEdit = { + if (postEditText.isNotBlank() && + postEditText.trim() != post.content.orEmpty().trim() + ) { + showPostViewModel.updatePost(context, post.id, postEditText) + } + isEditingPost = false + postEditText = "" + } + Column( modifier = modifier .fillMaxWidth() @@ -254,7 +273,8 @@ fun PostCard( text = { Text("Edit") }, onClick = { showPostMenu = false - // TODO: edit post (empty for now) + postEditText = post.content.orEmpty() + isEditingPost = true } ) DropdownMenuItem( @@ -272,7 +292,29 @@ fun PostCard( // --- 2. Media / text content --- if (post.media.isNotEmpty()) { - if (!post.content.isNullOrEmpty()) { + // Post text: becomes an edit field in the same place + if (isEditingPost) { + BasicTextField( + value = postEditText, + onValueChange = { postEditText = it }, + textStyle = MaterialTheme.typography.bodyMedium, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 4.dp) + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp), + horizontalArrangement = Arrangement.End + ) { + TextButton(onClick = cancelPostEdit) { Text("Cancel") } + TextButton( + enabled = postEditText.isNotBlank(), + onClick = savePostEdit + ) { Text("Save", color = AccentPink) } + } + } else if (!post.content.isNullOrEmpty()) { Text( text = post.content, style = MaterialTheme.typography.bodyMedium, @@ -281,6 +323,7 @@ fun PostCard( .padding(horizontal = 12.dp, vertical = 4.dp) ) } + Card( shape = RoundedCornerShape(16.dp), modifier = Modifier @@ -288,9 +331,7 @@ fun PostCard( .padding(start = 10.dp, end = 10.dp) .height(360.dp), ) { - // FIX: was AsyncImage(model = post.media.first().url) — showed ONLY - // the first media item, always as an image, with the raw URL. - // Now: swipeable pager over ALL media, images AND videos. + // Swipeable pager over ALL media, images AND videos PostMediaPager( media = post.media, modifier = Modifier.fillMaxSize() @@ -299,29 +340,44 @@ fun PostCard( } else { - Card( - shape = RoundedCornerShape(16.dp), - modifier = Modifier - .fillMaxWidth() - .padding(start = 10.dp, end = 10.dp) - .height(360.dp), - colors = CardDefaults.cardColors(containerColor = Color.White) - ) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Text( - text = post.content.orEmpty(), + // Text-only post: no card, no background, just the text + val textSize = if ((post.content?.length ?: 0) > 120) 18.sp else 22.sp + + if (isEditingPost) { + BasicTextField( + value = postEditText, + onValueChange = { postEditText = it }, + textStyle = TextStyle( color = TextPostTextColor, - fontSize = if ((post.content?.length ?: 0) > 120) 18.sp else 24.sp, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center, - maxLines = 8, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.padding(horizontal = 32.dp) - ) + fontSize = textSize, + fontWeight = FontWeight.Medium + ), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp) + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp), + horizontalArrangement = Arrangement.End + ) { + TextButton(onClick = cancelPostEdit) { Text("Cancel") } + TextButton( + enabled = postEditText.isNotBlank(), + onClick = savePostEdit + ) { Text("Save", color = AccentPink) } } + } else if (!post.content.isNullOrEmpty()) { + Text( + text = post.content, + color = TextPostTextColor, + fontSize = textSize, + fontWeight = FontWeight.Medium, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp) + ) } } 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 4757ba4..10c83d7 100644 --- a/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt +++ b/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt @@ -1,7 +1,11 @@ package com.example.dattingapp.homes import android.content.Intent -import androidx.compose.foundation.Image +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -12,15 +16,17 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Collections import androidx.compose.material.icons.filled.Favorite -import androidx.compose.material.icons.filled.MusicNote -import androidx.compose.material.icons.filled.PlayCircle -import androidx.compose.material.icons.filled.TextSnippet +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material.icons.filled.MoreHoriz +import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.outlined.ChatBubbleOutline import androidx.compose.material.icons.outlined.Settings +import androidx.compose.material.icons.outlined.Share 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 @@ -34,15 +40,14 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import coil.ImageLoader import coil.compose.AsyncImage -import coil.decode.VideoFrameDecoder +import com.example.dattingapp.EditMyProfileActivty import com.example.dattingapp.LikeANdMatchActivity import com.example.dattingapp.MatchesActivity import com.example.dattingapp.R import com.example.dattingapp.ShowPostingActivity -import com.example.dattingapp.homes.networkandapi.PostInteractionStore // NEW -import com.example.dattingapp.homes.networkandapi.rememberPostInteraction // NEW +import com.example.dattingapp.homes.networkandapi.PostInteractionStore +import com.example.dattingapp.homes.networkandapi.rememberPostInteraction import com.example.dattingapp.homes.profile.Author import com.example.dattingapp.homes.profile.Media import com.example.dattingapp.homes.profile.Post @@ -57,7 +62,6 @@ import java.time.LocalDate import java.time.Period import java.time.format.DateTimeFormatter -// Helper function to calculate age from birth_date string (e.g., "1995-05-15") private fun calculateAge(birthDateStr: String?): Int? { if (birthDateStr.isNullOrBlank()) return null return try { @@ -85,17 +89,16 @@ fun MyProfileScreen( val context = LocalContext.current val scrollState = rememberScrollState() + // Controls only the extra profile details (hidden by default, persists until toggled) + var showExtraInfo by rememberSaveable { mutableStateOf(false) } + val profile = profileResponse?.profile - val age = calculateAge(profile?.birth_date) + val age = calculateAge(profile?.birth_date) ?: 28 - // NEW: posts deleted anywhere in the app - val deletedIds by PostInteractionStore.deletedIds.collectAsState() - val visiblePosts = (profileResponse?.posts ?: emptyList()).filter { it.id !in deletedIds } + val allPosts = profileResponse?.posts ?: emptyList() - // NEW: seed the shared store so grid tiles show LIVE like/comment counts - // (likes/comments made on the details screen update the grid instantly) - LaunchedEffect(visiblePosts) { - visiblePosts.forEach { + LaunchedEffect(allPosts) { + allPosts.forEach { PostInteractionStore.setFromServer(it.id, it.liked_by_me, it.like_count, it.comment_count) } } @@ -105,51 +108,28 @@ fun MyProfileScreen( .fillMaxSize() .background(Color.White) .verticalScroll(scrollState) + .padding(16.dp) ) { + + Spacer(modifier = Modifier.height(16.dp)) + // ==================================================== - // HEADER & COVER IMAGE + // 2. PROFILE IDENTITY // ==================================================== - Box( - modifier = Modifier - .fillMaxWidth() - .height(260.dp) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp) ) { - Image( - painter = painterResource(id = R.drawable.locationpermissionbg), - contentDescription = "Cover Banner", - modifier = Modifier - .fillMaxWidth() - .height(200.dp), - contentScale = ContentScale.Crop - ) - - // SETTINGS BUTTON - IconButton( - onClick = onSettingsClick, - modifier = Modifier - .align(Alignment.TopEnd) - .padding(top = 16.dp, end = 16.dp) - .size(36.dp) - .clip(CircleShape) - .background(Color.Black.copy(alpha = 0.3f)) - ) { - Icon( - imageVector = Icons.Outlined.Settings, - contentDescription = "Settings", - tint = Color.White, - modifier = Modifier.size(20.dp) - ) - } - - // PROFILE IMAGE & VERIFIED BADGE Box( modifier = Modifier - .align(Alignment.BottomStart) - .padding(start = 24.dp) - .size(110.dp) + .size(84.dp) + .clip(CircleShape) + .background(brush = Brush.linearGradient(colors = listOf(BrandMagenta, BrandPurple))) + .padding(3.dp) ) { AsyncImage( - model = profile?.profile_image_url, + model = profile?.profile_image_url ?: "https://picsum.photos/300/300", contentDescription = "Profile Picture", contentScale = ContentScale.Crop, placeholder = painterResource(id = R.drawable.locationpermissionbg), @@ -157,151 +137,216 @@ fun MyProfileScreen( modifier = Modifier .fillMaxSize() .clip(CircleShape) - .border(width = 4.dp, color = Color.White, shape = CircleShape) ) - - Box( - modifier = Modifier - .align(Alignment.BottomEnd) - .size(28.dp) - .clip(CircleShape) - .background(BrandMagenta) - .border(width = 2.dp, color = Color.White, shape = CircleShape), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = "Verified", - tint = Color.White, - modifier = Modifier.size(16.dp) - ) - } } - } - // ==================================================== - // USER INFORMATION SECTION - // ==================================================== - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 8.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) + horizontalArrangement = Arrangement.spacedBy(6.dp) ) { Text( - text = if (age != null) "${profile?.full_name ?: ""}, $age" else (profile?.full_name ?: ""), - fontSize = 22.sp, + text = profile?.full_name ?: "Alex Rivera", + fontSize = 20.sp, fontWeight = FontWeight.Bold, color = textDark ) - } - } + Text( + text = "$age", + fontSize = 20.sp, + fontWeight = FontWeight.Normal, + color = textDark + ) - Spacer(modifier = Modifier.height(4.dp)) + IconButton( + onClick = { + val intent = Intent(context, EditMyProfileActivty::class.java) + context.startActivity(intent) + }, + modifier = Modifier + .size(36.dp) + .clip(CircleShape) + .background(Color(0xFFF7F7F7)) + ) { + Icon( + imageVector = Icons.Outlined.Settings, + contentDescription = "Settings", + tint = textDark, + modifier = Modifier.size(18.dp) + ) + } + } - val locationText = listOfNotNull(profile?.city, profile?.country).joinToString(", ") - if (locationText.isNotBlank()) { + val city = profile?.city ?: "New York" Text( - text = "📍 $locationText", - fontSize = 13.sp, - color = TextMuted + text = "📍 $city, NY • 2 miles away", + fontSize = 12.sp, + color = BrandPurple, + fontWeight = FontWeight.SemiBold ) - Spacer(modifier = Modifier.height(6.dp)) } + } - if (!profile?.bio.isNullOrBlank()) { - Card( - modifier = Modifier.fillMaxWidth(), - shape = RoundedCornerShape(12.dp), - colors = CardDefaults.cardColors(containerColor = Color(0xFFF9F9F9)), - border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) - ) { - Text( - text = profile.bio!!, - fontSize = 13.sp, - color = textDark, - lineHeight = 18.sp, - modifier = Modifier.padding(12.dp) - ) + Spacer(modifier = Modifier.height(16.dp)) + + // ==================================================== + // 3. STAT CARDS + // ==================================================== + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + StatCard( + number = profileResponse?.liked_users_count?.toString() ?: "24", + label = "Who Liked Me", + modifier = Modifier.weight(1f), + onClick = { + val intent = Intent(context, LikeANdMatchActivity::class.java).apply { putExtra("TYPE", "Likes") } + context.startActivity(intent) + onLikesClick() } - Spacer(modifier = Modifier.height(10.dp)) - } + ) + StatCard( + number = profileResponse?.liked_by_users_count?.toString() ?: "12", + label = "Who I Liked", + modifier = Modifier.weight(1f), + onClick = { + val intent = Intent(context, LikeANdMatchActivity::class.java).apply { putExtra("TYPE", "Liked") } + context.startActivity(intent) + onLikedClick() + } + ) + StatCard( + number = profileResponse?.matches_count?.toString() ?: "8", + label = "Matches", + modifier = Modifier.weight(1f), + onClick = { + val intent = Intent(context, MatchesActivity::class.java) + context.startActivity(intent) + onMatchClick() + } + ) + } - if (!profile?.interests.isNullOrEmpty()) { - FlowRow( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalArrangement = Arrangement.spacedBy(6.dp) + Spacer(modifier = Modifier.height(12.dp)) + + // ==================================================== + // 4. LOOKING FOR CARD (ALWAYS VISIBLE) + // ==================================================== + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(14.dp), + colors = CardDefaults.cardColors(containerColor = Color(0xFFF7F4F9)), + border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(14.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) ) { - profile.interests.forEach { interest -> - Surface( - shape = RoundedCornerShape(16.dp), - color = BrandMagenta.copy(alpha = 0.1f) - ) { - Text( - text = interest, - color = BrandMagenta, - fontSize = 12.sp, - fontWeight = FontWeight.Medium, - modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp) - ) - } + Box( + modifier = Modifier + .size(32.dp) + .clip(CircleShape) + .background(BrandMagenta.copy(alpha = 0.15f)), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.Favorite, + contentDescription = null, + tint = BrandMagenta, + modifier = Modifier.size(16.dp) + ) + } + Column { + Text( + text = "LOOKING FOR", + fontSize = 10.sp, + fontWeight = FontWeight.Bold, + color = TextMuted + ) + Text( + text = profile?.relationship_goal ?: "Long-term relationship / Serious dating", + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + color = textDark + ) } } + Text(text = "›", fontSize = 18.sp, color = TextMuted) } } Spacer(modifier = Modifier.height(12.dp)) // ==================================================== - // PREMIUM BANNER + // 5. VIDEO INTRO (ALWAYS VISIBLE) // ==================================================== - Box( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .clip(RoundedCornerShape(16.dp)) - .background(brush = Brush.horizontalGradient(colors = listOf(BrandMagenta, BrandPurple))) - .padding(16.dp) - ) { + Column(modifier = Modifier.fillMaxWidth()) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - Column { - Text( - text = "VibeCheck Premium", - color = Color.White, - fontSize = 16.sp, - fontWeight = FontWeight.Bold - ) - Spacer(modifier = Modifier.height(2.dp)) + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) { + Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null, tint = BrandMagenta, modifier = Modifier.size(18.dp)) + Text(text = "Video Intro", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark) + } + Surface( + shape = RoundedCornerShape(12.dp), + color = Color(0xFFF2EBF9) + ) { Text( - text = "Unlock unlimited likes & more", - color = Color.White.copy(alpha = 0.8f), - fontSize = 12.sp + text = "Say hello! 📹", + fontSize = 11.sp, + color = BrandMagenta, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) ) } - Button( - onClick = onUpgradeClick, - colors = ButtonDefaults.buttonColors(containerColor = Color.White), - shape = RoundedCornerShape(20.dp), - contentPadding = PaddingValues(horizontal = 14.dp, vertical = 4.dp) + } + Spacer(modifier = Modifier.height(8.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + .clip(RoundedCornerShape(16.dp)) + .background(Color.Black) + ) { + AsyncImage( + model = profile?.video_url ?: "https://picsum.photos/600/300", + contentDescription = "Video Intro", + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + Box( + modifier = Modifier + .size(48.dp) + .clip(CircleShape) + .background(Color.White.copy(alpha = 0.9f)) + .align(Alignment.Center), + contentAlignment = Alignment.Center + ) { + Icon(imageVector = Icons.Default.PlayArrow, contentDescription = "Play", tint = BrandMagenta, modifier = Modifier.size(28.dp)) + } + Surface( + color = Color.Black.copy(alpha = 0.6f), + shape = RoundedCornerShape(8.dp), + modifier = Modifier + .align(Alignment.BottomStart) + .padding(10.dp) ) { Text( - text = "Upgrade", - color = BrandMagenta, - fontSize = 12.sp, - fontWeight = FontWeight.Bold + text = "0:45s \"Quick vibe & Sunday routine\"", + color = Color.White, + fontSize = 10.sp, + modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) ) } } @@ -310,489 +355,399 @@ fun MyProfileScreen( Spacer(modifier = Modifier.height(16.dp)) // ==================================================== - // STATISTICS CARDS + // 🔘 TOGGLE BUTTON FOR EXTRA PROFILE DETAILS ONLY // ==================================================== - Row( + Surface( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) + .clickable { showExtraInfo = !showExtraInfo }, + shape = RoundedCornerShape(12.dp), + color = Color(0xFFF2EBF9) ) { - StatCard( - number = profileResponse?.liked_users_count?.toString() ?: "0", - label = "Likes", - modifier = Modifier.weight(1f), - onClick = { - val intent = Intent(context, LikeANdMatchActivity::class.java).apply { - putExtra("TYPE", "Likes") - } - context.startActivity(intent) - onLikesClick() - } - ) - StatCard( - number = profileResponse?.liked_by_users_count?.toString() ?: "0", - label = "Liked", - modifier = Modifier.weight(1f), - onClick = { - val intent = Intent(context, LikeANdMatchActivity::class.java).apply { - putExtra("TYPE", "Liked") - } - context.startActivity(intent) - onLikedClick() + Row( + modifier = Modifier.padding(vertical = 12.dp, horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + text = if (showExtraInfo) "Hide Extra Profile Details" else "View All Profile Details", + fontSize = 13.sp, + fontWeight = FontWeight.Bold, + color = BrandMagenta + ) + Icon( + imageVector = if (showExtraInfo) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown, + contentDescription = "Toggle Details", + tint = BrandMagenta + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // ==================================================== + // EXTRA PROFILE DETAILS (HIDDEN BY DEFAULT, TOGGLED) + // ==================================================== + AnimatedVisibility( + visible = showExtraInfo, + enter = expandVertically() + fadeIn(), + exit = shrinkVertically() + fadeOut() + ) { + Column(modifier = Modifier.fillMaxWidth()) { + // Bio + Text( + text = "About ${profile?.full_name?.substringBefore(" ") ?: "Alex"}", + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + color = textDark + ) + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = profile?.bio ?: "Adventure seeker, dog lover, and amateur chef. Always hunting for the crispiest croissants in Brooklyn.", + fontSize = 13.sp, + color = Color(0xFF555555), + lineHeight = 18.sp + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Dating Prompt + PromptCard( + title = "My Ideal weekend is...", + description = "Hunting down the crispiest croissants in Brooklyn, visiting a modern art exhibit, and sunset walks by the pier." + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Interests & Passions + Text( + text = "Interests & Passions", + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + color = textDark + ) + Spacer(modifier = Modifier.height(8.dp)) + val interestsList = profile?.interests ?: listOf("📷 Photography", "🐶 Dogs", "☕ Coffee Culture", "✈️ Travel") + OptFlowRow(interestsList) + + Spacer(modifier = Modifier.height(16.dp)) + + // Key Information + Column(verticalArrangement = Arrangement.spacedBy(10.dp)) { + Text( + text = "Key Information", + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + color = textDark + ) + + InfoRow(icon = "💼", title = "Occupation", value = "Creative Director at Studio Drift") + InfoRow(icon = "🎓", title = "Education", value = "NYU Gallatin School") + InfoRow(icon = "📏", title = "Height", value = "5'9\" (175 cm)") + InfoRow(icon = "🏛️", title = "Beliefs", value = "Spiritual / Agnostic") } - ) - StatCard( - number = profileResponse?.matches_count?.toString() ?: "0", - label = "Matches", - modifier = Modifier.weight(1f), - onClick = { - val intent = Intent(context, MatchesActivity ::class.java) - context.startActivity(intent) - onMatchClick() + + Spacer(modifier = Modifier.height(16.dp)) + + // Lifestyle Details + Text( + text = "Lifestyle Details", + fontSize = 15.sp, + fontWeight = FontWeight.Bold, + color = textDark + ) + Spacer(modifier = Modifier.height(8.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + LifestyleTile(icon = "🚭", label = "Smoking", detail = "Non-smoker", modifier = Modifier.weight(1f)) + LifestyleTile(icon = "🍷", label = "Drinking", detail = "Socially", modifier = Modifier.weight(1f)) } - ) - } - Spacer(modifier = Modifier.height(20.dp)) + Spacer(modifier = Modifier.height(20.dp)) + } + } // ==================================================== - // POSTED SECTION — header now includes the post count + // 6. MY VIBE POSTS (ALWAYS VISIBLE BELOW TOGGLE) // ==================================================== Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Posted", - fontSize = 18.sp, + text = "My Vibe Posts", + fontSize = 16.sp, fontWeight = FontWeight.Bold, color = textDark ) - Text( - text = "${visiblePosts.size} posts", - fontSize = 13.sp, - color = TextMuted - ) + TextButton(onClick = {}) { + Text(text = "+ New Vibe", fontSize = 12.sp, color = BrandMagenta, fontWeight = FontWeight.Bold) + } } - Spacer(modifier = Modifier.height(10.dp)) - ProfilePostsGrid(posts = visiblePosts) // CHANGED: deleted posts are filtered out + Spacer(modifier = Modifier.height(8.dp)) + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + if (allPosts.isEmpty()) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(100.dp), + contentAlignment = Alignment.Center + ) { + Text(text = "No posts yet", color = TextMuted, fontSize = 13.sp) + } + } else { + allPosts.forEach { post -> + FeedPostCard(post = post, profile = profile) + } + } + } Spacer(modifier = Modifier.height(30.dp)) } } // ============================================================ -// STAT CARD +// HELPER COMPOSABLES // ============================================================ + @Composable -private fun StatCard( - number: String, - label: String, - modifier: Modifier = Modifier, - numberColor: Color = textDark, - onClick: () -> Unit -) { +private fun PromptCard(title: String, description: String) { Card( - modifier = modifier.clickable { onClick() }, - shape = RoundedCornerShape(12.dp), - colors = CardDefaults.cardColors(containerColor = Color(0xFFFAFAFA)), + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color(0xFFF9F7FB)), border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = number, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - color = numberColor - ) - Spacer(modifier = Modifier.height(2.dp)) - Text( - text = label, - fontSize = 11.sp, - color = TextMuted - ) + Column(modifier = Modifier.padding(14.dp)) { + Text(text = "DATING PROMPT", fontSize = 9.sp, fontWeight = FontWeight.Bold, color = BrandMagenta) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = title, fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark) + Spacer(modifier = Modifier.height(6.dp)) + Text(text = description, fontSize = 12.sp, color = Color(0xFF555555), lineHeight = 16.sp) } } } -// ============================================================ -// STAGGERED PROFILE POSTS GRID -// ============================================================ @Composable -private fun ProfilePostsGrid(posts: List) { - if (posts.isEmpty()) { +private fun InfoRow(icon: String, title: String, value: String) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { Box( modifier = Modifier - .fillMaxWidth() - .height(120.dp), + .size(32.dp) + .clip(CircleShape) + .background(Color(0xFFF5F5F5)), contentAlignment = Alignment.Center ) { - Text(text = "No posts yet", color = TextMuted, fontSize = 14.sp) + Text(text = icon, fontSize = 14.sp) } - return - } - - val columns = remember(posts) { - val col1 = mutableListOf() - val col2 = mutableListOf() - - posts.forEachIndexed { index, post -> - if (index % 2 == 0) col1.add(post) else col2.add(post) + Column { + Text(text = title, fontSize = 10.sp, color = TextMuted) + Text(text = value, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = textDark) } - Pair(col1, col2) } +} - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.spacedBy(6.dp) +@Composable +private fun LifestyleTile(icon: String, label: String, detail: String, modifier: Modifier = Modifier) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(12.dp), + color = Color(0xFFF9F9F9) ) { - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(6.dp) - ) { - columns.first.forEachIndexed { idx, post -> - val aspectRatio = if (idx % 2 == 0) 0.8f else 1.2f - MasonryProfilePostItem(post = post, aspectRatio = aspectRatio) - } - } - Column( - modifier = Modifier.weight(1f), - verticalArrangement = Arrangement.spacedBy(6.dp) + Row( + modifier = Modifier.padding(10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - columns.second.forEachIndexed { idx, post -> - val aspectRatio = if (idx % 2 == 0) 1.2f else 0.8f - MasonryProfilePostItem(post = post, aspectRatio = aspectRatio) + 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) } } } } -// Small live like/comment row used on every tile @Composable -private fun LikeCommentRow(post: Post) { - // Live values from the shared store; fall back to the values that came - // with the profile response. - val live = rememberPostInteraction(post.id) - val likeCount = live?.likeCount ?: post.like_count - val commentCount = live?.commentCount ?: post.comment_count - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(2.dp) - ) { - Icon( - imageVector = Icons.Default.Favorite, - contentDescription = "Likes", - tint = Color.White.copy(alpha = 0.8f), - modifier = Modifier.size(9.dp) - ) - Text( - text = "$likeCount", - color = Color.White.copy(alpha = 0.8f), - fontSize = 8.sp - ) - } - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(2.dp) - ) { - Icon( - imageVector = Icons.Outlined.ChatBubbleOutline, - contentDescription = "Comments", - tint = Color.White.copy(alpha = 0.8f), - modifier = Modifier.size(9.dp) - ) - Text( - text = "$commentCount", - color = Color.White.copy(alpha = 0.8f), - fontSize = 8.sp - ) +private fun OptFlowRow(items: List) { + Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { + val chunked = items.chunked(3) + chunked.forEach { rowItems -> + Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { + rowItems.forEach { item -> + Surface( + shape = RoundedCornerShape(16.dp), + color = Color(0xFFF4F4F4) + ) { + Text( + text = item, + fontSize = 11.sp, + fontWeight = FontWeight.Medium, + color = textDark, + modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp) + ) + } + } + } } } } @Composable -private fun MasonryProfilePostItem(post: Post, aspectRatio: Float) { +private fun FeedPostCard(post: Post, profile: ProfileData?) { val context = LocalContext.current - - // NEW: first media item + type detection + total count - val firstMedia = post.media.firstOrNull() - val primaryMedia = firstMedia?.url - val mediaCount = post.media.size - val isVideo = firstMedia?.media_type.equals("video", ignoreCase = true) - val isImage = post.post_type.equals("image", ignoreCase = true) || - post.post_type.equals("video", ignoreCase = true) || - !primaryMedia.isNullOrEmpty() - - // NEW: ImageLoader that can grab a FRAME out of a video file. - // Needs: implementation("io.coil-kt:coil-video:2.6.0") in app/build.gradle.kts - // It only extracts a still image for display — nothing is played here. - val videoAwareImageLoader = remember { - ImageLoader.Builder(context) - .components { add(VideoFrameDecoder.Factory()) } - .build() - } + val live = rememberPostInteraction(post.id) + val likeCount = live?.likeCount ?: post.like_count + val commentCount = live?.commentCount ?: post.comment_count + val primaryMedia = post.media.firstOrNull()?.url Card( - shape = RoundedCornerShape(12.dp), - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), modifier = Modifier .fillMaxWidth() - .aspectRatio(aspectRatio) .clickable { - val intent = Intent(context, ShowPostingActivity::class.java).apply { - putExtra("Post_ID", post.id) - } + val intent = Intent(context, ShowPostingActivity::class.java).apply { putExtra("Post_ID", post.id) } context.startActivity(intent) - } + }, + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors(containerColor = Color.White), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), + border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) ) { - Box(modifier = Modifier.fillMaxSize()) { - if (isImage && !primaryMedia.isNullOrEmpty()) { - // CHANGED: the video-aware loader shows a real FRAME of the - // video as the tile background (works for images too). - AsyncImage( - model = primaryMedia, - imageLoader = videoAwareImageLoader, - contentDescription = if (isVideo) "Post Video" else "Post Image", - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize() - ) - - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.verticalGradient( - colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.65f)), - startY = 100f - ) - ) - ) - - // NEW: small play icon over the video frame so users know it's a video - if (isVideo) { - Icon( - imageVector = Icons.Default.PlayCircle, - contentDescription = "Video", - tint = Color.White.copy(alpha = 0.85f), - modifier = Modifier - .align(Alignment.Center) - .size(32.dp) + Column(modifier = Modifier.fillMaxWidth().padding(14.dp)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) { + AsyncImage( + model = profile?.profile_image_url ?: "https://picsum.photos/300/300", + contentDescription = "Avatar", + contentScale = ContentScale.Crop, + modifier = Modifier.size(36.dp).clip(CircleShape) ) - } - - Column( - modifier = Modifier - .align(Alignment.BottomStart) - .padding(8.dp), - verticalArrangement = Arrangement.spacedBy(2.dp) - ) { - if (!post.feeling.isNullOrBlank()) { - Text( - text = post.feeling, - color = Color.White.copy(alpha = 0.9f), - fontSize = 9.sp, - fontWeight = FontWeight.Bold - ) - } - if (!post.content.isNullOrBlank()) { - Text( - text = post.content, - color = Color.White, - fontSize = 11.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - // NEW: image tiles now show like + comment counts too - LikeCommentRow(post) - } - } else { - Column( - modifier = Modifier - .fillMaxSize() - .padding(10.dp), - verticalArrangement = Arrangement.SpaceBetween - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(3.dp) - ) { - Icon( - imageVector = when (post.post_type.lowercase()) { - "music" -> Icons.Default.MusicNote - else -> Icons.Default.TextSnippet - }, - contentDescription = null, - tint = Color.White, - modifier = Modifier.size(10.dp) - ) - Text( - text = post.post_type.uppercase(), - color = Color.White.copy(alpha = 0.9f), - fontSize = 7.sp, - fontWeight = FontWeight.ExtraBold - ) + Column { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) { + Text(text = profile?.full_name ?: "Alex Rivera", fontWeight = FontWeight.Bold, fontSize = 13.sp, color = textDark) + Icon(imageVector = Icons.Default.Check, contentDescription = "Verified", tint = BrandMagenta, modifier = Modifier.size(11.dp)) } + Text(text = "2 hours ago • Soho, New York", fontSize = 10.sp, color = TextMuted) } + } + } - Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { - if (!post.feeling.isNullOrBlank()) { - Surface( - shape = RoundedCornerShape(6.dp), - color = Color.White.copy(alpha = 0.2f) - ) { - Text( - text = "feeling ${post.feeling}", - color = Color.White, - fontSize = 7.sp, - modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp), - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } + Spacer(modifier = Modifier.height(8.dp)) - Text( - text = post.content ?: "", - color = Color.White, - fontSize = 11.sp, - fontWeight = FontWeight.SemiBold, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - lineHeight = 14.sp - ) - } - - // CHANGED: text tiles now show like AND comment counts - LikeCommentRow(post) - } + if (!post.content.isNullOrBlank()) { + Text(text = post.content!!, fontSize = 12.sp, color = textDark) + Spacer(modifier = Modifier.height(8.dp)) } - // NEW: stacked-pages badge — shown when the post has MORE THAN ONE - // media item (like Instagram's multi-photo indicator) - if (mediaCount > 1) { - Surface( - color = Color.Black.copy(alpha = 0.55f), - shape = RoundedCornerShape(8.dp), + if (!primaryMedia.isNullOrEmpty()) { + Box( modifier = Modifier - .align(Alignment.TopEnd) - .padding(6.dp) + .fillMaxWidth() + .height(200.dp) + .clip(RoundedCornerShape(12.dp)) ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(2.dp), - modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp) - ) { - Icon( - imageVector = Icons.Default.Collections, - contentDescription = "Multiple media", - tint = Color.White, - modifier = Modifier.size(11.dp) - ) - Text( - text = "$mediaCount", - color = Color.White, - fontSize = 9.sp, - fontWeight = FontWeight.Bold - ) - } + AsyncImage( + model = primaryMedia, + contentDescription = "Post Image", + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } + Spacer(modifier = Modifier.height(8.dp)) + } + + Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) { + Icon(imageVector = Icons.Default.Favorite, contentDescription = "Likes", tint = Color.Red, modifier = Modifier.size(14.dp)) + Text(text = "$likeCount", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = textDark) + } + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) { + Icon(imageVector = Icons.Outlined.ChatBubbleOutline, contentDescription = "Comments", tint = TextMuted, modifier = Modifier.size(14.dp)) + Text(text = "$commentCount", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = textDark) } } } } } -// ============================================================ -// PREVIEW -// ============================================================ +@Composable +private fun StatCard(number: String, label: String, modifier: Modifier = Modifier, onClick: () -> Unit) { + Card( + modifier = modifier.clickable { onClick() }, + shape = RoundedCornerShape(12.dp), + colors = CardDefaults.cardColors(containerColor = Color(0xFFFAFAFA)), + border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) + ) { + Column( + modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = number, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = textDark) + Spacer(modifier = Modifier.height(2.dp)) + Text(text = label, fontSize = 10.sp, color = TextMuted, maxLines = 1, overflow = TextOverflow.Ellipsis) + } + } +} + @Preview(showBackground = true, showSystemUi = true) @Composable fun MyProfileScreenSuccessPreview() { DattingAPPTheme { val mockUserResponse = UserResponse( id = 1, - email_or_phone = "elena@example.com", + email_or_phone = "alex@example.com", status = "active", profile = ProfileData( - full_name = "Elena", - gender = "Female", - birth_date = "2002-04-12", + full_name = "Alex Rivera", + gender = "Male", + birth_date = "1998-04-12", city = "New York", county = null, country = "USA", - relationship_goal = "Long-term partnership", - bio = "\"Always looking for the best flat white in the city. Amateur photographer, professional dog petter...\"", + relationship_goal = "Long-term relationship / Serious dating", + bio = "Adventure seeker, dog lover, and amateur chef. Always hunting for the crispiest croissants in Brooklyn.", profile_image_url = "https://picsum.photos/300/300", - video_url = null, - interests = listOf("Photography", "Dogs", "Coffee Shop Lover", "Art Galleries") + video_url = "https://picsum.photos/300/300", + interests = listOf("📷 Photography", "🐶 Dogs", "☕ Coffee Culture", "✈️ Travel") ), created_at = "2026-01-01", posts = listOf( Post( id = 101, - content = "Enjoying the art exhibition!", + content = "Finding the perfect flat white and crisp croissants in Brooklyn today 🥐✨", post_type = "image", content_format = "standard", - feeling = "Happy", + feeling = "excited", location = null, - author = Author(1, "Elena", "New York", "USA", 98), - like_count = 12, - comment_count = 3, + author = Author(1, "Alex Rivera", "New York", "USA", 98), + like_count = 48, + comment_count = 12, liked_by_me = false, media = listOf( - Media(1, "image", "image/jpeg", 1024, 1, "https://picsum.photos/400/400?random=1"), - Media(2, "image", "image/jpeg", 1024, 2, "https://picsum.photos/400/400?random=2"), - Media(3, "image", "image/jpeg", 1024, 3, "https://picsum.photos/400/400?random=3") + Media(1, "image", "image/jpeg", 1024, 1, "https://picsum.photos/400/400?random=1") ), created_at = "2026-02-10" - ), - Post( - id = 102, - content = "Weekend coffee vibes ☕", - post_type = "image", - content_format = "standard", - feeling = "Relaxed", - location = null, - author = Author(1, "Elena", "New York", "USA", 98), - like_count = 25, - comment_count = 5, - liked_by_me = false, - media = listOf( - Media(4, "image", "image/jpeg", 1024, 1, "https://picsum.photos/400/400?random=4"), - Media(5, "image", "image/jpeg", 1024, 2, "https://picsum.photos/400/400?random=5"), - Media(6, "image", "image/jpeg", 1024, 3, "https://picsum.photos/400/400?random=6") - ), - created_at = "2026-02-12" ) ), liked_users = emptyList(), liked_by_users = emptyList(), matches = emptyList(), - liked_users_count = 42, - liked_by_users_count = 128, - matches_count = 18 + liked_users_count = 24, + liked_by_users_count = 12, + matches_count = 8 ) MyProfileScreen( diff --git a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt index 69820e4..49d7f62 100644 --- a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt +++ b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt @@ -16,7 +16,10 @@ import retrofit2.http.Part import com.example.dattingapp.homes.profile.UserResponse import com.example.dattingapp.homes.profile.LikedUserResponse import com.example.dattingapp.homes.profile.MatchListItem +import com.example.dattingapp.homes.profile.UpdateProfileRequest import retrofit2.http.DELETE +import retrofit2.http.PATCH +import retrofit2.http.PUT interface HomeApiService { @@ -163,4 +166,23 @@ interface HomeApiService { suspend fun unlikePost( @Path("post_id") postId: Int ): Response + + @PATCH("api/v1/explore/comments/{comment_id}") // use @PATCH if the backend needs it + suspend fun editComment( + @Path("comment_id") commentId: Int, + @Body request: CreateCommentRequest + ): Response + + + @PATCH("api/v1/explore/posts/{post_id}") + suspend fun updatePost( + @Path("post_id") postId: Int, + @Body body: CreatePostRequest + ): Response + + + @PATCH("api/v1/users/me") // @PUT if the backend needs it + suspend fun updateMyProfile( + @Body body: UpdateProfileRequest + ): Response } \ No newline at end of file diff --git a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt index 575365d..b976013 100644 --- a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt +++ b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt @@ -26,6 +26,7 @@ import java.io.FileOutputStream import java.lang.Exception import com.example.dattingapp.homes.profile.LikedUserResponse import com.example.dattingapp.homes.profile.MatchListItem +import com.example.dattingapp.homes.profile.UpdateProfileRequest import kotlinx.coroutines.Job import org.json.JSONObject @@ -666,6 +667,91 @@ class LikedViewModel : ViewModel() { } } } + + fun swipeUser(context: Context, type: String, userId: Int, direction: String) { + val appContext = context.applicationContext + val flow = when (type) { + "Likes" -> _likesUiState + "Liked" -> _likedUiState + else -> _matchesUiState + } + val before = flow.value as? LikedUiState.Success ?: return + val index = before.LikeData.indexOfFirst { it.id == userId } + if (index < 0) return + val removed = before.LikeData[index] + + // 1. Remove the card immediately + flow.value = LikedUiState.Success(before.LikeData.filter { it.id != userId }) + + // 2. Send the swipe; put the card back if it fails + viewModelScope.launch { + fun restore() { + val now = flow.value as? LikedUiState.Success ?: return + if (now.LikeData.any { it.id == userId }) return + val list = now.LikeData.toMutableList() + list.add(index.coerceAtMost(list.size), removed) + flow.value = LikedUiState.Success(list) + Toast.makeText(appContext, "Action failed. Please try again.", Toast.LENGTH_SHORT).show() + } + try { + val response = HomeNetworkModule + .getApiService(appContext) + .sendSwipe(SwipeRequest(userId, direction)) + if (!response.isSuccessful) { + Log.e("LikedSwipe", "HTTP ${response.code()}: ${response.errorBody()?.string()}") + restore() + } + } catch (e: kotlinx.coroutines.CancellationException) { + throw e + } catch (e: Exception) { + Log.e("LikedSwipe", "Swipe failed", e) + restore() + } + } + } +} +sealed interface EditProfileUiState { + object Idle : EditProfileUiState + object Saving : EditProfileUiState + object Saved : EditProfileUiState + data class Error(val message: String) : EditProfileUiState +} + +class EditProfileViewModel : ViewModel() { + private val _state = MutableStateFlow(EditProfileUiState.Idle) + val state: StateFlow = _state.asStateFlow() + + fun save(context: Context, request: UpdateProfileRequest) { + if (_state.value is EditProfileUiState.Saving) return + val appContext = context.applicationContext + + viewModelScope.launch { + _state.value = EditProfileUiState.Saving + try { + val response = HomeNetworkModule.getApiService(appContext).updateMyProfile(request) + if (response.isSuccessful) { + _state.value = EditProfileUiState.Saved + } else { + val raw = try { response.errorBody()?.string() } catch (e: Exception) { null } + Log.e("EditProfile", "HTTP ${response.code()}: $raw url=${response.raw().request.url}") + _state.value = EditProfileUiState.Error( + when (response.code()) { + 401 -> "Authentication failed. Please log in again." + 422 -> "Invalid data: ${raw ?: "check the fields"}" + else -> "Save failed (${response.code()})" + } + ) + } + } catch (e: kotlinx.coroutines.CancellationException) { + throw e + } catch (e: Exception) { + Log.e("EditProfile", "Save failed", e) + _state.value = EditProfileUiState.Error("Request failed: ${e.javaClass.simpleName}") + } + } + } + + fun reset() { _state.value = EditProfileUiState.Idle } } sealed interface ShowPostUiState { @@ -734,6 +820,61 @@ class ShowPostViewModel : ViewModel() { } } + fun updatePost(context: Context, postId: Int, newContent: String) { + val appContext = context.applicationContext + val text = newContent.trim() + if (text.isEmpty()) return + + // keep the existing post type and feeling, change only the text + val current = (_showPostUiState.value as? ShowPostUiState.Success)?.post + + viewModelScope.launch { + try { + val api = HomeNetworkModule.getApiService(appContext) + val response = api.updatePost( + postId, + CreatePostRequest( + content = text, + post_type = current?.postType ?: "text", + content_format = "plain", + feeling = current?.feeling, + location = null + ) + ) + + if (response.isSuccessful) { + // silent refresh: no spinner, the screen just shows the new text + val fresh = api.getPostById(postId) + if (fresh.isSuccessful && fresh.body() != null) { + val post = fresh.body()!! + PostInteractionStore.setFromServer( + post.id, post.liked_by_me, post.like_count, post.comment_count + ) + _showPostUiState.value = ShowPostUiState.Success(post) + } + } else { + val raw = try { response.errorBody()?.string() } catch (e: Exception) { null } + Log.e("UpdatePost", "Failed ${response.code()}: $raw url=${response.raw().request.url}") + Toast.makeText( + appContext, + when (response.code()) { + 403 -> "You can only edit your own posts." + 404 -> "Post not found." + 422 -> "Invalid post content." + else -> "Update failed (${response.code()})" + }, + Toast.LENGTH_LONG + ).show() + } + } catch (e: kotlinx.coroutines.CancellationException) { + throw e + } catch (e: Exception) { + Log.e("UpdatePost", "Exception while updating post", e) + Toast.makeText(appContext, "Update failed: ${e.javaClass.simpleName}", Toast.LENGTH_LONG).show() + } + } + } + fun addCommentLocallyAndServer(context: Context, postId: Int, content: String, currentUserName: String?) { val appContext = context.applicationContext @@ -799,6 +940,62 @@ class ShowPostViewModel : ViewModel() { } } + fun editComment(context: Context, postId: Int, commentId: Int, newContent: String) { + val appContext = context.applicationContext + val text = newContent.trim() + if (text.isEmpty()) return + + // Optimistic update: the new text shows immediately + val previousList = _commentsUiState.value + _commentsUiState.value = previousList.map { + if (it.id == commentId) it.copy(content = text) else it + } + + viewModelScope.launch { + try { + val response = HomeNetworkModule + .getApiService(appContext) + .editComment(commentId, CreateCommentRequest(content = text)) + + if (response.isSuccessful) { + fetchComments(appContext, postId) // sync with the server + } else { + val rawError = try { + response.errorBody()?.string() ?: "(empty error body)" + } catch (e: Exception) { + "(failed to read error body)" + } + val detail = try { + JSONObject(rawError).optString("detail") + } catch (e: Exception) { "" } + + Log.e( + "CommentError", + "Edit failed code=${response.code()} url=${response.raw().request.url} body=$rawError" + ) + + _commentsUiState.value = previousList // roll back + Toast.makeText( + appContext, + when (response.code()) { + 403 -> "You can only edit your own comments." + 404 -> "Comment not found." + 405 -> "Edit method not allowed (try PATCH instead of PUT)." + else -> "Edit failed (${response.code()}): ${detail.ifBlank { rawError }}" + }, + Toast.LENGTH_LONG + ).show() + } + } catch (e: kotlinx.coroutines.CancellationException) { + throw e + } catch (e: Exception) { + Log.e("CommentError", "Exception while editing commentId=$commentId", e) + _commentsUiState.value = previousList + Toast.makeText(appContext, "Edit failed: ${e.javaClass.simpleName}", Toast.LENGTH_LONG).show() + } + } + } + // NEW: no longer depends on ShowPostUiState.Success, so it works from the feed too fun toggleLikeLocallyAndServer(context: Context, postId: Int, currentlyLiked: Boolean) { val appContext = context.applicationContext diff --git a/app/src/main/java/com/example/dattingapp/homes/profile/UserResponseData.kt b/app/src/main/java/com/example/dattingapp/homes/profile/UserResponseData.kt index b9abd41..3e9b09e 100644 --- a/app/src/main/java/com/example/dattingapp/homes/profile/UserResponseData.kt +++ b/app/src/main/java/com/example/dattingapp/homes/profile/UserResponseData.kt @@ -1,5 +1,8 @@ package com.example.dattingapp.homes.profile +import kotlinx.serialization.Serializable + +@Serializable data class UserResponse( val id: Int, val email_or_phone: String, @@ -130,4 +133,15 @@ data class MatchedUser( val video_url: String? = null, val distance_km: Double? = null, val compatibility_percentage: Int? = null +) + +data class UpdateProfileRequest( + val full_name: String? = null, + val gender: String? = null, + val birth_date: String? = null, + val city: String? = null, + val country: String? = null, + val relationship_goal: String? = null, + val bio: String? = null, + val interests: List? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/example/dattingapp/homes/profile/ui/theme/Color.kt b/app/src/main/java/com/example/dattingapp/homes/profile/ui/theme/Color.kt new file mode 100644 index 0000000..137fa31 --- /dev/null +++ b/app/src/main/java/com/example/dattingapp/homes/profile/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.example.dattingapp.homes.profile.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/app/src/main/java/com/example/dattingapp/homes/profile/ui/theme/Theme.kt b/app/src/main/java/com/example/dattingapp/homes/profile/ui/theme/Theme.kt new file mode 100644 index 0000000..7221093 --- /dev/null +++ b/app/src/main/java/com/example/dattingapp/homes/profile/ui/theme/Theme.kt @@ -0,0 +1,58 @@ +package com.example.dattingapp.homes.profile.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun DattingAPPTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dattingapp/homes/profile/ui/theme/Type.kt b/app/src/main/java/com/example/dattingapp/homes/profile/ui/theme/Type.kt new file mode 100644 index 0000000..dae76b0 --- /dev/null +++ b/app/src/main/java/com/example/dattingapp/homes/profile/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.example.dattingapp.homes.profile.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt b/app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt index 174525c..6817019 100644 --- a/app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt +++ b/app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt @@ -30,4 +30,6 @@ interface AuthApiService { suspend fun login( @Body request: LoginRequest ): Response + + } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index def0123..cd19f07 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -92,6 +92,8 @@ LikeANdMatchActivity ShowPostingActivity MatchesActivity + EditProfileActivity + EditProfileActivty \ No newline at end of file