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