Browse Source

feed

master
osman160128 2 weeks ago
parent
commit
58b14651e8
  1. 76
      app/src/main/java/com/example/dattingapp/FriendProfile.kt
  2. 47
      app/src/main/java/com/example/dattingapp/LikeANdMatchActivity.kt
  3. 8
      app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt
  4. 55
      app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt
  5. 662
      app/src/main/java/com/example/dattingapp/homes/MyProfile.kt
  6. 8
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt
  7. 23
      app/src/main/java/com/example/dattingapp/homes/networkandapi/TokenManager.kt
  8. 8
      app/src/main/java/com/example/dattingapp/registration/RegistrationScreen.kt

76
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
@ -140,10 +141,10 @@ fun FriendProfileScreen(
}
// ✅ turn each into a set of ids
val likedMeIds = (likesState as? LikedUiState.Success)
val iLikedIds = (likesState as? LikedUiState.Success)
?.LikeData?.map { it.id }?.toSet() ?: emptySet()
val iLikedIds = (likedState as? LikedUiState.Success)
val likedMeIds = (likedState as? LikedUiState.Success)
?.LikeData?.map { it.id }?.toSet() ?: emptySet()
val matchIds = (matchesState as? LikedUiState.Success)
@ -154,6 +155,12 @@ fun FriendProfileScreen(
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)
}
@ -303,8 +310,7 @@ fun FriendProfileScreen(
)
}
// Action Buttons Row
//action button
Row(
modifier = Modifier
.fillMaxWidth()
@ -312,16 +318,22 @@ fun FriendProfileScreen(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Message — inside a card
if (isMatch) {
// 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),
shape = RoundedCornerShape(10.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
colors = CardDefaults.cardColors(
containerColor = if (isMatch) Color.White else Color(0xFFF0F0F0),
disabledContainerColor = Color(0xFFF0F0F0)
),
elevation = CardDefaults.cardElevation(
defaultElevation = if (isMatch) 4.dp else 0.dp
)
) {
Row(
modifier = Modifier.fillMaxSize(),
@ -331,7 +343,7 @@ fun FriendProfileScreen(
Icon(
imageVector = Icons.Default.ChatBubble,
contentDescription = "Message",
tint = textDark,
tint = if (isMatch) textDark else Color(0xFFBDBDBD), // gray when disabled
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(8.dp))
@ -339,39 +351,48 @@ fun FriendProfileScreen(
text = "Message",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = textDark
color = if (isMatch) textDark else Color(0xFFBDBDBD) // gray when disabled
)
}
}
}
// ❤️ Like — red ICON when the user liked ME, hidden when matched
if (!isMatch) {
// ❤️ 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
},
enabled = !likeDisabled,
modifier = Modifier.size(48.dp),
shape = RoundedCornerShape(10.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
colors = CardDefaults.cardColors(
containerColor = if (heLikedMe) Color(0xFFE53935) else Color.White,
disabledContainerColor = if (heLikedMe) Color(0xFFE53935) else Color(0xFFF0F0F0)
),
elevation = CardDefaults.cardElevation(
defaultElevation = if (likeDisabled) 0.dp else 2.dp
)
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(
imageVector = Icons.Default.Favorite,
contentDescription = "Like",
tint = when {
heLikedMe -> Color(0xFFE53935) // 🔴 user liked me
iLikedHim -> Color(0xFF1E88E5) // 🔵 I liked the user
else -> textDark // ⚫ no relation yet
heLikedMe -> Color.White // white heart on red background
likeDisabled -> Color(0xFFBDBDBD) // gray when disabled
else -> textDark // normal
},
modifier = Modifier.size(22.dp)
modifier = Modifier.size(20.dp)
)
}
}
// Super Like — inside a card
// Super Like — card style
Card(
onClick = {
onStarClick()
@ -380,42 +401,41 @@ fun FriendProfileScreen(
modifier = Modifier.size(48.dp),
shape = RoundedCornerShape(10.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(
imageVector = Icons.Default.Star,
contentDescription = "Favorite",
tint = textDark,
modifier = Modifier.size(22.dp)
modifier = Modifier.size(20.dp)
)
}
}
}
// Cancel — inside a card, always visible
// 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),
shape = RoundedCornerShape(10.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Dislike",
tint = textDark,
modifier = Modifier.size(22.dp)
modifier = Modifier.size(20.dp)
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// User Posts Grid Section below Action Row

47
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,18 +56,50 @@ class LikeANdMatchActivity : ComponentActivity() {
DattingAPPTheme {
val likedViewModel: LikedViewModel = viewModel()
val context = LocalContext.current
val type = intent.getStringExtra("TYPE") ?: "liked"
val type = intent.getStringExtra("TYPE") ?: "Likes"
// Collect state from ViewModel
val uiState by likedViewModel.likesUiState.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,type)
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
@ -79,7 +113,7 @@ class LikeANdMatchActivity : ComponentActivity() {
}
is LikedUiState.Success -> {
ProfileGrid(
context =context,
context = context,
likedResponse = state.LikeData,
isLoading = false,
errorMessage = null
@ -96,7 +130,7 @@ class LikeANdMatchActivity : ComponentActivity() {
color = MaterialTheme.colorScheme.error,
fontWeight = FontWeight.Medium
)
Button(onClick = { likedViewModel.likedResponse(context,"like") }) {
Button(onClick = { likedViewModel.likedResponse(context, type) }) {
Text("Retry")
}
}
@ -108,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,

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

@ -274,12 +274,16 @@ fun LoginScreenContent(
Log.d("useridlogin","$id")
tokenManager.saveTokens(
accessToken = loginData.accessToken ?: "",
refreshToken = loginData.refreshToken ?: "",
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 {

55
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,12 +43,14 @@ 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
@ -64,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,
@ -132,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()
@ -147,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
@ -181,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)
)
}
},
@ -212,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,

662
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,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(

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

@ -535,12 +535,12 @@ sealed interface LikedUiState {
class LikedViewModel : ViewModel() {
// ✅ three separate states, one per list type
private val _likesUiState = MutableStateFlow<LikedUiState>(LikedUiState.Idle) // users who liked ME
val likesUiState: StateFlow<LikedUiState> = _likesUiState.asStateFlow()
private val _likedUiState = MutableStateFlow<LikedUiState>(LikedUiState.Idle) // users I liked
private val _likedUiState = MutableStateFlow<LikedUiState>(LikedUiState.Idle) // users who liked ME
val likedUiState: StateFlow<LikedUiState> = _likedUiState.asStateFlow()
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()

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?,profile_image_url: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,6 +28,14 @@ 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)
@ -33,12 +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 currentProfileImage(): String? = preferences.getString("profile_image_url", 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)

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)

Loading…
Cancel
Save