This is a dating app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

681 lines
26 KiB

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.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.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.Settings
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.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.R
import com.example.dattingapp.ShowPostingActivity
import com.example.dattingapp.homes.networkandapi.PostInteractionStore // NEW
import com.example.dattingapp.homes.profile.Author
import com.example.dattingapp.homes.profile.Media
import com.example.dattingapp.homes.profile.Post
import com.example.dattingapp.homes.profile.ProfileData
import com.example.dattingapp.homes.profile.UserResponse
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.TextMuted
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?,
isLoading: Boolean,
errorMessage: String?,
onRetry: () -> Unit = {},
onEditClick: () -> Unit = {},
onUpgradeClick: () -> Unit = {},
onSettingsClick: () -> Unit = {},
onLikesClick: () -> Unit = {},
onLikedClick: () -> Unit = {},
onMatchClick: () -> Unit = {}
) {
val context = LocalContext.current
val scrollState = rememberScrollState()
val profile = profileResponse?.profile
val age = calculateAge(profile?.birth_date)
// NEW: posts deleted anywhere in the app
val deletedIds by PostInteractionStore.deletedIds.collectAsState()
val visiblePosts = (profileResponse?.posts ?: emptyList()).filter { it.id !in deletedIds }
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
.verticalScroll(scrollState)
) {
// ====================================================
// HEADER & COVER IMAGE
// ====================================================
Box(
modifier = Modifier
.fillMaxWidth()
.height(260.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)
) {
AsyncImage(
model = profile?.profile_image_url,
contentDescription = "Profile Picture",
contentScale = ContentScale.Crop,
placeholder = painterResource(id = R.drawable.locationpermissionbg),
error = painterResource(id = R.drawable.locationpermissionbg),
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
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = if (age != null) "${profile?.full_name ?: ""}, $age" else (profile?.full_name ?: ""),
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
color = textDark
)
}
}
Spacer(modifier = Modifier.height(4.dp))
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))
}
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(10.dp))
}
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 = 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)
.clip(RoundedCornerShape(16.dp))
.background(brush = Brush.horizontalGradient(colors = listOf(BrandMagenta, BrandPurple)))
.padding(16.dp)
) {
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))
Text(
text = "Unlock unlimited likes & more",
color = Color.White.copy(alpha = 0.8f),
fontSize = 12.sp
)
}
Button(
onClick = onUpgradeClick,
colors = ButtonDefaults.buttonColors(containerColor = Color.White),
shape = RoundedCornerShape(20.dp),
contentPadding = PaddingValues(horizontal = 14.dp, vertical = 4.dp)
) {
Text(
text = "Upgrade",
color = BrandMagenta,
fontSize = 12.sp,
fontWeight = FontWeight.Bold
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// ====================================================
// STATISTICS CARDS
// ====================================================
Row(
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")
}
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()
}
)
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")
}
context.startActivity(intent)
onMatchClick()
}
)
}
Spacer(modifier = Modifier.height(20.dp))
// ====================================================
// POSTED 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 = visiblePosts) // CHANGED: deleted posts are filtered out
Spacer(modifier = Modifier.height(30.dp))
}
}
// ============================================================
// STAT CARD
// ============================================================
@Composable
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(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
)
}
}
}
// ============================================================
// STAGGERED PROFILE POSTS GRID
// ============================================================
@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
}
val columns = remember(posts) {
val col1 = mutableListOf<Post>()
val col2 = mutableListOf<Post>()
posts.forEachIndexed { index, post ->
if (index % 2 == 0) col1.add(post) else col2.add(post)
}
Pair(col1, col2)
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(6.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
MasonryProfilePostItem(post = post, aspectRatio = aspectRatio)
}
}
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)
}
}
}
}
@Composable
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(
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()
)
Box(
modifier = Modifier
.fillMaxSize()
.background(
Brush.verticalGradient(
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.65f)),
startY = 100f
)
)
)
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 {
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(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 = post.content ?: "",
color = Color.White,
fontSize = 11.sp,
fontWeight = FontWeight.SemiBold,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
lineHeight = 14.sp
)
}
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
// ============================================================
@Preview(showBackground = true, showSystemUi = true)
@Composable
fun MyProfileScreenSuccessPreview() {
DattingAPPTheme {
val mockUserResponse = UserResponse(
id = 1,
email_or_phone = "elena@example.com",
status = "active",
profile = ProfileData(
full_name = "Elena",
gender = "Female",
birth_date = "2002-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...\"",
profile_image_url = "https://picsum.photos/300/300",
video_url = null,
interests = listOf("Photography", "Dogs", "Coffee Shop Lover", "Art Galleries")
),
created_at = "2026-01-01",
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 = 42,
liked_by_users_count = 128,
matches_count = 18
)
MyProfileScreen(
profileResponse = mockUserResponse,
isLoading = false,
errorMessage = null
)
}
}