Compare commits

...

2 Commits

Author SHA1 Message Date
osman160128 58b14651e8 feed 2 weeks ago
osman160128 fbd1f81c9a friend profile update 2 weeks ago
  1. 135
      app/src/main/java/com/example/dattingapp/FriendProfile.kt
  2. 68
      app/src/main/java/com/example/dattingapp/LikeANdMatchActivity.kt
  3. 13
      app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt
  4. 59
      app/src/main/java/com/example/dattingapp/ShowPostingActivity.kt
  5. 4
      app/src/main/java/com/example/dattingapp/dataclass/DataClass.kt
  6. 58
      app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt
  7. 18
      app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt
  8. 668
      app/src/main/java/com/example/dattingapp/homes/MyProfile.kt
  9. 11
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt
  10. 48
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt
  11. 23
      app/src/main/java/com/example/dattingapp/homes/networkandapi/TokenManager.kt
  12. 4
      app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt
  13. 8
      app/src/main/java/com/example/dattingapp/registration/RegistrationScreen.kt
  14. 2
      app/src/main/java/com/example/dattingapp/ui/theme/Color.kt

135
app/src/main/java/com/example/dattingapp/FriendProfile.kt

@ -31,6 +31,7 @@ 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
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -51,6 +52,7 @@ 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
@ -82,6 +84,9 @@ class FriendProfile : ComponentActivity() {
}
}
when (val state = profileUiState) {
is FriendProfileUiState.Loading -> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
@ -120,6 +125,42 @@ fun FriendProfileScreen(
onStarClick: () -> Unit = {},
onCancelClick: () -> Unit = {}
) {
val likedViewModel: LikedViewModel = viewModel()
// ✅ collect each state separately
val likesState by likedViewModel.likesUiState.collectAsState() // who liked me
val likedState by likedViewModel.likedUiState.collectAsState() // I liked
val matchesState by likedViewModel.matchesUiState.collectAsState() // matches
// ✅ load all three lists once
LaunchedEffect(Unit) {
likedViewModel.likedResponse(context, "Likes")
likedViewModel.likedResponse(context, "Liked")
likedViewModel.likedResponse(context, "Matches")
}
// ✅ turn each into a set of ids
val iLikedIds = (likesState as? LikedUiState.Success)
?.LikeData?.map { it.id }?.toSet() ?: emptySet()
val likedMeIds = (likedState as? LikedUiState.Success)
?.LikeData?.map { it.id }?.toSet() ?: emptySet()
val matchIds = (matchesState as? LikedUiState.Success)
?.LikeData?.map { it.id }?.toSet() ?: emptySet()
// ✅ the three booleans for the button row
val isMatch = friendProfileData.id in matchIds
val heLikedMe = friendProfileData.id in likedMeIds
val iLikedHim = friendProfileData.id in iLikedIds
// ✅ add this at the top of FriendProfileScreen, next to your other states
var localLiked by rememberSaveable { mutableStateOf(false) }
// Like is disabled if: I liked them before (server) OR I just clicked Like now
val likeDisabled = iLikedHim || localLiked
val age = remember(friendProfileData.birth_date) {
calculateAge(friendProfileData.birth_date)
}
@ -269,7 +310,7 @@ fun FriendProfileScreen(
)
}
// Action Buttons Row
//action button
Row(
modifier = Modifier
.fillMaxWidth()
@ -277,86 +318,124 @@ fun FriendProfileScreen(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Button(
// Message — card style, no background color
// Message — enabled only when matched, grayed out otherwise
Card(
onClick = onMessageClick,
enabled = isMatch, // ✅ disabled when not matched
modifier = Modifier
.weight(1f)
.height(48.dp)
.background(
brush = gradientBrush,
shape = RoundedCornerShape(10.dp)
),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = Color.White
.height(48.dp),
shape = RoundedCornerShape(10.dp),
colors = CardDefaults.cardColors(
containerColor = if (isMatch) Color.White else Color(0xFFF0F0F0),
disabledContainerColor = Color(0xFFF0F0F0)
),
shape = RoundedCornerShape(10.dp)
elevation = CardDefaults.cardElevation(
defaultElevation = if (isMatch) 4.dp else 0.dp
)
) {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.ChatBubble,
contentDescription = "Message",
tint = if (isMatch) textDark else Color(0xFFBDBDBD), // gray when disabled
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Message",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
fontWeight = FontWeight.Bold,
color = if (isMatch) textDark else Color(0xFFBDBDBD) // gray when disabled
)
}
}
IconButton(
// ❤️ Like — red ICON when the user liked ME, normal otherwise
// hidden when matched
// ❤️ Like — RED background if user liked me, DISABLED if I liked them
Card(
onClick = {
onLikeClick()
handleLikeAndDislikeAction(context, "right", friendProfileData, swipeViewModel)
localLiked = true // ✅ disable after first click
},
modifier = Modifier
.size(48.dp)
.background(color = purpleButtonColor, shape = RoundedCornerShape(10.dp))
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 = Color(0xFFE2C4FF),
tint = when {
heLikedMe -> Color.White // white heart on red background
likeDisabled -> Color(0xFFBDBDBD) // gray when disabled
else -> textDark // normal
},
modifier = Modifier.size(20.dp)
)
}
}
IconButton(
// Super Like — card style
Card(
onClick = {
onStarClick()
handleLikeAndDislikeAction(context, "right", friendProfileData, swipeViewModel)
},
modifier = Modifier
.size(48.dp)
.background(color = purpleButtonColor, shape = RoundedCornerShape(10.dp))
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.Star,
contentDescription = "Favorite",
tint = Color(0xFFE2C4FF),
tint = textDark,
modifier = Modifier.size(20.dp)
)
}
}
IconButton(
// Cancel — card style, always visible
// Cancel — clicking it re-enables the Like button
Card(
onClick = {
onCancelClick()
handleLikeAndDislikeAction(context, "left", friendProfileData, swipeViewModel)
localLiked = false // ✅ Like button works again
},
modifier = Modifier
.size(48.dp)
.background(color = purpleButtonColor, shape = RoundedCornerShape(10.dp))
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 = Color(0xFFE2C4FF),
tint = textDark,
modifier = Modifier.size(20.dp)
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// User Posts Grid Section below Action Row

68
app/src/main/java/com/example/dattingapp/LikeANdMatchActivity.kt

@ -15,6 +15,8 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material3.*
@ -54,17 +56,50 @@ class LikeANdMatchActivity : ComponentActivity() {
DattingAPPTheme {
val likedViewModel: LikedViewModel = viewModel()
val context = LocalContext.current
val type = intent.getStringExtra("TYPE") ?: "Likes"
// Collect state from ViewModel
val uiState by likedViewModel.likeUiState.collectAsState()
// Dynamically collect the correct StateFlow based on the 'type'
val uiState by when (type) {
"Likes" -> likedViewModel.likesUiState.collectAsState()
"Liked" -> likedViewModel.likedUiState.collectAsState()
else -> likedViewModel.matchesUiState.collectAsState()
}
// Trigger network call once when screen loads
LaunchedEffect(Unit) {
likedViewModel.likedResponse(context)
likedViewModel.likedResponse(context, type)
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
@OptIn(ExperimentalMaterial3Api::class)
TopAppBar(
title = {
// Display dynamic title based on the page type
Text(
text = when (type) {
"Likes" -> "My Likes"
"Liked" -> "Liked Me"
else -> "My Matches"
},
fontWeight = FontWeight.Bold,
fontSize = 18.sp
)
},
navigationIcon = {
IconButton(onClick = { finish() }) {
Icon(
imageVector = Icons.Default.ArrowBack,
contentDescription = "Back"
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface
)
)
}
) { innerPadding ->
Box(
modifier = Modifier
@ -78,7 +113,7 @@ class LikeANdMatchActivity : ComponentActivity() {
}
is LikedUiState.Success -> {
ProfileGrid(
context =context,
context = context,
likedResponse = state.LikeData,
isLoading = false,
errorMessage = null
@ -95,7 +130,7 @@ class LikeANdMatchActivity : ComponentActivity() {
color = MaterialTheme.colorScheme.error,
fontWeight = FontWeight.Medium
)
Button(onClick = { likedViewModel.likedResponse(context) }) {
Button(onClick = { likedViewModel.likedResponse(context, type) }) {
Text("Retry")
}
}
@ -107,7 +142,6 @@ class LikeANdMatchActivity : ComponentActivity() {
}
}
}
// 1. Define ProfileCard FIRST so ProfileGrid can reference it without issue
@Composable
fun ProfileCard(context: Context, likedProfile: LikedUserResponse, modifier: Modifier = Modifier,
@ -120,7 +154,6 @@ fun ProfileCard(context: Context, likedProfile: LikedUserResponse, modifier: Mod
modifier = modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Box(
@ -128,6 +161,14 @@ fun ProfileCard(context: Context, likedProfile: LikedUserResponse, modifier: Mod
.fillMaxWidth()
.height(180.dp)
.clip(RoundedCornerShape(12.dp))
.clickable {
// Create an intent to launch your target activity (e.g., FriendProfileActivity)
val intent = Intent(context, FriendProfile::class.java).apply {
putExtra("USER_ID", likedProfile.id) // Pass the user ID here
}
context.startActivity(intent)
onNameClick() }
) {
// Use profile_image_url or fallback to the first media item
AsyncImage(
@ -182,17 +223,10 @@ fun ProfileCard(context: Context, likedProfile: LikedUserResponse, modifier: Mod
Column(
modifier = Modifier
.align(Alignment.BottomStart)
.clickable {
// Create an intent to launch your target activity (e.g., FriendProfileActivity)
val intent = Intent(context, FriendProfile::class.java).apply {
putExtra("USER_ID", likedProfile.id) // Pass the user ID here
}
context.startActivity(intent)
onNameClick() } // Makes just this text section clickable
// Makes just this text section clickable
.padding(8.dp)
){
val displayName = likedProfile.full_name.ifBlank { "User" }
val displayName = likedProfile.full_name?.ifBlank { "User" }
val age = remember(likedProfile.birth_date) {
calculateAge(likedProfile.birth_date)
}
@ -201,7 +235,7 @@ fun ProfileCard(context: Context, likedProfile: LikedUserResponse, modifier: Mod
Log.d("user","$likedProfile.id")
Text(
text = nameAndAge,
text = nameAndAge?:"",
color = Color.White,
fontWeight = FontWeight.Bold,
fontSize = 14.sp

13
app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt

@ -270,15 +270,20 @@ fun LoginScreenContent(
// Save retrieved tokens via TokenManager
val tokenManager = TokenManager.getInstance(context)
val id=loginData.user?.profile?.full_name
val id=loginData.user?.id
Log.d("useridlogin","$id")
tokenManager.saveTokens(
accessToken = loginData.accessToken ?: "",
refreshToken = loginData.refreshToken ?: "",
userId = loginData.user?.id ?: -1,
currenUserName = loginData.user?.profile?.full_name
refreshToken = loginData.refreshToken ?: ""
)
tokenManager.saveProfile(
userId = loginData.user?.id ?: -1,
currenUserName = loginData.user?.profile?.full_name,
profile_image_url=loginData.user?.profile?.profile_image_url
)
Toast.makeText(context, "Login successful!", Toast.LENGTH_SHORT).show()
onLoginSuccess()
} else {

59
app/src/main/java/com/example/dattingapp/ShowPostingActivity.kt

@ -1,4 +1,5 @@
package com.example.dattingapp
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@ -44,6 +45,7 @@ import com.example.dattingapp.homes.networkandapi.ShowPostUiState
import com.example.dattingapp.homes.networkandapi.ShowPostViewModel
import androidx.compose.material.icons.filled.Send
import androidx.compose.material3.CardDefaults
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -135,38 +137,52 @@ fun PostCard(
.padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
AsyncImage(
model = post.author?.profile_image_url,
contentDescription = "Post author image",
placeholder = painterResource(id = R.drawable.locationpermissionbg), // your existing placeholder
error = painterResource(id = R.drawable.locationpermissionbg),
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(Color.LightGray),
contentAlignment = Alignment.Center
) {
Text(text = post.author?.full_name?.take(1) ?: "U")
}
.clip(CircleShape),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.width(8.dp))
Column {
Row(verticalAlignment = Alignment.CenterVertically) {
val authorName = post.author?.full_name ?: "Anonymous"
val feelingText = post.feeling ?: ""
val context = LocalContext.current
// ✅ Clickable author name
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append("$authorName ")
text = authorName,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.clickable {
val intent = Intent(context, FriendProfile::class.java).apply {
putExtra("USER_ID", post.author?.id) // ← send the user id
}
context.startActivity(intent)
}
)
// Rest of the line, unchanged
if (feelingText.isNotEmpty()) {
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Normal)) {
append(" — feeling ")
}
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(feelingText)
}
}
},
fontSize = 16.sp
)
}
}
if (!post.location?.name.isNullOrEmpty()) {
Text(
@ -295,6 +311,7 @@ fun CommentSection(
val tokenManager = remember { TokenManager.getInstance(context) }
val currentUserName = remember { tokenManager.currentUserName() }
val currentProfileImage = remember { tokenManager.currentProfileImage() }
// ✅ get the logged-in user's id once
val currentUserId = remember { tokenManager.getUserId() }
@ -383,19 +400,16 @@ fun CommentSection(
.padding(vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
AsyncImage(
model = currentProfileImage,
contentDescription = "Post author image",
placeholder = painterResource(id = R.drawable.locationpermissionbg), // your existing placeholder
error = painterResource(id = R.drawable.locationpermissionbg),
modifier = Modifier
.size(32.dp)
.clip(CircleShape)
.background(Color.LightGray),
contentAlignment = Alignment.Center
) {
Text(
text = comment.author?.full_name?.take(1) ?: "U",
style = MaterialTheme.typography.bodySmall,
fontWeight = FontWeight.Bold
.clip(CircleShape),
contentScale = ContentScale.Crop
)
}
Spacer(modifier = Modifier.width(8.dp))
@ -513,6 +527,7 @@ fun ShowPostScreenPreview() {
full_name = "Alex Rivera",
city = "Shanghai",
country = "China",
profile_image_url=null,
compatibilityPercentage = 95
),
like_count = 12,
@ -542,6 +557,7 @@ fun ShowPostScreenPreview() {
full_name = "Sarah",
city = "Shanghai",
country = "China",
profile_image_url=null,
compatibilityPercentage = 100
),
createdAt = "2026-10-13T07:45:15"
@ -555,6 +571,7 @@ fun ShowPostScreenPreview() {
full_name = "David",
city = "Beijing",
country = "China",
profile_image_url=null,
compatibilityPercentage = 90
),
createdAt = "2026-10-13T08:04:48"

4
app/src/main/java/com/example/dattingapp/dataclass/DataClass.kt

@ -32,6 +32,10 @@ data class Author(
val full_name: String?,
val city: String?,
val country: String?,
@SerialName("profile_image_url")
val profile_image_url:String?,
@SerialName("compatibility_percentage") val compatibilityPercentage: Int? = 0
)

58
app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt

@ -30,6 +30,7 @@ import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@ -42,18 +43,21 @@ import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import coil.compose.AsyncImage
import com.example.dattingapp.R
import com.example.dattingapp.ShowPostingActivity
import com.example.dattingapp.dataclass.Author
import com.example.dattingapp.dataclass.Location
import com.example.dattingapp.dataclass.MediaItem
import com.example.dattingapp.dataclass.ShowPostResponse
import com.example.dattingapp.homes.networkandapi.PostResponse
import com.example.dattingapp.homes.networkandapi.TokenManager
import com.example.dattingapp.homes.profile.UserResponse
import com.example.dattingapp.ui.theme.DattingAPPTheme
import com.example.dattingapp.ui.theme.OverlayShadow
import com.example.dattingapp.ui.theme.TextPostTextColor
import com.example.dattingapp.ui.theme.lightChipBg
import com.example.dattingapp.ui.theme.primaryColor
import kotlinx.serialization.SerialName
// Data Models
@ -63,15 +67,6 @@ data class VibeCategory(
val isSelected: Boolean = false
)
data class EventCardData(
val id: String,
val title: String,
val description: String,
val imageUrl: String,
val tagLabel: String,
val isTrending: Boolean = false,
val tags: List<String> = emptyList()
)
data class UserMatch(
val id: String,
@ -131,6 +126,10 @@ fun VibeCheckHomeScreen(
private fun VibeCheckTopAppBar(
primaryColor: Color
) {
val context = LocalContext.current
val tokenManager = remember { TokenManager.getInstance(context) }
val currentUserName = remember { tokenManager.currentUserName() }
val currentProfileImage = remember { tokenManager.currentProfileImage() }
Row(
modifier = Modifier
.fillMaxWidth()
@ -146,17 +145,22 @@ private fun VibeCheckTopAppBar(
}
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
AsyncImage(
model = currentProfileImage,
contentDescription = "Post author image",
placeholder = painterResource(id = com.example.dattingapp.R.drawable.locationpermissionbg), // your existing placeholder
error = painterResource(id = R.drawable.locationpermissionbg),
modifier = Modifier
.size(32.dp)
.clip(CircleShape)
.background(Color.Gray)
.size(40.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.width(8.dp))
Spacer(modifier = Modifier.width(2.dp))
Text(
text = "VibeCheck",
text = currentUserName?:"",
color = primaryColor,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
@ -180,27 +184,32 @@ private fun SearchBarSection() {
value = searchText,
onValueChange = { searchText = it },
placeholder = {
Text(text = "Search", color = Color.Gray)
Text(text = "Search", color = Color.Gray, fontSize = 13.sp)
},
leadingIcon = {
Icon(
imageVector = Icons.Default.Search,
contentDescription = null,
tint = Color.Gray
tint = Color.Gray,
modifier = Modifier.size(18.dp)
)
},
trailingIcon = {
Row(modifier = Modifier.padding(end = 8.dp)) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Outlined.Image,
contentDescription = null,
tint = Color.Gray
tint = Color.Gray,
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Spacer(modifier = Modifier.width(6.dp))
Icon(
imageVector = Icons.Outlined.LocationOn,
contentDescription = null,
tint = Color.Gray
tint = Color.Gray,
modifier = Modifier.size(18.dp)
)
}
},
@ -211,12 +220,13 @@ private fun SearchBarSection() {
unfocusedBorderColor = Color.Transparent,
focusedBorderColor = Color.Transparent
),
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.fillMaxHeight()
)
}
}
@Composable
private fun VibeFilterChipsSection(
selectedColor: Color,
@ -628,6 +638,7 @@ private val mockFeed = listOf(
full_name = "Alex Rivera",
city = "Shanghai",
country = "China",
profile_image_url = null,
compatibilityPercentage = 95
),
like_count = 12,
@ -657,6 +668,7 @@ private val mockFeed = listOf(
full_name = "Sarah",
city = "Beijing",
country = "China",
profile_image_url = null,
compatibilityPercentage = 88
),
like_count = 0,

18
app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt

@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@ -205,12 +206,15 @@ private fun ProfileCardItem(
}
val defaultPainter = rememberVectorPainter(Icons.Default.Person)
if (!photoUrl.isNullOrBlank()) {
AsyncImage(
model = ImageRequest.Builder(context)
.data(photoUrl.takeIf { !it.isNullOrBlank() })
.data(photoUrl) // ← guaranteed non-null here
.apply {
if (!token.isNullOrBlank()) {
val authHeader = if (token.startsWith("Bearer ", ignoreCase = true)) token else "Bearer $token"
val authHeader =
if (token.startsWith("Bearer ", ignoreCase = true)) token
else "Bearer $token"
addHeader("Authorization", authHeader)
}
}
@ -218,7 +222,6 @@ private fun ProfileCardItem(
.build(),
placeholder = defaultPainter,
error = defaultPainter,
fallback = defaultPainter, // Shows Icons.Default.Person immediately when photoUrl is null/blank
contentDescription = "Profile Image",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop,
@ -226,6 +229,15 @@ private fun ProfileCardItem(
Log.e("CoilStatus", "Failed to load image", errorState.result.throwable)
}
)
} else {
// No URL at all → show the default directly, Coil never runs
Image(
painter = defaultPainter,
contentDescription = "Profile Image",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
}
// DARK GRADIENT OVERLAY
Box(

668
app/src/main/java/com/example/dattingapp/homes/MyProfile.kt

@ -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,104 +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),
@ -128,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(
@ -148,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(
@ -240,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,
@ -285,192 +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)
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 = { /* Optional: Handle Matches click */ }
onClick = {
val intent = Intent(context, LikeANdMatchActivity::class.java).apply {
putExtra("TYPE", "Liked")
}
context.startActivity(intent)
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 = { /* Optional: Handle Matches click */ }
)
onClick = {
val intent = Intent(context, LikeANdMatchActivity::class.java).apply {
putExtra("TYPE", "Matches")
}
context.startActivity(intent)
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
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
)
}
}
}
repeat(6) { rowIndex ->
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 = 12.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(

11
app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt

@ -69,9 +69,20 @@ interface HomeApiService {
): Response<UserResponse>
@GET("api/v1/users/me/liked")
suspend fun getLikeUsers(
): Response<List<LikedUserResponse>>
@GET("/api/v1/users/me/likes")
suspend fun getLikedUsers(
): Response<List<LikedUserResponse>>
@GET("/api/v1/matches")
suspend fun getMatches(
@Query("limit") limit: Int = 20,
@Query("offset") offset: Int = 0
): Response<List<LikedUserResponse>>
@GET("api/v1/explore/posts/{post_id}")
suspend fun getPostById(
@Path("post_id") postId: Int

48
app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt

@ -534,49 +534,62 @@ sealed interface LikedUiState {
class LikedViewModel : ViewModel() {
private val _likeUiState = MutableStateFlow<LikedUiState>(LikedUiState.Idle)
val likeUiState: StateFlow<LikedUiState> = _likeUiState.asStateFlow()
// ✅ three separate states, one per list type
private val _likedUiState = MutableStateFlow<LikedUiState>(LikedUiState.Idle) // users who liked ME
val likedUiState: StateFlow<LikedUiState> = _likedUiState.asStateFlow()
fun likedResponse(context: Context) {
if (_likeUiState.value is LikedUiState.Loading) return
private val _likesUiState = MutableStateFlow<LikedUiState>(LikedUiState.Idle) // users I liked
val likesUiState: StateFlow<LikedUiState> = _likesUiState.asStateFlow()
private val _matchesUiState = MutableStateFlow<LikedUiState>(LikedUiState.Idle) // matches
val matchesUiState: StateFlow<LikedUiState> = _matchesUiState.asStateFlow()
fun likedResponse(context: Context, type: String) {
val appContext = context.applicationContext
// ✅ pick which state this call writes to
val targetFlow = when (type) {
"Likes" -> _likesUiState
"Liked" -> _likedUiState
else -> _matchesUiState
}
if (targetFlow.value is LikedUiState.Loading) return
viewModelScope.launch {
_likeUiState.value = LikedUiState.Loading
targetFlow.value = LikedUiState.Loading
if (TokenManager.getInstance(appContext).getAccessToken().isNullOrBlank()) {
Log.e("MeetApi", "No access token in user_tokens preferences")
_likeUiState.value = LikedUiState.Error("No access token found. Please log in again.")
targetFlow.value = LikedUiState.Error("No access token found. Please log in again.")
return@launch
}
try {
// Returns CreatePostResponse directly
val createdPost = HomeNetworkModule
.getApiService(appContext)
.getLikedUsers()
val createdPost = when (type) {
"Likes" -> HomeNetworkModule.getApiService(appContext).getLikeUsers()
"Liked" -> HomeNetworkModule.getApiService(appContext).getLikedUsers()
else -> HomeNetworkModule.getApiService(appContext).getMatches(20, 10)
}
Log.d("CreatePostDebug", "Response Code: ${createdPost.code()}")
Log.d("CreatePostDebug", "$type Response Code: ${createdPost.code()}")
if (createdPost.isSuccessful && createdPost.body() != null) {
_likeUiState.value = LikedUiState.Success(createdPost.body()!!) // Use .body() here!
targetFlow.value = LikedUiState.Success(createdPost.body()!!)
} else {
_likeUiState.value = LikedUiState.Error("No post data received from the server.")
targetFlow.value = LikedUiState.Error("No data received from the server.")
}
} catch (e: HttpException) {
// Handle HTTP errors via HttpException automatically thrown by Retrofit
val message = when (e.code()) {
401 -> "Authentication failed. Please log in again."
403 -> "The server refused access to this profile."
404 -> "User profile was not found."
else -> "Request failed: HTTP ${e.code()}."
}
_likeUiState.value = LikedUiState.Error(message)
targetFlow.value = LikedUiState.Error(message)
} catch (e: Exception) {
Log.e("MeetApi", "Unexpected request failure", e)
_likeUiState.value = LikedUiState.Error("Request failed: ${e.javaClass.simpleName}")
targetFlow.value = LikedUiState.Error("Request failed: ${e.javaClass.simpleName}")
}
}
}
@ -653,6 +666,7 @@ class ShowPostViewModel : ViewModel() {
full_name = currentUserName ?: "You",
city = null,
country = null,
profile_image_url = null,
compatibilityPercentage = null
),
createdAt = "Just now"

23
app/src/main/java/com/example/dattingapp/homes/networkandapi/TokenManager.kt

@ -11,7 +11,13 @@ class TokenManager private constructor(context: Context) {
Context.MODE_PRIVATE
)
fun saveTokens(accessToken: String, refreshToken: String?,userId: Int,currenUserName:String?) {
private val prefProfile =
context.applicationContext.getSharedPreferences(
PREF_NAME,
Context.MODE_PRIVATE
)
fun saveTokens(accessToken: String, refreshToken: String?) {
val cleanToken = accessToken.trim()
.replace(
Regex("^Bearer\\s+", RegexOption.IGNORE_CASE),
@ -22,8 +28,17 @@ class TokenManager private constructor(context: Context) {
val saved = preferences.edit()
.putString(KEY_ACCESS_TOKEN, cleanToken)
.putString(KEY_REFRESH_TOKEN, refreshToken)
.commit()
// Do not log the token itself.
Log.d("TokenManager", "Tokens saved: $saved")
}
fun saveProfile(userId: Int,currenUserName:String?,profile_image_url:String?) {
val saved = preferences.edit()
.putInt("user_id", userId)
.putString("currenUserName", currenUserName)
.putString("profile_image_url", profile_image_url)
.commit()
// Do not log the token itself.
@ -32,11 +47,13 @@ class TokenManager private constructor(context: Context) {
fun getUserId(): Int {
return preferences.getInt("user_id", -1)
return prefProfile.getInt("user_id", -1)
}
fun currentUserName(): String? = preferences.getString("full_name", null)
fun currentUserName(): String? = prefProfile.getString("currenUserName", null)
fun currentProfileImage(): String? = prefProfile.getString("profile_image_url", null)
fun getAccessToken(): String? {
return preferences.getString(KEY_ACCESS_TOKEN, null)

4
app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt

@ -74,5 +74,7 @@ data class LoginUser(
data class LoginProfile(
@SerializedName("full_name")
val full_name: String? = null
val full_name: String? = null,
@SerializedName("profile_image_url")
val profile_image_url: String? = null
)

8
app/src/main/java/com/example/dattingapp/registration/RegistrationScreen.kt

@ -80,6 +80,7 @@ import android.content.pm.PackageManager
import android.location.LocationManager
import android.os.Looper
import android.Manifest
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
@ -100,6 +101,8 @@ import androidx.compose.material.icons.filled.LocationOn
// Permission check
import androidx.core.content.ContextCompat
import com.example.dattingapp.homes.networkandapi.TokenManager
class RegistrationScreen : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
@ -156,7 +159,12 @@ fun RegistrationFlowContainer(
when (val state = uiState) {
is RegistrationUiState.Success -> {
Toast.makeText(context, "Registration Successful!", Toast.LENGTH_SHORT).show()
val tokenManager = TokenManager.getInstance(context)
tokenManager.saveTokens(
accessToken = state.response.accessToken,
refreshToken = state.response.refreshToken
)
// Navigate to UploadProfileImageActivity and finish flow
val intent = Intent(context, UploadProfileImageActivity::class.java).apply {
putExtra("ACCESS_TOKEN", state.response.accessToken)

2
app/src/main/java/com/example/dattingapp/ui/theme/Color.kt

@ -113,7 +113,7 @@ colors = listOf(
Color(0xFF6B00B0) // Deep Purple
)
)
val purpleButtonColor = Color(0xFF6B00F5)
val purpleButtonColor = Color(0x236B00F5)
// Same post id always gives the same color
val OverlayShadow = Shadow(

Loading…
Cancel
Save