|
|
@ -1,6 +1,8 @@ |
|
|
package com.example.dattingapp.homes |
|
|
package com.example.dattingapp.homes |
|
|
|
|
|
|
|
|
|
|
|
import android.content.Context |
|
|
import android.util.Log |
|
|
import android.util.Log |
|
|
|
|
|
import androidx.compose.animation.core.Animatable |
|
|
import androidx.compose.foundation.background |
|
|
import androidx.compose.foundation.background |
|
|
import androidx.compose.foundation.layout.* |
|
|
import androidx.compose.foundation.layout.* |
|
|
import androidx.compose.foundation.shape.CircleShape |
|
|
import androidx.compose.foundation.shape.CircleShape |
|
|
@ -21,11 +23,15 @@ import androidx.compose.ui.graphics.Color |
|
|
import androidx.compose.ui.layout.ContentScale |
|
|
import androidx.compose.ui.layout.ContentScale |
|
|
import androidx.compose.ui.platform.LocalContext |
|
|
import androidx.compose.ui.platform.LocalContext |
|
|
import androidx.compose.ui.text.font.FontWeight |
|
|
import androidx.compose.ui.text.font.FontWeight |
|
|
|
|
|
import androidx.compose.ui.tooling.preview.Preview |
|
|
import androidx.compose.ui.unit.dp |
|
|
import androidx.compose.ui.unit.dp |
|
|
import androidx.compose.ui.unit.sp |
|
|
import androidx.compose.ui.unit.sp |
|
|
|
|
|
import androidx.lifecycle.viewmodel.compose.viewModel |
|
|
import coil.compose.AsyncImage |
|
|
import coil.compose.AsyncImage |
|
|
import coil.request.ImageRequest |
|
|
import coil.request.ImageRequest |
|
|
import com.example.dattingapp.homes.networkandapi.MeetUserProfile |
|
|
import com.example.dattingapp.homes.networkandapi.MeetUserProfile |
|
|
|
|
|
import com.example.dattingapp.homes.networkandapi.SwipeRequest |
|
|
|
|
|
import com.example.dattingapp.homes.networkandapi.SwipeViewModel |
|
|
import com.example.dattingapp.homes.networkandapi.TokenManager |
|
|
import com.example.dattingapp.homes.networkandapi.TokenManager |
|
|
import com.example.dattingapp.ui.theme.BrandPink |
|
|
import com.example.dattingapp.ui.theme.BrandPink |
|
|
import com.example.dattingapp.ui.theme.BrandPurple |
|
|
import com.example.dattingapp.ui.theme.BrandPurple |
|
|
@ -43,9 +49,10 @@ fun MeetDiscoveryScreen( |
|
|
onSearchClick: () -> Unit = {}, |
|
|
onSearchClick: () -> Unit = {}, |
|
|
onSuperLikeClick: (MeetUserProfile) -> Unit = {}, |
|
|
onSuperLikeClick: (MeetUserProfile) -> Unit = {}, |
|
|
onRejectClick: (MeetUserProfile) -> Unit = {}, |
|
|
onRejectClick: (MeetUserProfile) -> Unit = {}, |
|
|
onLikeClick: (MeetUserProfile) -> Unit = {} |
|
|
onLikeClick: (MeetUserProfile) -> Unit = {}, |
|
|
|
|
|
swipeViewModel: SwipeViewModel = viewModel() |
|
|
) { |
|
|
) { |
|
|
// Index tracking current active user profile (starts at 0) |
|
|
val context = LocalContext.current |
|
|
var currentIndex by remember { mutableIntStateOf(0) } |
|
|
var currentIndex by remember { mutableIntStateOf(0) } |
|
|
|
|
|
|
|
|
Box( |
|
|
Box( |
|
|
@ -121,7 +128,6 @@ fun MeetDiscoveryScreen( |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
profiles.isNotEmpty() && currentIndex < profiles.size -> { |
|
|
profiles.isNotEmpty() && currentIndex < profiles.size -> { |
|
|
// Current profile starting at index 0 |
|
|
|
|
|
val currentProfile = profiles[currentIndex] |
|
|
val currentProfile = profiles[currentIndex] |
|
|
|
|
|
|
|
|
ProfileCardItem( |
|
|
ProfileCardItem( |
|
|
@ -131,10 +137,12 @@ fun MeetDiscoveryScreen( |
|
|
currentIndex++ |
|
|
currentIndex++ |
|
|
}, |
|
|
}, |
|
|
onReject = { |
|
|
onReject = { |
|
|
|
|
|
handleSwipeAction(context,direction="pass", currentProfile, swipeViewModel) |
|
|
onRejectClick(currentProfile) |
|
|
onRejectClick(currentProfile) |
|
|
currentIndex++ |
|
|
currentIndex++ |
|
|
}, |
|
|
}, |
|
|
onLike = { |
|
|
onLike = { |
|
|
|
|
|
handleSwipeAction(context,direction="like", currentProfile, swipeViewModel) |
|
|
onLikeClick(currentProfile) |
|
|
onLikeClick(currentProfile) |
|
|
currentIndex++ |
|
|
currentIndex++ |
|
|
} |
|
|
} |
|
|
@ -161,20 +169,22 @@ private fun ProfileCardItem( |
|
|
onReject: () -> Unit, |
|
|
onReject: () -> Unit, |
|
|
onLike: () -> Unit |
|
|
onLike: () -> Unit |
|
|
) { |
|
|
) { |
|
|
// Extract primary photo URL from media list |
|
|
val context = LocalContext.current |
|
|
|
|
|
|
|
|
|
|
|
val scope = rememberCoroutineScope() |
|
|
|
|
|
// Reset drag offset whenever the profile changes |
|
|
|
|
|
val offsetX = remember(profile) { Animatable(0f) } |
|
|
|
|
|
|
|
|
val photoUrl = remember(profile) { |
|
|
val photoUrl = remember(profile) { |
|
|
profile.media |
|
|
profile.media |
|
|
?.firstOrNull { it.mediaType.equals("image", ignoreCase = true) || it.url != null } |
|
|
?.firstOrNull { it.mediaType.equals("image", ignoreCase = true) || it.url != null } |
|
|
?.fullMediaUrl |
|
|
?.fullMediaUrl |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Log.d("photourl", "$photoUrl") |
|
|
|
|
|
// Helper to calculate age from birth_date string (formatted e.g. "YYYY-MM-DD") |
|
|
|
|
|
val age = remember(profile.birthDate) { |
|
|
val age = remember(profile.birthDate) { |
|
|
calculateAge(profile.birthDate) |
|
|
calculateAge(profile.birthDate) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Location display formatting |
|
|
|
|
|
val locationText = remember(profile) { |
|
|
val locationText = remember(profile) { |
|
|
val loc = listOfNotNull(profile.city, profile.country).joinToString(", ") |
|
|
val loc = listOfNotNull(profile.city, profile.country).joinToString(", ") |
|
|
val dist = profile.distanceKm?.let { "${it.toInt()} km away" } |
|
|
val dist = profile.distanceKm?.let { "${it.toInt()} km away" } |
|
|
@ -186,8 +196,6 @@ private fun ProfileCardItem( |
|
|
.fillMaxSize() |
|
|
.fillMaxSize() |
|
|
.clip(RoundedCornerShape(20.dp)) |
|
|
.clip(RoundedCornerShape(20.dp)) |
|
|
) { |
|
|
) { |
|
|
// BACKEND PROFILE IMAGE |
|
|
|
|
|
val context = LocalContext.current |
|
|
|
|
|
val token = remember(context) { |
|
|
val token = remember(context) { |
|
|
TokenManager.getInstance(context).getAccessToken() |
|
|
TokenManager.getInstance(context).getAccessToken() |
|
|
} |
|
|
} |
|
|
@ -197,7 +205,6 @@ private fun ProfileCardItem( |
|
|
.data(photoUrl) |
|
|
.data(photoUrl) |
|
|
.apply { |
|
|
.apply { |
|
|
if (!token.isNullOrBlank()) { |
|
|
if (!token.isNullOrBlank()) { |
|
|
// Formatting matches "Bearer <token>" or raw token based on backend preference |
|
|
|
|
|
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) |
|
|
addHeader("Authorization", authHeader) |
|
|
} |
|
|
} |
|
|
@ -295,7 +302,6 @@ private fun ProfileCardItem( |
|
|
verticalAlignment = Alignment.CenterVertically, |
|
|
verticalAlignment = Alignment.CenterVertically, |
|
|
horizontalArrangement = Arrangement.spacedBy(8.dp) |
|
|
horizontalArrangement = Arrangement.spacedBy(8.dp) |
|
|
) { |
|
|
) { |
|
|
// Name & Age |
|
|
|
|
|
val displayName = profile.fullName ?: "User" |
|
|
val displayName = profile.fullName ?: "User" |
|
|
val nameAndAge = if (age != null) "$displayName, $age" else displayName |
|
|
val nameAndAge = if (age != null) "$displayName, $age" else displayName |
|
|
|
|
|
|
|
|
@ -306,7 +312,6 @@ private fun ProfileCardItem( |
|
|
color = Color.White |
|
|
color = Color.White |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
// Compatibility / Match Percentage Badge |
|
|
|
|
|
profile.compatibilityPercentage?.let { match -> |
|
|
profile.compatibilityPercentage?.let { match -> |
|
|
Surface( |
|
|
Surface( |
|
|
shape = RoundedCornerShape(10.dp), |
|
|
shape = RoundedCornerShape(10.dp), |
|
|
@ -336,7 +341,6 @@ private fun ProfileCardItem( |
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(4.dp)) |
|
|
Spacer(modifier = Modifier.height(4.dp)) |
|
|
|
|
|
|
|
|
// Location & Distance |
|
|
|
|
|
if (locationText.isNotBlank()) { |
|
|
if (locationText.isNotBlank()) { |
|
|
Row( |
|
|
Row( |
|
|
verticalAlignment = Alignment.CenterVertically, |
|
|
verticalAlignment = Alignment.CenterVertically, |
|
|
@ -356,7 +360,6 @@ private fun ProfileCardItem( |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Bio |
|
|
|
|
|
profile.bio?.takeIf { it.isNotBlank() }?.let { bio -> |
|
|
profile.bio?.takeIf { it.isNotBlank() }?.let { bio -> |
|
|
Spacer(modifier = Modifier.height(8.dp)) |
|
|
Spacer(modifier = Modifier.height(8.dp)) |
|
|
Text( |
|
|
Text( |
|
|
@ -370,16 +373,43 @@ private fun ProfileCardItem( |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Utility to calculate age from date string (e.g. "1999-05-15") |
|
|
//Helper to calculate age from birth date string (e.g., "1999-05-15") |
|
|
private fun calculateAge(birthDateStr: String?): Int? { |
|
|
private fun calculateAge(birthDateStr: String?): Int? { |
|
|
if (birthDateStr.isNull_or_blank()) return null |
|
|
if (birthDateStr.isNullOrBlank()) return null |
|
|
return try { |
|
|
return try { |
|
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") |
|
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") |
|
|
val birthDate = LocalDate.parse(birthDateStr?.take(10), formatter) |
|
|
val birthDate = LocalDate.parse(birthDateStr.take(10), formatter) |
|
|
Period.between(birthDate, LocalDate.now()).years |
|
|
Period.between(birthDate, LocalDate.now()).years |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
null |
|
|
null |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private fun String?.isNull_or_blank(): Boolean = this == null || this.trim().isEmpty() |
|
|
// Helper method for reject swipe network call |
|
|
|
|
|
private fun handleSwipeAction( |
|
|
|
|
|
context: Context, |
|
|
|
|
|
direction:String, |
|
|
|
|
|
profile: MeetUserProfile, |
|
|
|
|
|
swipeViewModel: SwipeViewModel |
|
|
|
|
|
) { |
|
|
|
|
|
val targetUserId = profile.id |
|
|
|
|
|
val swipeRequest = SwipeRequest(targetUserId, direction) |
|
|
|
|
|
swipeViewModel.sendSwipeResponse(context, swipeRequest) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Preview( |
|
|
|
|
|
name = "Discovery Screen - Active Card", |
|
|
|
|
|
showBackground = true, |
|
|
|
|
|
widthDp = 390, |
|
|
|
|
|
heightDp = 844 |
|
|
|
|
|
) |
|
|
|
|
|
@Composable |
|
|
|
|
|
fun MeetDiscoveryScreenPreview() { |
|
|
|
|
|
MaterialTheme { |
|
|
|
|
|
MeetDiscoveryScreen( |
|
|
|
|
|
profiles = emptyList(), |
|
|
|
|
|
isLoading = false, |
|
|
|
|
|
errorMessage = null |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
} |