18 changed files with 1766 additions and 731 deletions
@ -0,0 +1,224 @@ |
|||
package com.example.dattingapp |
|||
|
|||
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.ArrowBack |
|||
import androidx.compose.material.icons.filled.Favorite |
|||
import androidx.compose.material3.* |
|||
import androidx.compose.runtime.* |
|||
import androidx.compose.ui.Alignment |
|||
import androidx.compose.ui.Modifier |
|||
import androidx.compose.ui.draw.clip |
|||
import androidx.compose.ui.graphics.Brush |
|||
import androidx.compose.ui.graphics.Color |
|||
import androidx.compose.ui.layout.ContentScale |
|||
import androidx.compose.ui.platform.LocalContext |
|||
import androidx.compose.ui.text.font.FontWeight |
|||
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.MatchesUiState |
|||
import com.example.dattingapp.homes.networkandapi.MatchesViewModel |
|||
import com.example.dattingapp.homes.profile.MatchListItem |
|||
import com.example.dattingapp.ui.theme.DattingAPPTheme |
|||
import java.time.LocalDate |
|||
import java.time.Period |
|||
import java.time.format.DateTimeFormatter |
|||
|
|||
class MatchesActivity : ComponentActivity() { |
|||
override fun onCreate(savedInstanceState: Bundle?) { |
|||
super.onCreate(savedInstanceState) |
|||
enableEdgeToEdge() |
|||
setContent { |
|||
DattingAPPTheme { |
|||
MatchesScreen(onBack = { finish() }) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@OptIn(ExperimentalMaterial3Api::class) |
|||
@Composable |
|||
private fun MatchesScreen( |
|||
onBack: () -> Unit, |
|||
viewModel: MatchesViewModel = viewModel() |
|||
) { |
|||
val context = LocalContext.current |
|||
val state by viewModel.state.collectAsState() |
|||
|
|||
LaunchedEffect(Unit) { viewModel.loadMatches(context) } |
|||
|
|||
Scaffold( |
|||
modifier = Modifier.fillMaxSize(), |
|||
topBar = { |
|||
TopAppBar( |
|||
title = { Text("My Matches", fontWeight = FontWeight.Bold, fontSize = 18.sp) }, |
|||
navigationIcon = { |
|||
IconButton(onClick = onBack) { |
|||
Icon(Icons.Default.ArrowBack, contentDescription = "Back") |
|||
} |
|||
}, |
|||
colors = TopAppBarDefaults.topAppBarColors( |
|||
containerColor = MaterialTheme.colorScheme.surface |
|||
) |
|||
) |
|||
} |
|||
) { innerPadding -> |
|||
Box( |
|||
modifier = Modifier |
|||
.fillMaxSize() |
|||
.padding(innerPadding), |
|||
contentAlignment = Alignment.Center |
|||
) { |
|||
when (val s = state) { |
|||
is MatchesUiState.Idle, is MatchesUiState.Loading -> CircularProgressIndicator() |
|||
|
|||
is MatchesUiState.Error -> Column( |
|||
horizontalAlignment = Alignment.CenterHorizontally, |
|||
verticalArrangement = Arrangement.spacedBy(8.dp), |
|||
modifier = Modifier.padding(16.dp) |
|||
) { |
|||
Text(s.message, color = MaterialTheme.colorScheme.error) |
|||
Button(onClick = { viewModel.loadMatches(context) }) { Text("Retry") } |
|||
} |
|||
|
|||
is MatchesUiState.Success -> { |
|||
if (s.matches.isEmpty()) { |
|||
Text("No matches yet.", color = Color.Gray) |
|||
} else { |
|||
LazyVerticalGrid( |
|||
columns = GridCells.Fixed(2), |
|||
contentPadding = PaddingValues(12.dp), |
|||
horizontalArrangement = Arrangement.spacedBy(12.dp), |
|||
verticalArrangement = Arrangement.spacedBy(12.dp), |
|||
modifier = Modifier.fillMaxSize() |
|||
) { |
|||
items(s.matches, key = { it.id }) { match -> |
|||
MatchCard(match) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Composable |
|||
private fun MatchCard(match: MatchListItem) { |
|||
val context = LocalContext.current |
|||
val user = match.user ?: return |
|||
|
|||
val name = user.full_name?.ifBlank { null } ?: "User" |
|||
val age = remember(user.birth_date) { matchAge(user.birth_date) } |
|||
val title = if (age != null) "$name, $age" else name |
|||
val matchedOn = match.matched_at?.take(10) |
|||
|
|||
Card( |
|||
shape = RoundedCornerShape(16.dp), |
|||
colors = CardDefaults.cardColors(containerColor = Color.White), |
|||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), |
|||
modifier = Modifier.fillMaxWidth() |
|||
) { |
|||
Box( |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.height(200.dp) |
|||
.clip(RoundedCornerShape(16.dp)) |
|||
.clickable { |
|||
// the OTHER person's id, so their profile opens |
|||
context.startActivity( |
|||
Intent(context, FriendProfile::class.java) |
|||
.putExtra("USER_ID", user.id) |
|||
) |
|||
} |
|||
) { |
|||
AsyncImage( |
|||
model = user.profile_image_url, |
|||
contentDescription = name, |
|||
contentScale = ContentScale.Crop, |
|||
modifier = Modifier.fillMaxSize() |
|||
) |
|||
|
|||
Box( |
|||
modifier = Modifier |
|||
.fillMaxSize() |
|||
.background( |
|||
Brush.verticalGradient( |
|||
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.7f)), |
|||
startY = 120f |
|||
) |
|||
) |
|||
) |
|||
|
|||
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 = "Match", |
|||
fontSize = 11.sp, |
|||
fontWeight = FontWeight.Bold, |
|||
color = Color(0xFF2D3436) |
|||
) |
|||
} |
|||
} |
|||
|
|||
Column( |
|||
modifier = Modifier |
|||
.align(Alignment.BottomStart) |
|||
.padding(10.dp) |
|||
) { |
|||
Text( |
|||
text = title, |
|||
color = Color.White, |
|||
fontWeight = FontWeight.Bold, |
|||
fontSize = 14.sp |
|||
) |
|||
if (matchedOn != null) { |
|||
Text( |
|||
text = "Matched $matchedOn", |
|||
color = Color.White.copy(alpha = 0.8f), |
|||
fontSize = 10.sp |
|||
) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private fun matchAge(birthDate: String?): Int? { |
|||
if (birthDate.isNullOrBlank()) return null |
|||
return try { |
|||
val d = LocalDate.parse(birthDate.take(10), DateTimeFormatter.ofPattern("yyyy-MM-dd")) |
|||
Period.between(d, LocalDate.now()).years |
|||
} catch (e: Exception) { |
|||
null |
|||
} |
|||
} |
|||
Loading…
Reference in new issue