17 changed files with 2564 additions and 669 deletions
@ -0,0 +1,301 @@ |
|||||
|
package com.example.dattingapp |
||||
|
|
||||
|
import android.content.Context |
||||
|
import android.content.Intent |
||||
|
import android.os.Bundle |
||||
|
import androidx.activity.ComponentActivity |
||||
|
import androidx.activity.compose.setContent |
||||
|
import androidx.activity.enableEdgeToEdge |
||||
|
import androidx.compose.foundation.background |
||||
|
import androidx.compose.foundation.clickable |
||||
|
import androidx.compose.foundation.layout.* |
||||
|
import androidx.compose.foundation.lazy.grid.GridCells |
||||
|
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.filled.Favorite |
||||
|
import androidx.compose.material.icons.filled.LocationOn |
||||
|
import androidx.compose.material3.* |
||||
|
import androidx.compose.runtime.Composable |
||||
|
import androidx.compose.runtime.LaunchedEffect |
||||
|
import androidx.compose.runtime.collectAsState |
||||
|
import androidx.compose.runtime.getValue |
||||
|
import androidx.compose.runtime.remember |
||||
|
import androidx.compose.ui.Alignment |
||||
|
import androidx.compose.ui.Modifier |
||||
|
import androidx.compose.ui.draw.clip |
||||
|
import androidx.compose.ui.graphics.Brush |
||||
|
import androidx.compose.ui.graphics.Color |
||||
|
import androidx.compose.ui.layout.ContentScale |
||||
|
import androidx.compose.ui.platform.LocalContext |
||||
|
import androidx.compose.ui.text.font.FontWeight |
||||
|
import androidx.compose.ui.tooling.preview.Preview |
||||
|
import androidx.compose.ui.unit.dp |
||||
|
import androidx.compose.ui.unit.sp |
||||
|
import androidx.lifecycle.viewmodel.compose.viewModel |
||||
|
import coil.compose.AsyncImage |
||||
|
import com.example.dattingapp.homes.networkandapi.LikedUiState |
||||
|
import com.example.dattingapp.homes.networkandapi.LikedViewModel |
||||
|
import com.example.dattingapp.homes.profile.LikedUserResponse |
||||
|
import com.example.dattingapp.ui.theme.DattingAPPTheme |
||||
|
import java.time.LocalDate |
||||
|
import java.time.Period |
||||
|
import java.time.format.DateTimeFormatter |
||||
|
import kotlin.jvm.java |
||||
|
|
||||
|
class LikeANdMatchActivity : ComponentActivity() { |
||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||
|
super.onCreate(savedInstanceState) |
||||
|
enableEdgeToEdge() |
||||
|
|
||||
|
setContent { |
||||
|
DattingAPPTheme { |
||||
|
val likedViewModel: LikedViewModel = viewModel() |
||||
|
val context = LocalContext.current |
||||
|
|
||||
|
// Collect state from ViewModel |
||||
|
val uiState by likedViewModel.likeUiState.collectAsState() |
||||
|
|
||||
|
// Trigger network call once when screen loads |
||||
|
LaunchedEffect(Unit) { |
||||
|
likedViewModel.likedResponse(context) |
||||
|
} |
||||
|
|
||||
|
Scaffold( |
||||
|
modifier = Modifier.fillMaxSize(), |
||||
|
) { innerPadding -> |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.fillMaxSize() |
||||
|
.padding(innerPadding), |
||||
|
contentAlignment = Alignment.Center |
||||
|
) { |
||||
|
when (val state = uiState) { |
||||
|
is LikedUiState.Idle, is LikedUiState.Loading -> { |
||||
|
CircularProgressIndicator() |
||||
|
} |
||||
|
is LikedUiState.Success -> { |
||||
|
ProfileGrid( |
||||
|
context =context, |
||||
|
likedResponse = state.LikeData, |
||||
|
isLoading = false, |
||||
|
errorMessage = null |
||||
|
) |
||||
|
} |
||||
|
is LikedUiState.Error -> { |
||||
|
Column( |
||||
|
horizontalAlignment = Alignment.CenterHorizontally, |
||||
|
verticalArrangement = Arrangement.spacedBy(8.dp), |
||||
|
modifier = Modifier.padding(16.dp) |
||||
|
) { |
||||
|
Text( |
||||
|
text = state.message, |
||||
|
color = MaterialTheme.colorScheme.error, |
||||
|
fontWeight = FontWeight.Medium |
||||
|
) |
||||
|
Button(onClick = { likedViewModel.likedResponse(context) }) { |
||||
|
Text("Retry") |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 1. Define ProfileCard FIRST so ProfileGrid can reference it without issue |
||||
|
@Composable |
||||
|
fun ProfileCard(context: Context, likedProfile: LikedUserResponse, modifier: Modifier = Modifier, |
||||
|
onNameClick: () -> Unit={}, |
||||
|
) { |
||||
|
Card( |
||||
|
shape = RoundedCornerShape(16.dp), |
||||
|
colors = CardDefaults.cardColors(containerColor = Color.White), |
||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), |
||||
|
modifier = modifier.fillMaxWidth() |
||||
|
) { |
||||
|
Column( |
||||
|
modifier = Modifier.padding(8.dp), |
||||
|
verticalArrangement = Arrangement.spacedBy(8.dp) |
||||
|
) { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.height(180.dp) |
||||
|
.clip(RoundedCornerShape(12.dp)) |
||||
|
) { |
||||
|
// Use profile_image_url or fallback to the first media item |
||||
|
AsyncImage( |
||||
|
model = likedProfile.profile_image_url?.takeIf { it.isNotBlank() } |
||||
|
?: likedProfile.media.firstOrNull(), |
||||
|
contentDescription = likedProfile.full_name, |
||||
|
contentScale = ContentScale.Crop, |
||||
|
modifier = Modifier.fillMaxSize() |
||||
|
) |
||||
|
|
||||
|
// Gradient Overlay |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.fillMaxSize() |
||||
|
.background( |
||||
|
Brush.verticalGradient( |
||||
|
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.7f)), |
||||
|
startY = 100f |
||||
|
) |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
// Match Badge |
||||
|
Surface( |
||||
|
shape = RoundedCornerShape(20.dp), |
||||
|
color = Color.White.copy(alpha = 0.9f), |
||||
|
modifier = Modifier |
||||
|
.padding(8.dp) |
||||
|
.align(Alignment.TopStart) |
||||
|
) { |
||||
|
Row( |
||||
|
verticalAlignment = Alignment.CenterVertically, |
||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp), |
||||
|
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.Favorite, |
||||
|
contentDescription = null, |
||||
|
tint = Color(0xFF6C5CE7), |
||||
|
modifier = Modifier.size(12.dp) |
||||
|
) |
||||
|
Text( |
||||
|
text = "${likedProfile.compatibility_percentage}%", |
||||
|
fontSize = 11.sp, |
||||
|
fontWeight = FontWeight.Bold, |
||||
|
color = Color(0xFF2D3436) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Name, Age & Distance |
||||
|
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 |
||||
|
.padding(8.dp) |
||||
|
){ |
||||
|
val displayName = likedProfile.full_name.ifBlank { "User" } |
||||
|
val age = remember(likedProfile.birth_date) { |
||||
|
calculateAge(likedProfile.birth_date) |
||||
|
} |
||||
|
val nameAndAge = if (age != null) "$displayName, $age" else displayName |
||||
|
|
||||
|
Text( |
||||
|
text = nameAndAge, |
||||
|
color = Color.White, |
||||
|
fontWeight = FontWeight.Bold, |
||||
|
fontSize = 14.sp |
||||
|
) |
||||
|
Row( |
||||
|
verticalAlignment = Alignment.CenterVertically, |
||||
|
horizontalArrangement = Arrangement.spacedBy(2.dp) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.LocationOn, |
||||
|
contentDescription = null, |
||||
|
tint = Color.White.copy(alpha = 0.8f), |
||||
|
modifier = Modifier.size(10.dp) |
||||
|
) |
||||
|
Text( |
||||
|
text = "${likedProfile.distance_km ?: 0.0} km", |
||||
|
color = Color.White.copy(alpha = 0.8f), |
||||
|
fontSize = 10.sp |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 2. Define ProfileGrid AFTER ProfileCard |
||||
|
@Composable |
||||
|
fun ProfileGrid( |
||||
|
context: Context, |
||||
|
likedResponse: List<LikedUserResponse>, |
||||
|
isLoading: Boolean, |
||||
|
errorMessage: String?, |
||||
|
onRetry: () -> Unit = {}, |
||||
|
modifier: Modifier = Modifier |
||||
|
) { |
||||
|
if (likedResponse.isEmpty() && !isLoading) { |
||||
|
Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { |
||||
|
Text("No liked profiles found.", color = Color.Gray) |
||||
|
} |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
LazyVerticalGrid( |
||||
|
columns = GridCells.Fixed(2), |
||||
|
contentPadding = PaddingValues(12.dp), |
||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp), |
||||
|
verticalArrangement = Arrangement.spacedBy(12.dp), |
||||
|
modifier = modifier.fillMaxSize() |
||||
|
) { |
||||
|
items(likedResponse) { profile -> |
||||
|
ProfileCard(context,likedProfile = profile) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Helper to calculate age from birth date string (e.g., "1999-05-15") |
||||
|
private fun calculateAge(birthDateStr: String?): Int? { |
||||
|
if (birthDateStr.isNullOrBlank()) return null |
||||
|
return try { |
||||
|
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") |
||||
|
val birthDate = LocalDate.parse(birthDateStr.take(10), formatter) |
||||
|
Period.between(birthDate, LocalDate.now()).years |
||||
|
} catch (e: Exception) { |
||||
|
null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Preview(showBackground = true) |
||||
|
@Composable |
||||
|
fun ProfileGridPreview() { |
||||
|
val sampleLikedUsers = listOf( |
||||
|
LikedUserResponse( |
||||
|
id = 55, |
||||
|
full_name = "Sarah Jenkins", |
||||
|
gender = "female", |
||||
|
birth_date = "1995-08-20", |
||||
|
city = "Shanghai", |
||||
|
county = "District A", |
||||
|
country = "China", |
||||
|
relationship_goal = "dating", |
||||
|
bio = "Coffee lover and traveler.", |
||||
|
profile_image_url = "", |
||||
|
video_url = null, |
||||
|
distance_km = 4.1, |
||||
|
compatibility_percentage = 92, |
||||
|
media = emptyList() |
||||
|
) |
||||
|
) |
||||
|
val context = LocalContext.current |
||||
|
|
||||
|
DattingAPPTheme { |
||||
|
ProfileGrid( |
||||
|
context=context, |
||||
|
likedResponse = sampleLikedUsers, |
||||
|
isLoading = false, |
||||
|
errorMessage = null |
||||
|
) |
||||
|
} |
||||
|
} |
||||
@ -1,252 +0,0 @@ |
|||||
package com.example.dattingapp |
|
||||
import androidx.compose.foundation.background |
|
||||
import androidx.compose.foundation.layout.* |
|
||||
import androidx.compose.foundation.lazy.grid.GridCells |
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid |
|
||||
import androidx.compose.foundation.lazy.grid.items |
|
||||
import androidx.compose.foundation.shape.CircleShape |
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape |
|
||||
import androidx.compose.material.icons.Icons |
|
||||
import androidx.compose.material.icons.filled.Close |
|
||||
import androidx.compose.material.icons.filled.Favorite |
|
||||
import androidx.compose.material.icons.filled.LocationOn |
|
||||
import androidx.compose.material.icons.outlined.ChatBubbleOutline |
|
||||
import androidx.compose.material3.* |
|
||||
import androidx.compose.runtime.Composable |
|
||||
import androidx.compose.ui.Alignment |
|
||||
import androidx.compose.ui.Modifier |
|
||||
import androidx.compose.ui.draw.clip |
|
||||
import androidx.compose.ui.graphics.Brush |
|
||||
import androidx.compose.ui.graphics.Color |
|
||||
import androidx.compose.ui.layout.ContentScale |
|
||||
import androidx.compose.ui.text.font.FontWeight |
|
||||
import androidx.compose.ui.tooling.preview.Preview |
|
||||
import androidx.compose.ui.unit.dp |
|
||||
import androidx.compose.ui.unit.sp |
|
||||
import coil.compose.AsyncImage |
|
||||
|
|
||||
|
|
||||
|
|
||||
class LikeAndMatchScreen { |
|
||||
} |
|
||||
|
|
||||
data class Profile( |
|
||||
val name: String, |
|
||||
val age: Int, |
|
||||
val distance: String, |
|
||||
val matchPercentage: Int, |
|
||||
val tags: List<String>, |
|
||||
val imageUrl: String, |
|
||||
val hasUnread: Boolean = false |
|
||||
) |
|
||||
|
|
||||
@Composable |
|
||||
fun ProfileGrid(profiles: List<Profile>, modifier: Modifier = Modifier) { |
|
||||
LazyVerticalGrid( |
|
||||
columns = GridCells.Fixed(2), |
|
||||
contentPadding = PaddingValues(12.dp), |
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp), |
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp), |
|
||||
modifier = modifier.fillMaxSize() |
|
||||
) { |
|
||||
items(profiles) { profile -> |
|
||||
ProfileCard(profile = profile) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Composable |
|
||||
fun ProfileCard(profile: Profile, modifier: Modifier = Modifier) { |
|
||||
Card( |
|
||||
shape = RoundedCornerShape(16.dp), |
|
||||
colors = CardDefaults.cardColors(containerColor = Color.White), |
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), |
|
||||
modifier = modifier.fillMaxWidth() |
|
||||
) { |
|
||||
Column( |
|
||||
modifier = Modifier.padding(8.dp), |
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp) |
|
||||
) { |
|
||||
// Image with Overlays |
|
||||
Box( |
|
||||
modifier = Modifier |
|
||||
.fillMaxWidth() |
|
||||
.height(180.dp) |
|
||||
.clip(RoundedCornerShape(12.dp)) |
|
||||
) { |
|
||||
AsyncImage( |
|
||||
model = profile.imageUrl, |
|
||||
contentDescription = profile.name, |
|
||||
contentScale = ContentScale.Crop, |
|
||||
modifier = Modifier.fillMaxSize() |
|
||||
) |
|
||||
|
|
||||
// Gradient Overlay for Text Readability |
|
||||
Box( |
|
||||
modifier = Modifier |
|
||||
.fillMaxSize() |
|
||||
.background( |
|
||||
Brush.verticalGradient( |
|
||||
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.7f)), |
|
||||
startY = 100f |
|
||||
) |
|
||||
) |
|
||||
) |
|
||||
|
|
||||
// Match Badge |
|
||||
Surface( |
|
||||
shape = RoundedCornerShape(20.dp), |
|
||||
color = Color.White.copy(alpha = 0.9f), |
|
||||
modifier = Modifier |
|
||||
.padding(8.dp) |
|
||||
.align(Alignment.TopStart) |
|
||||
) { |
|
||||
Row( |
|
||||
verticalAlignment = Alignment.CenterVertically, |
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp), |
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) |
|
||||
) { |
|
||||
Icon( |
|
||||
imageVector = Icons.Default.Favorite, |
|
||||
contentDescription = null, |
|
||||
tint = Color(0xFF6C5CE7), |
|
||||
modifier = Modifier.size(12.dp) |
|
||||
) |
|
||||
Text( |
|
||||
text = "${profile.matchPercentage}%", |
|
||||
fontSize = 11.sp, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
color = Color(0xFF2D3436) |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Notification Dot |
|
||||
if (profile.hasUnread) { |
|
||||
Box( |
|
||||
modifier = Modifier |
|
||||
.padding(8.dp) |
|
||||
.size(8.dp) |
|
||||
.clip(CircleShape) |
|
||||
.background(Color(0xFF6C5CE7)) |
|
||||
.align(Alignment.TopEnd) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
// Name, Age & Distance |
|
||||
Column( |
|
||||
modifier = Modifier |
|
||||
.align(Alignment.BottomStart) |
|
||||
.padding(8.dp) |
|
||||
) { |
|
||||
Text( |
|
||||
text = "${profile.name}, ${profile.age}", |
|
||||
color = Color.White, |
|
||||
fontWeight = FontWeight.Bold, |
|
||||
fontSize = 14.sp |
|
||||
) |
|
||||
Row( |
|
||||
verticalAlignment = Alignment.CenterVertically, |
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp) |
|
||||
) { |
|
||||
Icon( |
|
||||
imageVector = Icons.Default.LocationOn, |
|
||||
contentDescription = null, |
|
||||
tint = Color.White.copy(alpha = 0.8f), |
|
||||
modifier = Modifier.size(10.dp) |
|
||||
) |
|
||||
Text( |
|
||||
text = profile.distance, |
|
||||
color = Color.White.copy(alpha = 0.8f), |
|
||||
fontSize = 10.sp |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Tags Row |
|
||||
Row( |
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp), |
|
||||
modifier = Modifier.fillMaxWidth() |
|
||||
) { |
|
||||
profile.tags.take(2).forEach { tag -> |
|
||||
Surface( |
|
||||
shape = RoundedCornerShape(6.dp), |
|
||||
color = Color(0xFFF1F2F6), |
|
||||
modifier = Modifier.weight(1f) |
|
||||
) { |
|
||||
Text( |
|
||||
text = tag, |
|
||||
fontSize = 10.sp, |
|
||||
color = Color(0xFF636E72), |
|
||||
modifier = Modifier.padding(vertical = 4.dp, horizontal = 4.dp), |
|
||||
maxLines = 1 |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Action Buttons |
|
||||
Row( |
|
||||
horizontalArrangement = Arrangement.SpaceBetween, |
|
||||
verticalAlignment = Alignment.CenterVertically, |
|
||||
modifier = Modifier.fillMaxWidth().padding(10.dp) |
|
||||
) { |
|
||||
IconButton( |
|
||||
onClick = { }, |
|
||||
modifier = Modifier |
|
||||
.size(22.dp) |
|
||||
.background(Color(0xFFF8F9FA), CircleShape) |
|
||||
) { |
|
||||
Icon( |
|
||||
imageVector = Icons.Default.Close, |
|
||||
contentDescription = "Pass", |
|
||||
tint = Color(0xFF636E72), |
|
||||
modifier = Modifier.size(16.dp) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
IconButton( |
|
||||
onClick = { }, |
|
||||
modifier = Modifier |
|
||||
.size(22.dp) |
|
||||
.background(Color(0xFFF1EFFE), CircleShape) |
|
||||
) { |
|
||||
Icon( |
|
||||
imageVector = Icons.Outlined.ChatBubbleOutline, |
|
||||
contentDescription = "Message", |
|
||||
tint = Color(0xFF6C5CE7), |
|
||||
modifier = Modifier.size(16.dp) |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
IconButton( |
|
||||
onClick = { }, |
|
||||
modifier = Modifier |
|
||||
.size(22.dp) |
|
||||
.background(Color(0xFFD63031), CircleShape) |
|
||||
) { |
|
||||
Icon( |
|
||||
imageVector = Icons.Default.Favorite, |
|
||||
contentDescription = "Like", |
|
||||
tint = Color.White, |
|
||||
modifier = Modifier.size(16.dp) |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Preview(showBackground = true) |
|
||||
@Composable |
|
||||
fun ProfileGridPreview() { |
|
||||
val sampleProfiles = listOf( |
|
||||
Profile("Elena", 24, "2 miles away", 98, listOf("Photography", "Coffee"), "", true), |
|
||||
Profile("Sarah", 26, "4 miles away", 94, listOf("Travel", "Music"), ""), |
|
||||
Profile("Julian", 27, "3 miles away", 91, listOf("Music Prod", "Vinyl"), ""), |
|
||||
Profile("Maya", 25, "5 miles away", 89, listOf("Surfing", "Yoga"), "") |
|
||||
) |
|
||||
|
|
||||
ProfileGrid(profiles = sampleProfiles) |
|
||||
} |
|
||||
@ -0,0 +1,447 @@ |
|||||
|
package com.example.dattingapp |
||||
|
|
||||
|
import android.os.Bundle |
||||
|
import androidx.activity.ComponentActivity |
||||
|
import androidx.activity.compose.setContent |
||||
|
import androidx.activity.enableEdgeToEdge |
||||
|
import androidx.compose.foundation.layout.fillMaxSize |
||||
|
import androidx.compose.foundation.layout.padding |
||||
|
import androidx.compose.material3.Text |
||||
|
import androidx.compose.runtime.Composable |
||||
|
import androidx.compose.runtime.remember |
||||
|
import androidx.compose.ui.Modifier |
||||
|
import com.example.dattingapp.ui.theme.DattingAPPTheme |
||||
|
import androidx.compose.foundation.background |
||||
|
import androidx.compose.foundation.layout.* |
||||
|
import androidx.compose.foundation.shape.CircleShape |
||||
|
import androidx.compose.material.icons.Icons |
||||
|
import androidx.compose.material.icons.filled.Favorite |
||||
|
import androidx.compose.material3.* |
||||
|
import androidx.compose.runtime.* |
||||
|
import androidx.compose.ui.Alignment |
||||
|
import androidx.compose.ui.draw.clip |
||||
|
import androidx.compose.ui.graphics.Color |
||||
|
import androidx.compose.ui.layout.ContentScale |
||||
|
import androidx.compose.ui.platform.LocalContext |
||||
|
import androidx.compose.ui.unit.dp |
||||
|
import coil.compose.AsyncImage |
||||
|
import android.util.Log |
||||
|
import androidx.compose.foundation.rememberScrollState |
||||
|
import androidx.compose.foundation.shape.RoundedCornerShape |
||||
|
import androidx.compose.foundation.text.BasicTextField |
||||
|
import androidx.compose.foundation.verticalScroll |
||||
|
import androidx.compose.material.icons.filled.FavoriteBorder |
||||
|
import androidx.compose.material.icons.outlined.ChatBubbleOutline |
||||
|
import androidx.compose.ui.text.SpanStyle |
||||
|
import androidx.compose.ui.text.buildAnnotatedString |
||||
|
import androidx.compose.ui.text.font.FontWeight |
||||
|
import androidx.compose.ui.text.withStyle |
||||
|
import androidx.compose.ui.unit.sp |
||||
|
import androidx.lifecycle.viewmodel.compose.viewModel |
||||
|
import com.example.dattingapp.dataclass.Author |
||||
|
import com.example.dattingapp.dataclass.ShowPostResponse |
||||
|
import com.example.dattingapp.homes.networkandapi.ShowPostUiState |
||||
|
import com.example.dattingapp.homes.networkandapi.ShowPostViewModel |
||||
|
import androidx.compose.material.icons.filled.Send |
||||
|
import androidx.compose.ui.tooling.preview.Preview |
||||
|
import com.example.dattingapp.dataclass.CommentResponse |
||||
|
import com.example.dattingapp.dataclass.Location |
||||
|
|
||||
|
// Color definitions matching the design palette |
||||
|
val PrimaryDarkText = Color(0xFF1E1E1E) |
||||
|
val SecondaryGrayText = Color(0xFF757575) |
||||
|
val AccentPink = Color(0xFFD81B60) |
||||
|
val CommentBubbleBg = Color(0xFFF6F6F6) |
||||
|
|
||||
|
class ShowPostingActivity : ComponentActivity() { |
||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||
|
super.onCreate(savedInstanceState) |
||||
|
enableEdgeToEdge() |
||||
|
|
||||
|
val postId = intent.getIntExtra("Post_ID", 0) |
||||
|
Log.d("Post_ID", "$postId") |
||||
|
|
||||
|
setContent { |
||||
|
DattingAPPTheme { |
||||
|
val context = LocalContext.current |
||||
|
val showPostViewModel: ShowPostViewModel = viewModel() |
||||
|
val uiState by showPostViewModel.showPostUiState.collectAsState() |
||||
|
val comments by showPostViewModel.commentsUiState.collectAsState() |
||||
|
|
||||
|
// Trigger API calls when the screen launches |
||||
|
LaunchedEffect(postId) { |
||||
|
showPostViewModel.fetchPostById(context, postId) |
||||
|
showPostViewModel.fetchComments(context, postId) |
||||
|
} |
||||
|
|
||||
|
Scaffold( |
||||
|
modifier = Modifier.fillMaxSize() |
||||
|
) { innerPadding -> |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.fillMaxSize() |
||||
|
.padding(innerPadding), |
||||
|
) { |
||||
|
when (val state = uiState) { |
||||
|
is ShowPostUiState.Idle, is ShowPostUiState.Loading -> { |
||||
|
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) |
||||
|
} |
||||
|
is ShowPostUiState.Success -> { |
||||
|
// Full screen scrollable container |
||||
|
Column( |
||||
|
modifier = Modifier |
||||
|
.fillMaxSize() |
||||
|
.verticalScroll(rememberScrollState()) |
||||
|
.padding(bottom = 24.dp) |
||||
|
) { |
||||
|
PostCard( |
||||
|
post = state.post, |
||||
|
comments = comments, |
||||
|
onAddComment = { commentText -> |
||||
|
val currentUserName = state.post.author?.full_name |
||||
|
showPostViewModel.addCommentLocallyAndServer( |
||||
|
context = context, |
||||
|
postId = postId, |
||||
|
content = commentText, |
||||
|
currentUserName = currentUserName |
||||
|
) |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
is ShowPostUiState.Error -> { |
||||
|
Text( |
||||
|
text = state.message, |
||||
|
color = Color.Red, |
||||
|
modifier = Modifier.align(Alignment.Center) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Composable |
||||
|
fun PostCard( |
||||
|
post: ShowPostResponse, |
||||
|
comments: List<CommentResponse>, |
||||
|
onAddComment: (String) -> Unit = {}, |
||||
|
modifier: Modifier = Modifier |
||||
|
) { |
||||
|
Column( |
||||
|
modifier = modifier |
||||
|
.fillMaxWidth() |
||||
|
.padding(vertical = 8.dp) |
||||
|
) { |
||||
|
// --- 1. Author Header --- |
||||
|
Row( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.padding(horizontal = 12.dp, vertical = 8.dp), |
||||
|
verticalAlignment = Alignment.CenterVertically |
||||
|
) { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.size(40.dp) |
||||
|
.clip(CircleShape) |
||||
|
.background(Color.LightGray), |
||||
|
contentAlignment = Alignment.Center |
||||
|
) { |
||||
|
Text(text = post.author?.full_name?.take(1) ?: "U") |
||||
|
} |
||||
|
Spacer(modifier = Modifier.width(8.dp)) |
||||
|
Column { |
||||
|
Row(verticalAlignment = Alignment.CenterVertically) { |
||||
|
val authorName = post.author?.full_name ?: "Anonymous" |
||||
|
val feelingText = post.feeling ?: "" |
||||
|
|
||||
|
Text( |
||||
|
text = buildAnnotatedString { |
||||
|
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { |
||||
|
append("$authorName ") |
||||
|
} |
||||
|
if (feelingText.isNotEmpty()) { |
||||
|
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( |
||||
|
text = post.location?.name ?: "", |
||||
|
style = MaterialTheme.typography.bodySmall, |
||||
|
color = Color.Gray |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// --- 2. Post Text Content --- |
||||
|
if (!post.content.isNullOrEmpty()) { |
||||
|
Text( |
||||
|
text = post.content, |
||||
|
style = MaterialTheme.typography.bodyMedium, |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.padding(horizontal = 12.dp, vertical = 4.dp) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
// --- 3. Media Carousel (Images) --- |
||||
|
if (post.media.isNotEmpty()) { |
||||
|
Box( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.height(350.dp) |
||||
|
.padding(vertical = 4.dp) |
||||
|
) { |
||||
|
AsyncImage( |
||||
|
model = post.media.first().url, |
||||
|
contentDescription = "Post Media Image", |
||||
|
contentScale = ContentScale.Crop, |
||||
|
modifier = Modifier.fillMaxSize() |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// --- 4. Interaction Buttons --- |
||||
|
Row( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.padding(horizontal = 8.dp, vertical = 4.dp), |
||||
|
verticalAlignment = Alignment.CenterVertically |
||||
|
) { |
||||
|
IconButton(onClick = { /* Handle like action */ }) { |
||||
|
Icon( |
||||
|
imageVector = if (post.likedByMe) Icons.Filled.Favorite else Icons.Default.FavoriteBorder, |
||||
|
contentDescription = "Like", |
||||
|
tint = if (post.likedByMe) Color.Red else Color.Black |
||||
|
) |
||||
|
} |
||||
|
Text(text = "${post.likeCount}") |
||||
|
|
||||
|
Spacer(modifier = Modifier.width(16.dp)) |
||||
|
|
||||
|
IconButton(onClick = { /* Handle comments */ }) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Outlined.ChatBubbleOutline, |
||||
|
contentDescription = "Comments" |
||||
|
) |
||||
|
} |
||||
|
Text(text = "${post.commentCount}") |
||||
|
} |
||||
|
|
||||
|
// --- 5. Comment Section UI --- |
||||
|
CommentSection( |
||||
|
comments = comments, |
||||
|
onAddCommentClick = onAddComment |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Composable |
||||
|
fun CommentSection( |
||||
|
comments: List<CommentResponse>, |
||||
|
onAddCommentClick: (String) -> Unit |
||||
|
) { |
||||
|
var newCommentText by remember { mutableStateOf("") } |
||||
|
|
||||
|
Column( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.padding(horizontal = 12.dp, vertical = 8.dp) |
||||
|
) { |
||||
|
Text( |
||||
|
text = "Comments", |
||||
|
style = MaterialTheme.typography.titleSmall, |
||||
|
fontWeight = FontWeight.Bold, |
||||
|
modifier = Modifier.padding(bottom = 8.dp) |
||||
|
) |
||||
|
|
||||
|
// List of fetched comments |
||||
|
comments.reversed().forEach { comment -> |
||||
|
Row( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.padding(vertical = 6.dp), |
||||
|
verticalAlignment = Alignment.CenterVertically |
||||
|
) { |
||||
|
Box( |
||||
|
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 |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
Spacer(modifier = Modifier.width(8.dp)) |
||||
|
|
||||
|
Column( |
||||
|
modifier = Modifier |
||||
|
.weight(1f) |
||||
|
.background(CommentBubbleBg, shape = RoundedCornerShape(12.dp)) |
||||
|
.padding(horizontal = 12.dp, vertical = 8.dp) |
||||
|
) { |
||||
|
Text( |
||||
|
text = comment.author?.full_name ?: "Anonymous", |
||||
|
style = MaterialTheme.typography.bodySmall, |
||||
|
fontWeight = FontWeight.Bold |
||||
|
) |
||||
|
Spacer(modifier = Modifier.height(2.dp)) |
||||
|
Text( |
||||
|
text = comment.content, |
||||
|
style = MaterialTheme.typography.bodyMedium |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Spacer(modifier = Modifier.height(8.dp)) |
||||
|
|
||||
|
// Bottom Comment Input Field Bar |
||||
|
Row( |
||||
|
modifier = Modifier |
||||
|
.fillMaxWidth() |
||||
|
.background(CommentBubbleBg, shape = RoundedCornerShape(24.dp)) |
||||
|
.padding(horizontal = 12.dp, vertical = 4.dp), |
||||
|
verticalAlignment = Alignment.CenterVertically |
||||
|
) { |
||||
|
BasicTextField( |
||||
|
value = newCommentText, |
||||
|
onValueChange = { newCommentText = it }, |
||||
|
modifier = Modifier |
||||
|
.weight(1f) |
||||
|
.padding(vertical = 8.dp), |
||||
|
decorationBox = { innerTextField -> |
||||
|
if (newCommentText.isEmpty()) { |
||||
|
Text( |
||||
|
text = "Add a comment...", |
||||
|
color = Color.Gray, |
||||
|
style = MaterialTheme.typography.bodyMedium |
||||
|
) |
||||
|
} |
||||
|
innerTextField() |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
IconButton( |
||||
|
onClick = { |
||||
|
if (newCommentText.isNotBlank()) { |
||||
|
onAddCommentClick(newCommentText) |
||||
|
newCommentText = "" |
||||
|
} |
||||
|
}, |
||||
|
modifier = Modifier.size(32.dp) |
||||
|
) { |
||||
|
Icon( |
||||
|
imageVector = Icons.Default.Send, |
||||
|
contentDescription = "Send Comment", |
||||
|
tint = AccentPink, |
||||
|
modifier = Modifier.size(16.dp) |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
@Preview(showBackground = true, backgroundColor = 0xFFFFFFFF) |
||||
|
@Composable |
||||
|
fun ShowPostScreenPreview() { |
||||
|
DattingAPPTheme { |
||||
|
// Mock sample data mirroring your API schema |
||||
|
val mockPost = ShowPostResponse( |
||||
|
id = 21, |
||||
|
content = "Exploring the city and enjoying some coffee! ☕🏙️ #Android #JetpackCompose", |
||||
|
postType = "image", |
||||
|
contentFormat = "markdown", |
||||
|
feeling = "happy 😊", |
||||
|
location = Location( |
||||
|
name = "Chiattagong", |
||||
|
latitude = 27.9671, |
||||
|
longitude = -175.0599 |
||||
|
), |
||||
|
author = Author( |
||||
|
id = 49, |
||||
|
full_name = "Alex Rivera", |
||||
|
city = "Shanghai", |
||||
|
country = "China", |
||||
|
compatibilityPercentage = 95 |
||||
|
), |
||||
|
likeCount = 12, |
||||
|
commentCount = 2, |
||||
|
likedByMe = true, |
||||
|
media = listOf( |
||||
|
com.example.dattingapp.dataclass.MediaItem( |
||||
|
id = 5, |
||||
|
mediaType = "image", |
||||
|
mimeType = "image/png", |
||||
|
sizeBytes = 199619, |
||||
|
position = 0, |
||||
|
url = "https://images.unsplash.com/photo-1541167760496-1628856ab772" |
||||
|
) |
||||
|
), |
||||
|
createdAt = "2026-10-13T06:06:15" |
||||
|
) |
||||
|
|
||||
|
// Mock comments list |
||||
|
val mockComments = listOf( |
||||
|
CommentResponse( |
||||
|
id = 1, |
||||
|
postId = 21, |
||||
|
content = "This spot looks amazing! Enjoy your day! ☕", |
||||
|
author = Author( |
||||
|
id = 54, |
||||
|
full_name = "Sarah", |
||||
|
city = "Shanghai", |
||||
|
country = "China", |
||||
|
compatibilityPercentage = 100 |
||||
|
), |
||||
|
createdAt = "2026-10-13T07:45:15" |
||||
|
), |
||||
|
CommentResponse( |
||||
|
id = 2, |
||||
|
postId = 21, |
||||
|
content = "What a wonderful view, I love it!", |
||||
|
author = Author( |
||||
|
id = 55, |
||||
|
full_name = "David", |
||||
|
city = "Beijing", |
||||
|
country = "China", |
||||
|
compatibilityPercentage = 90 |
||||
|
), |
||||
|
createdAt = "2026-10-13T08:04:48" |
||||
|
) |
||||
|
) |
||||
|
|
||||
|
// Full screen container matching activity layout |
||||
|
Column( |
||||
|
modifier = Modifier |
||||
|
.fillMaxSize() |
||||
|
.verticalScroll(rememberScrollState()) |
||||
|
.padding(vertical = 16.dp) |
||||
|
) { |
||||
|
PostCard( |
||||
|
post = mockPost, |
||||
|
comments = mockComments, |
||||
|
onAddComment = { newComment -> |
||||
|
// Preview action handler |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
package com.example.dattingapp.dataclass |
||||
|
|
||||
|
import kotlinx.serialization.SerialName |
||||
|
import kotlinx.serialization.Serializable |
||||
|
|
||||
|
@Serializable |
||||
|
data class ShowPostResponse( |
||||
|
val id: Int, |
||||
|
val content: String?, |
||||
|
@SerialName("post_type") val postType: String?, |
||||
|
@SerialName("content_format") val contentFormat: String?, |
||||
|
val feeling: String?, |
||||
|
@SerialName("location") val location: Location?, |
||||
|
@SerialName("author") val author: Author? = null, |
||||
|
@SerialName("like_count") val likeCount: Int = 0, |
||||
|
@SerialName("comment_count") val commentCount: Int = 0, |
||||
|
@SerialName("liked_by_me") val likedByMe: Boolean = false, |
||||
|
val media: List<MediaItem> = emptyList(), |
||||
|
@SerialName("created_at") val createdAt: String? |
||||
|
) |
||||
|
|
||||
|
@Serializable |
||||
|
data class Location( |
||||
|
val name: String?, |
||||
|
val latitude: Double?, |
||||
|
val longitude: Double? |
||||
|
) |
||||
|
|
||||
|
@Serializable |
||||
|
data class Author( |
||||
|
val id: Int, |
||||
|
val full_name: String?, |
||||
|
val city: String?, |
||||
|
val country: String?, |
||||
|
@SerialName("compatibility_percentage") val compatibilityPercentage: Int? = 0 |
||||
|
) |
||||
|
|
||||
|
@Serializable |
||||
|
data class MediaItem( |
||||
|
val id: Long, |
||||
|
@SerialName("media_type") val mediaType: String, // "image" or "video" |
||||
|
@SerialName("mime_type") val mimeType: String?, |
||||
|
@SerialName("size_bytes") val sizeBytes: Long?, |
||||
|
val position: Int?, |
||||
|
val url: String |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@Serializable |
||||
|
data class CreateCommentRequest( |
||||
|
val content: String |
||||
|
) |
||||
|
|
||||
|
@Serializable |
||||
|
data class CommentResponse( |
||||
|
val id: Int, |
||||
|
@SerialName("post_id") val postId: Int, |
||||
|
val content: String, |
||||
|
val author: Author?, |
||||
|
@SerialName("created_at") val createdAt: String? |
||||
|
) |
||||
@ -0,0 +1,72 @@ |
|||||
|
package com.example.dattingapp.homes.profile |
||||
|
|
||||
|
import kotlinx.serialization.Serializable |
||||
|
|
||||
|
@Serializable |
||||
|
data class FriendProfileResponse( |
||||
|
val id: Int, |
||||
|
val full_name: String, |
||||
|
val gender: String?, |
||||
|
val birth_date: String?, |
||||
|
val city: String?, |
||||
|
val county: String?, |
||||
|
val country: String?, |
||||
|
val relationship_goal: String?, |
||||
|
val bio: String?, |
||||
|
val profile_image_url: String?, |
||||
|
val video_url: String?, |
||||
|
val distance_km: Double?, |
||||
|
val compatibility_percentage: Int, |
||||
|
val media: List<FriendUserMedia>, |
||||
|
val interests: List<String>, |
||||
|
val posts: List<FriendPost> |
||||
|
) |
||||
|
|
||||
|
@Serializable |
||||
|
data class FriendUserMedia( |
||||
|
val id: Int, |
||||
|
val media_type: String, |
||||
|
val url: String |
||||
|
) |
||||
|
|
||||
|
@Serializable |
||||
|
data class FriendPost( |
||||
|
val id: Int, |
||||
|
val content: String?, |
||||
|
val post_type: String, |
||||
|
val content_format: String, |
||||
|
val feeling: String?, |
||||
|
val location: FrienLocation?, |
||||
|
val author: FriendAuthor, |
||||
|
val like_count: Int, |
||||
|
val comment_count: Int, |
||||
|
val liked_by_me: Boolean, |
||||
|
val media: List<FrienPostMedia>, |
||||
|
val created_at: String |
||||
|
) |
||||
|
|
||||
|
@Serializable |
||||
|
data class FrienLocation( |
||||
|
val name: String, |
||||
|
val latitude: Double, |
||||
|
val longitude: Double |
||||
|
) |
||||
|
|
||||
|
@Serializable |
||||
|
data class FriendAuthor( |
||||
|
val id: Int, |
||||
|
val full_name: String?, |
||||
|
val city: String?, |
||||
|
val country: String?, |
||||
|
val compatibility_percentage: Int |
||||
|
) |
||||
|
|
||||
|
@Serializable |
||||
|
data class FrienPostMedia( |
||||
|
val id: Int, |
||||
|
val media_type: String, |
||||
|
val mime_type: String?, |
||||
|
val size_bytes: Long?, |
||||
|
val position: Int?, |
||||
|
val url: String |
||||
|
) |
||||
@ -0,0 +1,107 @@ |
|||||
|
package com.example.dattingapp.homes.profile |
||||
|
|
||||
|
data class UserResponse( |
||||
|
val id: Int, |
||||
|
val email_or_phone: String, |
||||
|
val status: String, |
||||
|
val profile: ProfileData, |
||||
|
val created_at: String, |
||||
|
val posts: List<Post>, |
||||
|
val liked_users: List<LikedUser>, |
||||
|
val liked_by_users: List<Any>, |
||||
|
val matches: List<Any>, |
||||
|
val liked_users_count: Int, |
||||
|
val liked_by_users_count: Int, |
||||
|
val matches_count: Int |
||||
|
) |
||||
|
|
||||
|
data class ProfileData( |
||||
|
val full_name: String, |
||||
|
val gender: String?, |
||||
|
val birth_date: String?, |
||||
|
val city: String?, |
||||
|
val county: String?, |
||||
|
val country: String?, |
||||
|
val relationship_goal: String?, |
||||
|
val bio: String?, |
||||
|
val profile_image_url: String?, |
||||
|
val video_url: String?, |
||||
|
val interests: List<String> |
||||
|
) |
||||
|
|
||||
|
data class Post( |
||||
|
val id: Int, |
||||
|
val content: String?, |
||||
|
val post_type: String, |
||||
|
val content_format: String, |
||||
|
val feeling: String?, |
||||
|
val location: Location?, |
||||
|
val author: Author, |
||||
|
val like_count: Int, |
||||
|
val comment_count: Int, |
||||
|
val liked_by_me: Boolean, |
||||
|
val media: List<Media>, |
||||
|
val created_at: String |
||||
|
) |
||||
|
|
||||
|
data class Location( |
||||
|
val name: String, |
||||
|
val latitude: Double, |
||||
|
val longitude: Double |
||||
|
) |
||||
|
|
||||
|
data class Author( |
||||
|
val id: Int, |
||||
|
val full_name: String?, |
||||
|
val city: String?, |
||||
|
val country: String?, |
||||
|
val compatibility_percentage: Int |
||||
|
) |
||||
|
|
||||
|
data class Media( |
||||
|
val id: Int, |
||||
|
val media_type: String, |
||||
|
val mime_type: String?, |
||||
|
val size_bytes: Long?, |
||||
|
val position: Int?, |
||||
|
val url: String |
||||
|
) |
||||
|
|
||||
|
data class LikedUser( |
||||
|
val id: Int, |
||||
|
val full_name: String?, |
||||
|
val gender: String?, |
||||
|
val birth_date: String?, |
||||
|
val city: String?, |
||||
|
val county: String?, |
||||
|
val country: String?, |
||||
|
val relationship_goal: String?, |
||||
|
val bio: String?, |
||||
|
val profile_image_url: String?, |
||||
|
val video_url: String?, |
||||
|
val distance_km: Double?, |
||||
|
val compatibility_percentage: Int, |
||||
|
val media: List<UserMedia> |
||||
|
) |
||||
|
|
||||
|
data class UserMedia( |
||||
|
val id: Int, |
||||
|
val media_type: String, |
||||
|
val url: String |
||||
|
) |
||||
|
data class LikedUserResponse( |
||||
|
val id: Int, |
||||
|
val full_name: String, |
||||
|
val gender: String, |
||||
|
val birth_date: String, |
||||
|
val city: String, |
||||
|
val county: String?, |
||||
|
val country: String, |
||||
|
val relationship_goal: String, |
||||
|
val bio: String, |
||||
|
val profile_image_url: String?, |
||||
|
val video_url: String?, |
||||
|
val distance_km: Double?, |
||||
|
val compatibility_percentage: Int, |
||||
|
val media: List<Media> // Adjust if media contains custom objects |
||||
|
) |
||||
Loading…
Reference in new issue