|
|
|
@ -1,15 +1,21 @@ |
|
|
|
package com.example.dattingapp.homes |
|
|
|
|
|
|
|
import android.content.Intent |
|
|
|
import androidx.compose.foundation.Image |
|
|
|
import androidx.compose.foundation.background |
|
|
|
import androidx.compose.foundation.border |
|
|
|
import androidx.compose.foundation.rememberScrollState |
|
|
|
import androidx.compose.foundation.verticalScroll |
|
|
|
import androidx.compose.foundation.clickable |
|
|
|
import androidx.compose.foundation.layout.* |
|
|
|
import androidx.compose.foundation.rememberScrollState |
|
|
|
import androidx.compose.foundation.shape.CircleShape |
|
|
|
import androidx.compose.foundation.shape.RoundedCornerShape |
|
|
|
import androidx.compose.foundation.verticalScroll |
|
|
|
import androidx.compose.material.icons.Icons |
|
|
|
import androidx.compose.material.icons.automirrored.filled.TextSnippet |
|
|
|
import androidx.compose.material.icons.filled.Check |
|
|
|
import androidx.compose.material.icons.filled.Favorite |
|
|
|
import androidx.compose.material.icons.filled.MusicNote |
|
|
|
import androidx.compose.material.icons.filled.TextSnippet |
|
|
|
import androidx.compose.material.icons.outlined.Edit |
|
|
|
import androidx.compose.material.icons.outlined.Settings |
|
|
|
import androidx.compose.material3.* |
|
|
|
@ -20,105 +26,114 @@ import androidx.compose.ui.draw.clip |
|
|
|
import androidx.compose.ui.graphics.Brush |
|
|
|
import androidx.compose.ui.graphics.Color |
|
|
|
import androidx.compose.ui.layout.ContentScale |
|
|
|
import androidx.compose.ui.platform.LocalContext |
|
|
|
import androidx.compose.ui.res.painterResource |
|
|
|
import androidx.compose.ui.text.font.FontWeight |
|
|
|
import androidx.compose.ui.text.style.TextOverflow |
|
|
|
import androidx.compose.ui.tooling.preview.Preview |
|
|
|
import androidx.compose.ui.unit.dp |
|
|
|
import androidx.compose.ui.unit.sp |
|
|
|
import coil.compose.AsyncImage |
|
|
|
import com.example.dattingapp.LikeANdMatchActivity |
|
|
|
import com.example.dattingapp.MasonryPostItem |
|
|
|
import com.example.dattingapp.R |
|
|
|
import com.example.dattingapp.ShowPostingActivity |
|
|
|
import com.example.dattingapp.StaggeredFriendPostsGrid |
|
|
|
import com.example.dattingapp.homes.profile.Author |
|
|
|
import com.example.dattingapp.homes.profile.Media |
|
|
|
import com.example.dattingapp.homes.profile.UserResponse |
|
|
|
import com.example.dattingapp.homes.profile.Post |
|
|
|
import com.example.dattingapp.homes.profile.ProfileData |
|
|
|
import com.example.dattingapp.ui.theme.BrandMagenta |
|
|
|
import com.example.dattingapp.ui.theme.BrandPurple |
|
|
|
import com.example.dattingapp.ui.theme.DattingAPPTheme |
|
|
|
import com.example.dattingapp.ui.theme.textDark |
|
|
|
import com.example.dattingapp.ui.theme.TextMuted |
|
|
|
import com.example.dattingapp.homes.profile.ProfileData |
|
|
|
import com.example.dattingapp.LikeANdMatchActivity |
|
|
|
import android.content.Intent |
|
|
|
import android.util.Log |
|
|
|
import androidx.compose.foundation.clickable |
|
|
|
import androidx.compose.ui.platform.LocalContext |
|
|
|
import androidx.core.content.ContextCompat.startActivity |
|
|
|
|
|
|
|
// ============================================================ |
|
|
|
// MY PROFILE SCREEN |
|
|
|
// ============================================================ |
|
|
|
|
|
|
|
import com.example.dattingapp.ui.theme.textDark |
|
|
|
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 { |
|
|
|
val formatter = DateTimeFormatter.ISO_LOCAL_DATE |
|
|
|
val birthDate = LocalDate.parse(birthDateStr.take(10), formatter) |
|
|
|
Period.between(birthDate, LocalDate.now()).years |
|
|
|
} catch (e: Exception) { |
|
|
|
null |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Composable |
|
|
|
fun MyProfileScreen( |
|
|
|
profileResponse : UserResponse?, // Change from MyProfileData? to MyProfileResponse? |
|
|
|
profileResponse: UserResponse?, |
|
|
|
isLoading: Boolean, |
|
|
|
errorMessage: String?, |
|
|
|
onRetry: () -> Unit = {}, |
|
|
|
onEditClick: () -> Unit = {}, |
|
|
|
onUpgradeClick: () -> Unit = {}, |
|
|
|
onSettingsClick: () -> Unit = {}, |
|
|
|
onLikesClick: () -> Unit={}, |
|
|
|
onLikedClick: () -> Unit={}, |
|
|
|
onMatchClick: () -> Unit={} |
|
|
|
|
|
|
|
|
|
|
|
onLikesClick: () -> Unit = {}, |
|
|
|
onLikedClick: () -> Unit = {}, |
|
|
|
onMatchClick: () -> Unit = {} |
|
|
|
) { |
|
|
|
// ======================================================== |
|
|
|
// FULL SCREEN SCROLL |
|
|
|
// ======================================================== |
|
|
|
val context = LocalContext.current |
|
|
|
val scrollState = rememberScrollState() |
|
|
|
|
|
|
|
val profile = profileResponse?.profile |
|
|
|
val age = calculateAge(profile?.birth_date) |
|
|
|
|
|
|
|
Column( |
|
|
|
modifier = Modifier.fillMaxSize().background(Color.White).verticalScroll(scrollState)) { |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxSize() |
|
|
|
.background(Color.White) |
|
|
|
.verticalScroll(scrollState) |
|
|
|
) { |
|
|
|
// ==================================================== |
|
|
|
// HEADER |
|
|
|
// HEADER & COVER IMAGE |
|
|
|
// ==================================================== |
|
|
|
Box( |
|
|
|
modifier = Modifier.fillMaxWidth().height(260.dp) |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth() |
|
|
|
.height(260.dp) |
|
|
|
) { |
|
|
|
// ================================================= |
|
|
|
// COVER IMAGE |
|
|
|
// ================================================= |
|
|
|
Image( |
|
|
|
painter = painterResource(id = R.drawable.locationpermissionbg), |
|
|
|
contentDescription = "Cover Banner", |
|
|
|
modifier = Modifier.fillMaxWidth().height(200.dp), |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth() |
|
|
|
.height(200.dp), |
|
|
|
contentScale = ContentScale.Crop |
|
|
|
) |
|
|
|
// ================================================= |
|
|
|
// SETTINGS BUTTON |
|
|
|
// ================================================= |
|
|
|
|
|
|
|
// SETTINGS BUTTON |
|
|
|
IconButton( |
|
|
|
onClick = onSettingsClick, |
|
|
|
modifier = Modifier.align(Alignment.TopEnd) |
|
|
|
modifier = Modifier |
|
|
|
.align(Alignment.TopEnd) |
|
|
|
.padding(top = 16.dp, end = 16.dp) |
|
|
|
.size(36.dp) |
|
|
|
.clip(CircleShape) |
|
|
|
.background(Color.Black.copy(alpha = 0.3f) |
|
|
|
) |
|
|
|
.background(Color.Black.copy(alpha = 0.3f)) |
|
|
|
) { |
|
|
|
|
|
|
|
Icon( |
|
|
|
|
|
|
|
imageVector = Icons.Outlined.Settings, |
|
|
|
|
|
|
|
contentDescription = "Settings", |
|
|
|
tint = Color.White, |
|
|
|
modifier = Modifier.size(20.dp) |
|
|
|
) |
|
|
|
} |
|
|
|
// ================================================= |
|
|
|
// PROFILE IMAGE |
|
|
|
// ================================================= |
|
|
|
|
|
|
|
// PROFILE IMAGE & VERIFIED BADGE |
|
|
|
Box( |
|
|
|
modifier = Modifier.align(Alignment.BottomStart) |
|
|
|
modifier = Modifier |
|
|
|
.align(Alignment.BottomStart) |
|
|
|
.padding(start = 24.dp) |
|
|
|
.size(110.dp) |
|
|
|
) { |
|
|
|
|
|
|
|
|
|
|
|
AsyncImage( |
|
|
|
// Access nested inner 'profile' first, then 'profileImageUrl' |
|
|
|
model = profileResponse?.profile?.profile_image_url, |
|
|
|
model = profile?.profile_image_url, |
|
|
|
contentDescription = "Profile Picture", |
|
|
|
contentScale = ContentScale.Crop, |
|
|
|
placeholder = painterResource(id = R.drawable.locationpermissionbg), |
|
|
|
@ -129,15 +144,13 @@ fun MyProfileScreen( |
|
|
|
.border(width = 4.dp, color = Color.White, shape = CircleShape) |
|
|
|
) |
|
|
|
|
|
|
|
// ================================================= |
|
|
|
// VERIFIED BADGE |
|
|
|
// ================================================= |
|
|
|
Box( |
|
|
|
modifier = Modifier.align(Alignment.BottomEnd).size(28.dp).clip(CircleShape) |
|
|
|
modifier = Modifier |
|
|
|
.align(Alignment.BottomEnd) |
|
|
|
.size(28.dp) |
|
|
|
.clip(CircleShape) |
|
|
|
.background(BrandMagenta) |
|
|
|
.border(width = 2.dp, color = Color.White, shape = CircleShape |
|
|
|
), |
|
|
|
|
|
|
|
.border(width = 2.dp, color = Color.White, shape = CircleShape), |
|
|
|
contentAlignment = Alignment.Center |
|
|
|
) { |
|
|
|
Icon( |
|
|
|
@ -149,90 +162,103 @@ fun MyProfileScreen( |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// ==================================================== |
|
|
|
// USER INFORMATION |
|
|
|
// USER INFORMATION SECTION |
|
|
|
// ==================================================== |
|
|
|
|
|
|
|
Column( |
|
|
|
|
|
|
|
modifier = Modifier.fillMaxWidth() |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth() |
|
|
|
.padding(horizontal = 24.dp, vertical = 8.dp) |
|
|
|
) { |
|
|
|
// ================================================= |
|
|
|
// NAME + EDIT |
|
|
|
// ================================================= |
|
|
|
|
|
|
|
// NAME, AGE & EDIT BUTTON |
|
|
|
Row( |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
|
verticalAlignment = Alignment.CenterVertically, |
|
|
|
horizontalArrangement = Arrangement.SpaceBetween |
|
|
|
) { |
|
|
|
Row( |
|
|
|
|
|
|
|
verticalAlignment = Alignment.CenterVertically, |
|
|
|
horizontalArrangement = Arrangement.spacedBy(8.dp) |
|
|
|
) { |
|
|
|
|
|
|
|
Text( |
|
|
|
text = profileResponse?.profile?.full_name?:"", |
|
|
|
text = if (age != null) "${profile?.full_name ?: ""}, $age" else (profile?.full_name ?: ""), |
|
|
|
fontSize = 22.sp, |
|
|
|
fontWeight = FontWeight.Bold, |
|
|
|
color = BrandMagenta |
|
|
|
color = textDark |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// EDIT BUTTON |
|
|
|
|
|
|
|
OutlinedButton( |
|
|
|
onClick = onEditClick, |
|
|
|
modifier = Modifier.height(30.dp), |
|
|
|
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp), |
|
|
|
border = ButtonDefaults |
|
|
|
.outlinedButtonBorder |
|
|
|
.copy(width = 1.dp) |
|
|
|
) { |
|
|
|
Spacer(modifier = Modifier.height(4.dp)) |
|
|
|
|
|
|
|
Icon( |
|
|
|
imageVector = Icons.Outlined.Edit, |
|
|
|
contentDescription = null, |
|
|
|
tint = BrandMagenta, |
|
|
|
modifier = Modifier.size(14.dp) |
|
|
|
// LOCATION & STATUS |
|
|
|
val locationText = listOfNotNull(profile?.city, profile?.country).joinToString(", ") |
|
|
|
if (locationText.isNotBlank()) { |
|
|
|
Text( |
|
|
|
text = "📍 $locationText", |
|
|
|
fontSize = 13.sp, |
|
|
|
color = TextMuted |
|
|
|
) |
|
|
|
Spacer(modifier = Modifier.height(6.dp)) |
|
|
|
} |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.width(4.dp)) |
|
|
|
|
|
|
|
// BIO CONTAINER |
|
|
|
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 = "Edit", |
|
|
|
fontSize = 12.sp, |
|
|
|
color = BrandMagenta, |
|
|
|
fontWeight = FontWeight.Bold |
|
|
|
text = profile.bio!!, |
|
|
|
fontSize = 13.sp, |
|
|
|
color = textDark, |
|
|
|
lineHeight = 18.sp, |
|
|
|
modifier = Modifier.padding(12.dp) |
|
|
|
) |
|
|
|
} |
|
|
|
Spacer(modifier = Modifier.height(10.dp)) |
|
|
|
} |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(4.dp)) |
|
|
|
// ================================================= |
|
|
|
// BIO |
|
|
|
// ================================================= |
|
|
|
|
|
|
|
Log.d("my id","${profileResponse?.id}" ) |
|
|
|
// INTERESTS CHIPS |
|
|
|
if (!profile?.interests.isNullOrEmpty()) { |
|
|
|
FlowRow( |
|
|
|
modifier = Modifier.fillMaxWidth(), |
|
|
|
horizontalArrangement = Arrangement.spacedBy(6.dp), |
|
|
|
verticalArrangement = Arrangement.spacedBy(6.dp) |
|
|
|
) { |
|
|
|
profile.interests.forEach { interest -> |
|
|
|
Surface( |
|
|
|
shape = RoundedCornerShape(16.dp), |
|
|
|
color = BrandMagenta.copy(alpha = 0.1f) |
|
|
|
) { |
|
|
|
Text( |
|
|
|
|
|
|
|
text = profileResponse?.profile?.bio?:"", |
|
|
|
fontSize = 13.sp, |
|
|
|
color = TextMuted, |
|
|
|
lineHeight = 18.sp |
|
|
|
text = interest, |
|
|
|
color = BrandMagenta, |
|
|
|
fontSize = 12.sp, |
|
|
|
fontWeight = FontWeight.Medium, |
|
|
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(12.dp)) |
|
|
|
|
|
|
|
// ==================================================== |
|
|
|
// PREMIUM BANNER |
|
|
|
// ==================================================== |
|
|
|
Box( |
|
|
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp) |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth() |
|
|
|
.padding(horizontal = 16.dp) |
|
|
|
.clip(RoundedCornerShape(16.dp)) |
|
|
|
.background( |
|
|
|
brush = Brush.horizontalGradient( |
|
|
|
colors = listOf(BrandMagenta, BrandPurple) |
|
|
|
) |
|
|
|
) |
|
|
|
.background(brush = Brush.horizontalGradient(colors = listOf(BrandMagenta, BrandPurple))) |
|
|
|
.padding(16.dp) |
|
|
|
) { |
|
|
|
Row( |
|
|
|
@ -241,42 +267,26 @@ fun MyProfileScreen( |
|
|
|
verticalAlignment = Alignment.CenterVertically |
|
|
|
) { |
|
|
|
Column { |
|
|
|
|
|
|
|
Text( |
|
|
|
text = "VibeCheck Premium", |
|
|
|
color = Color.White, |
|
|
|
fontSize = 16.sp, |
|
|
|
fontWeight = FontWeight.Bold |
|
|
|
) |
|
|
|
|
|
|
|
Spacer( |
|
|
|
modifier = Modifier.height(2.dp) |
|
|
|
) |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(2.dp)) |
|
|
|
Text( |
|
|
|
|
|
|
|
text = "Unlock unlimited likes & more", |
|
|
|
color = Color.White.copy(alpha = 0.8f), |
|
|
|
fontSize = 12.sp |
|
|
|
) |
|
|
|
} |
|
|
|
// UPGRADE BUTTON |
|
|
|
|
|
|
|
Button( |
|
|
|
|
|
|
|
onClick = onUpgradeClick, |
|
|
|
colors = ButtonDefaults.buttonColors( |
|
|
|
containerColor = Color.White |
|
|
|
), |
|
|
|
colors = ButtonDefaults.buttonColors(containerColor = Color.White), |
|
|
|
shape = RoundedCornerShape(20.dp), |
|
|
|
contentPadding = PaddingValues( |
|
|
|
horizontal = 14.dp, |
|
|
|
vertical = 4.dp |
|
|
|
) |
|
|
|
contentPadding = PaddingValues(horizontal = 14.dp, vertical = 4.dp) |
|
|
|
) { |
|
|
|
|
|
|
|
Text( |
|
|
|
|
|
|
|
text = "Upgrade", |
|
|
|
color = BrandMagenta, |
|
|
|
fontSize = 12.sp, |
|
|
|
@ -286,211 +296,411 @@ fun MyProfileScreen( |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Spacer( |
|
|
|
modifier = Modifier.height(16.dp) |
|
|
|
) |
|
|
|
Spacer(modifier = Modifier.height(16.dp)) |
|
|
|
|
|
|
|
// ==================================================== |
|
|
|
// STATISTICS |
|
|
|
// STATISTICS CARDS |
|
|
|
// ==================================================== |
|
|
|
|
|
|
|
Row( |
|
|
|
|
|
|
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth() |
|
|
|
.padding(horizontal = 16.dp), |
|
|
|
horizontalArrangement = Arrangement.spacedBy(8.dp) |
|
|
|
) { |
|
|
|
|
|
|
|
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") // ← e.g. "like", "liked", or "matches" |
|
|
|
// putExtra("USER_ID", userId.toString()) // ← if you also need a user id |
|
|
|
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") // ← e.g. "like", "liked", or "matches" |
|
|
|
// putExtra("USER_ID", userId.toString()) // ← if you also need a user id |
|
|
|
putExtra("TYPE", "Liked") |
|
|
|
} |
|
|
|
context.startActivity(intent) |
|
|
|
onLikesClick() |
|
|
|
onLikedClick() |
|
|
|
} |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// StatCard( |
|
|
|
// number = "Top", |
|
|
|
// label = "Match", |
|
|
|
// numberColor = BrandMagenta, |
|
|
|
// modifier = Modifier.weight(1f) |
|
|
|
// ) |
|
|
|
StatCard( |
|
|
|
number = profileResponse?.matches_count?.toString() ?: "0", |
|
|
|
label = "Matches", |
|
|
|
modifier = Modifier.weight(1f), |
|
|
|
onClick = { |
|
|
|
val intent = Intent(context, LikeANdMatchActivity::class.java).apply { |
|
|
|
putExtra("TYPE", "Matches") // ← e.g. "like", "liked", or "matches" |
|
|
|
// putExtra("USER_ID", userId.toString()) // ← if you also need a user id |
|
|
|
putExtra("TYPE", "Matches") |
|
|
|
} |
|
|
|
context.startActivity(intent) |
|
|
|
onLikesClick() |
|
|
|
onMatchClick() |
|
|
|
} |
|
|
|
) |
|
|
|
} |
|
|
|
Spacer( |
|
|
|
modifier = Modifier.height(16.dp) |
|
|
|
) |
|
|
|
|
|
|
|
ProfileMediaGrid() |
|
|
|
Spacer(modifier = Modifier.height(20.dp)) |
|
|
|
|
|
|
|
Spacer( |
|
|
|
modifier = Modifier.height(30.dp) |
|
|
|
// ==================================================== |
|
|
|
// ALREADY POSTED / DYNAMIC POSTS SECTION |
|
|
|
// ==================================================== |
|
|
|
Text( |
|
|
|
text = "Posted", |
|
|
|
fontSize = 18.sp, |
|
|
|
fontWeight = FontWeight.Bold, |
|
|
|
color = textDark, |
|
|
|
modifier = Modifier.padding(horizontal = 16.dp) |
|
|
|
) |
|
|
|
Spacer(modifier = Modifier.height(10.dp)) |
|
|
|
|
|
|
|
ProfilePostsGrid(posts = profileResponse?.posts ?: emptyList()) |
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(30.dp)) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ============================================================ |
|
|
|
// PROFILE MEDIA GRID |
|
|
|
// STAT CARD |
|
|
|
// ============================================================ |
|
|
|
|
|
|
|
@Composable |
|
|
|
private fun ProfileMediaGrid() { |
|
|
|
|
|
|
|
private fun StatCard( |
|
|
|
number: String, |
|
|
|
label: String, |
|
|
|
modifier: Modifier = Modifier, |
|
|
|
numberColor: Color = textDark, |
|
|
|
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(2.dp) |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth() |
|
|
|
.padding(vertical = 8.dp), |
|
|
|
horizontalAlignment = Alignment.CenterHorizontally |
|
|
|
) { |
|
|
|
// 6 rows × 3 columns = 18 images |
|
|
|
|
|
|
|
repeat(6) { rowIndex -> |
|
|
|
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 |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Row( |
|
|
|
modifier = Modifier.fillMaxWidth() |
|
|
|
) { |
|
|
|
|
|
|
|
repeat(3) { columnIndex -> |
|
|
|
// ============================================================ |
|
|
|
// STAGGERED PROFILE POSTS GRID (Matches your custom Masonry layout) |
|
|
|
// ============================================================ |
|
|
|
|
|
|
|
val imageIndex = rowIndex * 3 + columnIndex |
|
|
|
@Composable |
|
|
|
private fun ProfilePostsGrid(posts: List<Post>) { |
|
|
|
if (posts.isEmpty()) { |
|
|
|
Box( |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth() |
|
|
|
.height(120.dp), |
|
|
|
contentAlignment = Alignment.Center |
|
|
|
) { |
|
|
|
Text(text = "No posts yet", color = TextMuted, fontSize = 14.sp) |
|
|
|
} |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// Distribute posts into two columns |
|
|
|
val columns = remember(posts) { |
|
|
|
val col1 = mutableListOf<Post>() |
|
|
|
val col2 = mutableListOf<Post>() |
|
|
|
|
|
|
|
Image( |
|
|
|
painter = painterResource( |
|
|
|
id = R.drawable.locationpermissionbg |
|
|
|
), |
|
|
|
posts.forEachIndexed { index, post -> |
|
|
|
if (index % 2 == 0) { |
|
|
|
col1.add(post) |
|
|
|
} else { |
|
|
|
col2.add(post) |
|
|
|
} |
|
|
|
} |
|
|
|
Pair(col1, col2) |
|
|
|
} |
|
|
|
|
|
|
|
contentDescription = "Profile Image $imageIndex", |
|
|
|
modifier = Modifier.weight(1f).aspectRatio(1f).padding(1.dp), |
|
|
|
contentScale = ContentScale.Crop |
|
|
|
) |
|
|
|
Row( |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxWidth() |
|
|
|
.padding(horizontal = 16.dp), |
|
|
|
horizontalArrangement = Arrangement.spacedBy(6.dp) |
|
|
|
) { |
|
|
|
// Column 1 |
|
|
|
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 2 |
|
|
|
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 |
|
|
|
MasonryProfilePostItem(post = post, aspectRatio = aspectRatio) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ============================================================ |
|
|
|
// STAT CARD |
|
|
|
// ============================================================ |
|
|
|
|
|
|
|
@Composable |
|
|
|
private fun StatCard( |
|
|
|
number: String, |
|
|
|
label: String, |
|
|
|
modifier: Modifier = Modifier, |
|
|
|
numberColor: Color = textDark, |
|
|
|
onClick:()->Unit |
|
|
|
|
|
|
|
) { |
|
|
|
private fun MasonryProfilePostItem(post: Post, aspectRatio: Float) { |
|
|
|
val context = LocalContext.current |
|
|
|
val primaryMedia = post.media.firstOrNull()?.url |
|
|
|
val isImage = post.post_type.equals("image", ignoreCase = true) || !primaryMedia.isNullOrEmpty() |
|
|
|
|
|
|
|
Card( |
|
|
|
modifier = modifier.clickable { onClick() }, |
|
|
|
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) |
|
|
|
} |
|
|
|
context.startActivity(intent) |
|
|
|
} |
|
|
|
) { |
|
|
|
Box( |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxSize() |
|
|
|
) { |
|
|
|
if (isImage && !primaryMedia.isNullOrEmpty()) { |
|
|
|
AsyncImage( |
|
|
|
model = primaryMedia, |
|
|
|
contentDescription = "Post Image", |
|
|
|
contentScale = ContentScale.Crop, |
|
|
|
modifier = Modifier.fillMaxSize() |
|
|
|
) |
|
|
|
|
|
|
|
colors = CardDefaults.cardColors( |
|
|
|
containerColor = Color(0xFFFAFAFA) |
|
|
|
), |
|
|
|
// Dark Gradient Scrim for text readability over images |
|
|
|
Box( |
|
|
|
modifier = Modifier |
|
|
|
.fillMaxSize() |
|
|
|
.background( |
|
|
|
Brush.verticalGradient( |
|
|
|
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.65f)), |
|
|
|
startY = 100f |
|
|
|
) |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
border = |
|
|
|
CardDefaults.outlinedCardBorder() |
|
|
|
.copy(width = 0.5.dp) |
|
|
|
// Image metadata overlay |
|
|
|
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 |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
// Text / Status Card Design |
|
|
|
Column( |
|
|
|
modifier = Modifier.fillMaxWidth().padding(vertical = 5.dp), |
|
|
|
horizontalAlignment = Alignment.CenterHorizontally |
|
|
|
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 = number, |
|
|
|
fontSize = 16.sp, |
|
|
|
fontWeight = FontWeight.Bold, |
|
|
|
color = numberColor |
|
|
|
text = post.post_type.uppercase(), |
|
|
|
color = Color.White.copy(alpha = 0.9f), |
|
|
|
fontSize = 7.sp, |
|
|
|
fontWeight = FontWeight.ExtraBold |
|
|
|
) |
|
|
|
Spacer( |
|
|
|
modifier = Modifier.height(2.dp) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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 |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Text( |
|
|
|
text = label, |
|
|
|
text = post.content ?: "", |
|
|
|
color = Color.White, |
|
|
|
fontSize = 11.sp, |
|
|
|
color = TextMuted |
|
|
|
fontWeight = FontWeight.SemiBold, |
|
|
|
maxLines = 3, |
|
|
|
overflow = TextOverflow.Ellipsis, |
|
|
|
lineHeight = 14.sp |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
// Footer Likes |
|
|
|
Row( |
|
|
|
verticalAlignment = Alignment.CenterVertically, |
|
|
|
horizontalArrangement = Arrangement.spacedBy(2.dp) |
|
|
|
) { |
|
|
|
Icon( |
|
|
|
imageVector = Icons.Default.Favorite, |
|
|
|
contentDescription = null, |
|
|
|
tint = Color.White.copy(alpha = 0.8f), |
|
|
|
modifier = Modifier.size(9.dp) |
|
|
|
) |
|
|
|
Text( |
|
|
|
text = "${post.like_count}", |
|
|
|
color = Color.White.copy(alpha = 0.8f), |
|
|
|
fontSize = 8.sp |
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ============================================================ |
|
|
|
// PREVIEW |
|
|
|
// PREVIEWS |
|
|
|
// ============================================================ |
|
|
|
|
|
|
|
@Preview(showBackground = true, showSystemUi = true) |
|
|
|
@Composable |
|
|
|
fun MyProfileScreenSuccessPreview() { |
|
|
|
DattingAPPTheme { |
|
|
|
// Mock UserResponse matching your backend model hierarchy |
|
|
|
// Mock UserResponse matching your backend model structure |
|
|
|
val mockUserResponse = UserResponse( |
|
|
|
id = 1, |
|
|
|
email_or_phone = "alex.dev@example.com", |
|
|
|
email_or_phone = "elena@example.com", |
|
|
|
status = "active", |
|
|
|
profile = ProfileData( |
|
|
|
full_name = "Alex Morgan", |
|
|
|
gender = "Male", |
|
|
|
birth_date = "1995-05-15", |
|
|
|
full_name = "Elena", |
|
|
|
gender = "Female", |
|
|
|
birth_date = "2002-04-12", // 24 years old |
|
|
|
city = "New York", |
|
|
|
county = "Manhattan", |
|
|
|
county = null, |
|
|
|
country = "USA", |
|
|
|
relationship_goal = "Long-term partnership", |
|
|
|
bio = "Adventure seeker, dog lover, and amateur chef. Let's find a vibe!", |
|
|
|
bio = "\"Always looking for the best flat white in the city. Amateur photographer, professional dog petter...\"", |
|
|
|
profile_image_url = "https://picsum.photos/300/300", |
|
|
|
video_url = null, |
|
|
|
interests = listOf("Hiking", "Cooking", "Photography") |
|
|
|
interests = listOf("Photography", "Dogs", "Coffee Shop Lover", "Art Galleries") |
|
|
|
), |
|
|
|
created_at = "2026-01-01", |
|
|
|
posts = emptyList(), |
|
|
|
posts = listOf( |
|
|
|
Post( |
|
|
|
id = 101, |
|
|
|
content = "Enjoying the art exhibition!", |
|
|
|
post_type = "image", |
|
|
|
content_format = "standard", |
|
|
|
feeling = "Happy", |
|
|
|
location = null, |
|
|
|
author = Author(1, "Elena", "New York", "USA", 98), |
|
|
|
like_count = 12, |
|
|
|
comment_count = 3, |
|
|
|
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") |
|
|
|
), |
|
|
|
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 = 24, |
|
|
|
liked_by_users_count = 12, |
|
|
|
matches_count = 8 |
|
|
|
liked_users_count = 42, |
|
|
|
liked_by_users_count = 128, |
|
|
|
matches_count = 18 |
|
|
|
) |
|
|
|
|
|
|
|
MyProfileScreen( |
|
|
|
|