|
|
@ -1,15 +1,18 @@ |
|
|
package com.example.dattingapp.homes |
|
|
package com.example.dattingapp.homes |
|
|
|
|
|
|
|
|
import android.os.Bundle |
|
|
import android.content.Intent |
|
|
import androidx.activity.ComponentActivity |
|
|
import androidx.compose.foundation.BorderStroke |
|
|
import androidx.activity.compose.setContent |
|
|
import androidx.media3.common.MediaItem as ExoMediaItem |
|
|
import androidx.activity.enableEdgeToEdge |
|
|
|
|
|
import androidx.compose.foundation.background |
|
|
import androidx.compose.foundation.background |
|
|
import androidx.compose.foundation.border |
|
|
import androidx.compose.foundation.border |
|
|
|
|
|
import androidx.compose.foundation.clickable |
|
|
|
|
|
import androidx.compose.foundation.indication |
|
|
import androidx.compose.foundation.layout.* |
|
|
import androidx.compose.foundation.layout.* |
|
|
import androidx.compose.foundation.lazy.LazyColumn |
|
|
import androidx.compose.foundation.lazy.LazyColumn |
|
|
import androidx.compose.foundation.lazy.LazyRow |
|
|
import androidx.compose.foundation.lazy.LazyRow |
|
|
import androidx.compose.foundation.lazy.items |
|
|
import androidx.compose.foundation.lazy.items |
|
|
|
|
|
import androidx.compose.foundation.pager.HorizontalPager |
|
|
|
|
|
import androidx.compose.foundation.pager.rememberPagerState |
|
|
import androidx.compose.foundation.shape.CircleShape |
|
|
import androidx.compose.foundation.shape.CircleShape |
|
|
import androidx.compose.foundation.shape.RoundedCornerShape |
|
|
import androidx.compose.foundation.shape.RoundedCornerShape |
|
|
import androidx.compose.material.icons.Icons |
|
|
import androidx.compose.material.icons.Icons |
|
|
@ -20,27 +23,38 @@ import androidx.compose.runtime.* |
|
|
import androidx.compose.ui.Alignment |
|
|
import androidx.compose.ui.Alignment |
|
|
import androidx.compose.ui.Modifier |
|
|
import androidx.compose.ui.Modifier |
|
|
import androidx.compose.ui.draw.clip |
|
|
import androidx.compose.ui.draw.clip |
|
|
|
|
|
import androidx.compose.ui.geometry.Offset |
|
|
import androidx.compose.ui.graphics.Brush |
|
|
import androidx.compose.ui.graphics.Brush |
|
|
import androidx.compose.ui.graphics.Color |
|
|
import androidx.compose.ui.graphics.Color |
|
|
|
|
|
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.text.TextStyle |
|
|
import androidx.compose.ui.text.font.FontWeight |
|
|
import androidx.compose.ui.text.font.FontWeight |
|
|
|
|
|
import androidx.compose.ui.text.style.TextAlign |
|
|
|
|
|
import androidx.compose.ui.text.style.TextOverflow |
|
|
import androidx.compose.ui.tooling.preview.Preview |
|
|
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.compose.ui.viewinterop.AndroidView |
|
|
|
|
|
import androidx.media3.common.Player |
|
|
|
|
|
import androidx.media3.exoplayer.ExoPlayer |
|
|
|
|
|
import androidx.media3.ui.PlayerView |
|
|
|
|
|
import coil.compose.AsyncImage |
|
|
|
|
|
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.profile.UserResponse |
|
|
import com.example.dattingapp.ui.theme.DattingAPPTheme |
|
|
import com.example.dattingapp.ui.theme.DattingAPPTheme |
|
|
|
|
|
import com.example.dattingapp.ui.theme.OverlayShadow |
|
|
|
|
|
import com.example.dattingapp.ui.theme.TextPostTextColor |
|
|
import com.example.dattingapp.ui.theme.lightChipBg |
|
|
import com.example.dattingapp.ui.theme.lightChipBg |
|
|
import com.example.dattingapp.ui.theme.primaryColor |
|
|
import com.example.dattingapp.ui.theme.primaryColor |
|
|
|
|
|
|
|
|
class MeetActivity : ComponentActivity() { |
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) { |
|
|
|
|
|
super.onCreate(savedInstanceState) |
|
|
|
|
|
enableEdgeToEdge() |
|
|
|
|
|
setContent { |
|
|
|
|
|
DattingAPPTheme { |
|
|
|
|
|
VibeCheckHomeScreen() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Data Models |
|
|
// Data Models |
|
|
data class VibeCategory( |
|
|
data class VibeCategory( |
|
|
@ -68,10 +82,13 @@ data class UserMatch( |
|
|
|
|
|
|
|
|
@Composable |
|
|
@Composable |
|
|
fun VibeCheckHomeScreen( |
|
|
fun VibeCheckHomeScreen( |
|
|
modifier: Modifier = Modifier |
|
|
feedResponse: List<ShowPostResponse> = emptyList(), // Change from MyProfileData? to MyProfileResponse? |
|
|
|
|
|
isLoading: Boolean, |
|
|
|
|
|
errorMessage: String?, |
|
|
|
|
|
onRetry: () -> Unit = {}, |
|
|
) { |
|
|
) { |
|
|
Scaffold( |
|
|
Scaffold( |
|
|
modifier = modifier.fillMaxSize(), |
|
|
modifier = Modifier.fillMaxSize(), |
|
|
topBar = { |
|
|
topBar = { |
|
|
VibeCheckTopAppBar(primaryColor) |
|
|
VibeCheckTopAppBar(primaryColor) |
|
|
} |
|
|
} |
|
|
@ -87,45 +104,22 @@ fun VibeCheckHomeScreen( |
|
|
SearchBarSection() |
|
|
SearchBarSection() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
item { |
|
|
// item { |
|
|
VibeFilterChipsSection( |
|
|
// VibeFilterChipsSection( |
|
|
selectedColor = primaryColor, |
|
|
// selectedColor = primaryColor, |
|
|
unselectedBg = lightChipBg |
|
|
// unselectedBg = lightChipBg |
|
|
) |
|
|
// ) |
|
|
} |
|
|
// } |
|
|
|
|
|
|
|
|
item { |
|
|
// One card per post from the API (replaces both hardcoded cards) |
|
|
|
|
|
items(feedResponse, key = { it.id }) { post -> |
|
|
FeaturedEventCard( |
|
|
FeaturedEventCard( |
|
|
data = EventCardData( |
|
|
post = post, |
|
|
id = "1", |
|
|
|
|
|
title = "Coffee & Pottery", |
|
|
|
|
|
description = "Get your hands dirty and your caffeine fix. Perfect for a low-pressure first date.", |
|
|
|
|
|
imageUrl = "", |
|
|
|
|
|
tagLabel = "Live", |
|
|
|
|
|
isTrending = false, |
|
|
|
|
|
tags = listOf("Creative", "Chill") |
|
|
|
|
|
), |
|
|
|
|
|
badgeColor = primaryColor |
|
|
badgeColor = primaryColor |
|
|
) |
|
|
) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
item { |
|
|
|
|
|
MostMatchingSection(primaryColor) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
item { |
|
|
|
|
|
FeaturedEventCard( |
|
|
|
|
|
data = EventCardData( |
|
|
|
|
|
id = "2", |
|
|
|
|
|
title = "Sunset Beach Volley", |
|
|
|
|
|
description = "Join a casual pickup game down at the pier. Bring sunscreen and good vibes.", |
|
|
|
|
|
imageUrl = "", |
|
|
|
|
|
tagLabel = "TRENDING", |
|
|
|
|
|
isTrending = true |
|
|
|
|
|
), |
|
|
|
|
|
badgeColor = Color(0xFF7C4DFF) |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
item { |
|
|
item { |
|
|
Spacer(modifier = Modifier.height(16.dp)) |
|
|
Spacer(modifier = Modifier.height(16.dp)) |
|
|
} |
|
|
} |
|
|
@ -266,56 +260,103 @@ private fun VibeFilterChipsSection( |
|
|
|
|
|
|
|
|
@Composable |
|
|
@Composable |
|
|
private fun FeaturedEventCard( |
|
|
private fun FeaturedEventCard( |
|
|
data: EventCardData, |
|
|
post: ShowPostResponse, |
|
|
badgeColor: Color |
|
|
badgeColor: Color |
|
|
) { |
|
|
) { |
|
|
|
|
|
val context = LocalContext.current |
|
|
|
|
|
|
|
|
|
|
|
val hasMedia = post.media.isNotEmpty() |
|
|
|
|
|
val tags = listOfNotNull(post.feeling, post.location?.name) |
|
|
|
|
|
|
|
|
|
|
|
// White over photos, dark over the white text-only card |
|
|
|
|
|
val contentColor = if (hasMedia) Color.White else TextPostTextColor |
|
|
|
|
|
val shadowStyle = if (hasMedia) TextStyle(shadow = OverlayShadow) else TextStyle.Default |
|
|
|
|
|
|
|
|
Card( |
|
|
Card( |
|
|
shape = RoundedCornerShape(16.dp), |
|
|
shape = RoundedCornerShape(16.dp), |
|
|
|
|
|
border = if (hasMedia) null else BorderStroke(1.dp, Color(0xFFEDEDED)), |
|
|
|
|
|
colors = CardDefaults.cardColors( |
|
|
|
|
|
containerColor = if (hasMedia) Color.DarkGray else Color.White |
|
|
|
|
|
), |
|
|
modifier = Modifier |
|
|
modifier = Modifier |
|
|
.fillMaxWidth() |
|
|
.fillMaxWidth() |
|
|
.height(360.dp) |
|
|
.height(360.dp) |
|
|
|
|
|
.clickable { |
|
|
|
|
|
val intent = Intent(context, ShowPostingActivity::class.java).apply { |
|
|
|
|
|
putExtra("Post_ID", post.id) |
|
|
|
|
|
} |
|
|
|
|
|
context.startActivity(intent) |
|
|
|
|
|
} |
|
|
) { |
|
|
) { |
|
|
Box(modifier = Modifier.fillMaxSize()) { |
|
|
Box(modifier = Modifier.fillMaxSize()) { |
|
|
|
|
|
|
|
|
|
|
|
// 1. Content: swipeable media, or big centered text |
|
|
|
|
|
if (hasMedia) { |
|
|
|
|
|
PostMediaPager(media = post.media, modifier = Modifier.fillMaxSize()) |
|
|
|
|
|
|
|
|
|
|
|
// 2. Top scrim (keeps the badge readable) |
|
|
Box( |
|
|
Box( |
|
|
modifier = Modifier |
|
|
modifier = Modifier |
|
|
.fillMaxSize() |
|
|
.fillMaxWidth() |
|
|
.background(Color.DarkGray) |
|
|
.height(90.dp) |
|
|
|
|
|
.align(Alignment.TopCenter) |
|
|
|
|
|
.background( |
|
|
|
|
|
Brush.verticalGradient( |
|
|
|
|
|
listOf(Color.Black.copy(alpha = 0.45f), Color.Transparent) |
|
|
|
|
|
) |
|
|
|
|
|
) |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// 3. Bottom scrim (keeps author, text and tags readable) |
|
|
Box( |
|
|
Box( |
|
|
modifier = Modifier |
|
|
modifier = Modifier |
|
|
.fillMaxSize() |
|
|
.fillMaxWidth() |
|
|
|
|
|
.height(180.dp) |
|
|
|
|
|
.align(Alignment.BottomCenter) |
|
|
.background( |
|
|
.background( |
|
|
Brush.verticalGradient( |
|
|
Brush.verticalGradient( |
|
|
colors = listOf( |
|
|
listOf(Color.Transparent, Color.Black.copy(alpha = 0.8f)) |
|
|
Color.Transparent, |
|
|
|
|
|
Color.Black.copy(alpha = 0.85f) |
|
|
|
|
|
), |
|
|
|
|
|
startY = 200f |
|
|
|
|
|
) |
|
|
) |
|
|
) |
|
|
) |
|
|
) |
|
|
) |
|
|
|
|
|
} else { |
|
|
|
|
|
Text( |
|
|
|
|
|
text = post.content.orEmpty(), |
|
|
|
|
|
color = TextPostTextColor, |
|
|
|
|
|
fontSize = if ((post.content?.length ?: 0) > 120) 18.sp else 24.sp, |
|
|
|
|
|
fontWeight = FontWeight.Bold, |
|
|
|
|
|
textAlign = TextAlign.Center, |
|
|
|
|
|
maxLines = 8, |
|
|
|
|
|
overflow = TextOverflow.Ellipsis, |
|
|
|
|
|
modifier = Modifier |
|
|
|
|
|
.align(Alignment.Center) |
|
|
|
|
|
.padding(horizontal = 32.dp) |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 4. Badge (it has its own colored background, so white text is fine) |
|
|
Surface( |
|
|
Surface( |
|
|
color = badgeColor, |
|
|
color = badgeColor, |
|
|
shape = RoundedCornerShape(12.dp), |
|
|
shape = RoundedCornerShape(12.dp), |
|
|
modifier = Modifier |
|
|
modifier = Modifier.padding(12.dp).align(Alignment.TopStart) |
|
|
.padding(12.dp) |
|
|
|
|
|
.align(Alignment.TopStart) |
|
|
|
|
|
) { |
|
|
) { |
|
|
Row( |
|
|
Row( |
|
|
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp), |
|
|
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp), |
|
|
verticalAlignment = Alignment.CenterVertically |
|
|
verticalAlignment = Alignment.CenterVertically |
|
|
) { |
|
|
) { |
|
|
Icon( |
|
|
Icon( |
|
|
imageVector = if (!data.isTrending) Icons.Default.RadioButtonChecked else Icons.Default.Bolt, |
|
|
imageVector = when (post.postType) { |
|
|
|
|
|
"video" -> Icons.Default.Videocam |
|
|
|
|
|
"image" -> Icons.Default.Image |
|
|
|
|
|
else -> Icons.Default.TextFields |
|
|
|
|
|
}, |
|
|
contentDescription = null, |
|
|
contentDescription = null, |
|
|
tint = Color.White, |
|
|
tint = Color.White, |
|
|
modifier = Modifier.size(12.dp) |
|
|
modifier = Modifier.size(12.dp) |
|
|
) |
|
|
) |
|
|
Spacer(modifier = Modifier.width(4.dp)) |
|
|
Spacer(Modifier.width(4.dp)) |
|
|
Text( |
|
|
Text( |
|
|
text = data.tagLabel, |
|
|
text = post.postType?.uppercase() ?: "POST", |
|
|
color = Color.White, |
|
|
color = Color.White, |
|
|
fontSize = 10.sp, |
|
|
fontSize = 10.sp, |
|
|
fontWeight = FontWeight.Bold |
|
|
fontWeight = FontWeight.Bold |
|
|
@ -323,69 +364,89 @@ private fun FeaturedEventCard( |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (!data.isTrending) { |
|
|
// 5. Like / comment, on a dark pill when over a photo |
|
|
Column( |
|
|
Column( |
|
|
modifier = Modifier |
|
|
modifier = Modifier |
|
|
.align(Alignment.CenterEnd) |
|
|
.align(Alignment.BottomEnd) |
|
|
.padding(end = 12.dp), |
|
|
.padding(end = 12.dp, bottom = 16.dp) |
|
|
verticalArrangement = Arrangement.spacedBy(12.dp) |
|
|
.clip(RoundedCornerShape(24.dp)) |
|
|
|
|
|
.padding(vertical = 4.dp, horizontal = 2.dp), |
|
|
|
|
|
horizontalAlignment = Alignment.CenterHorizontally, |
|
|
|
|
|
verticalArrangement = Arrangement.spacedBy(16.dp) // space BETWEEN like and comment groups |
|
|
) { |
|
|
) { |
|
|
IconButton(onClick = { }) { |
|
|
// Like + count |
|
|
|
|
|
Column(horizontalAlignment = Alignment.CenterHorizontally) { |
|
|
Icon( |
|
|
Icon( |
|
|
imageVector = Icons.Outlined.ThumbUp, |
|
|
imageVector = if (post.liked_by_me) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, |
|
|
contentDescription = "Like", |
|
|
contentDescription = "Like", |
|
|
tint = Color.White |
|
|
tint = if (post.liked_by_me) Color.Red else contentColor, |
|
|
|
|
|
modifier = Modifier |
|
|
|
|
|
.size(28.dp) |
|
|
|
|
|
.clickable { } |
|
|
|
|
|
) |
|
|
|
|
|
Text( |
|
|
|
|
|
text = "${post.like_count}", |
|
|
|
|
|
color = contentColor, |
|
|
|
|
|
fontSize = 12.sp, |
|
|
|
|
|
style = shadowStyle |
|
|
) |
|
|
) |
|
|
} |
|
|
} |
|
|
IconButton(onClick = { }) { |
|
|
|
|
|
|
|
|
// Comment + count |
|
|
|
|
|
Column(horizontalAlignment = Alignment.CenterHorizontally) { |
|
|
Icon( |
|
|
Icon( |
|
|
imageVector = Icons.Outlined.ChatBubbleOutline, |
|
|
imageVector = Icons.Outlined.ChatBubbleOutline, |
|
|
contentDescription = "Comment", |
|
|
contentDescription = "Comment", |
|
|
tint = Color.White |
|
|
tint = contentColor, |
|
|
|
|
|
modifier = Modifier |
|
|
|
|
|
.size(28.dp) |
|
|
|
|
|
.clickable { } |
|
|
) |
|
|
) |
|
|
} |
|
|
Text( |
|
|
IconButton(onClick = { }) { |
|
|
text = "${post.comment_count}", |
|
|
Icon( |
|
|
color = contentColor, |
|
|
imageVector = Icons.Outlined.BookmarkBorder, |
|
|
fontSize = 12.sp, |
|
|
contentDescription = "Save", |
|
|
style = shadowStyle |
|
|
tint = Color.White |
|
|
|
|
|
) |
|
|
) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Column( |
|
|
// 6. Bottom info |
|
|
modifier = Modifier |
|
|
Column(modifier = Modifier.align(Alignment.BottomStart) |
|
|
.align(Alignment.BottomStart) |
|
|
.padding(start = 16.dp, top = 16.dp, bottom = 16.dp, end = 72.dp)) { |
|
|
.padding(16.dp) |
|
|
|
|
|
) { |
|
|
|
|
|
Text( |
|
|
Text( |
|
|
text = data.title, |
|
|
text = post.author?.full_name ?: "Anonymous", |
|
|
color = Color.White, |
|
|
color = contentColor, |
|
|
fontSize = 20.sp, |
|
|
fontSize = 20.sp, |
|
|
fontWeight = FontWeight.Bold |
|
|
fontWeight = FontWeight.Bold, |
|
|
|
|
|
style = shadowStyle |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
Spacer(modifier = Modifier.height(4.dp)) |
|
|
if (hasMedia && !post.content.isNullOrBlank()) { |
|
|
|
|
|
Spacer(Modifier.height(4.dp)) |
|
|
Text( |
|
|
Text( |
|
|
text = data.description, |
|
|
text = post.content, |
|
|
color = Color.LightGray, |
|
|
color = Color.White, |
|
|
fontSize = 12.sp, |
|
|
fontSize = 13.sp, |
|
|
lineHeight = 16.sp |
|
|
lineHeight = 17.sp, |
|
|
|
|
|
maxLines = 1, |
|
|
|
|
|
overflow = TextOverflow.Ellipsis, |
|
|
|
|
|
style = shadowStyle |
|
|
) |
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (data.tags.isNotEmpty()) { |
|
|
if (tags.isNotEmpty()) { |
|
|
Spacer(modifier = Modifier.height(8.dp)) |
|
|
Spacer(Modifier.height(8.dp)) |
|
|
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { |
|
|
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { |
|
|
data.tags.forEach { tag -> |
|
|
tags.forEach { tag -> |
|
|
Surface( |
|
|
Surface( |
|
|
color = Color.White.copy(alpha = 0.2f), |
|
|
color = if (hasMedia) Color.Black.copy(alpha = 0.45f) |
|
|
|
|
|
else Color(0xFFF1F1F1), |
|
|
shape = RoundedCornerShape(6.dp) |
|
|
shape = RoundedCornerShape(6.dp) |
|
|
) { |
|
|
) { |
|
|
Text( |
|
|
Text( |
|
|
text = tag, |
|
|
text = tag, |
|
|
color = Color.White, |
|
|
color = contentColor, |
|
|
fontSize = 10.sp, |
|
|
fontSize = 10.sp, |
|
|
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) |
|
|
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) |
|
|
) |
|
|
) |
|
|
@ -397,6 +458,100 @@ private fun FeaturedEventCard( |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
@Composable |
|
|
|
|
|
private fun PostMediaPager(media: List<MediaItem>, modifier: Modifier = Modifier) { |
|
|
|
|
|
val pagerState = rememberPagerState(pageCount = { media.size }) |
|
|
|
|
|
|
|
|
|
|
|
Box(modifier) { |
|
|
|
|
|
HorizontalPager(state = pagerState, modifier = Modifier.fillMaxSize()) { page -> |
|
|
|
|
|
val item = media[page] |
|
|
|
|
|
if (item.mediaType == "video") { |
|
|
|
|
|
PostVideoPlayer( |
|
|
|
|
|
url = item.url, |
|
|
|
|
|
isActive = pagerState.currentPage == page |
|
|
|
|
|
) |
|
|
|
|
|
} else { |
|
|
|
|
|
AsyncImage( |
|
|
|
|
|
model = item.url, |
|
|
|
|
|
contentDescription = "Post media ${page + 1}", |
|
|
|
|
|
contentScale = ContentScale.Crop, |
|
|
|
|
|
modifier = Modifier.fillMaxSize() |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// "1/3" counter, only when there is more than one item |
|
|
|
|
|
if (media.size > 1) { |
|
|
|
|
|
Surface( |
|
|
|
|
|
color = Color.Black.copy(alpha = 0.5f), |
|
|
|
|
|
shape = RoundedCornerShape(12.dp), |
|
|
|
|
|
modifier = Modifier.align(Alignment.TopEnd).padding(12.dp) |
|
|
|
|
|
) { |
|
|
|
|
|
Text( |
|
|
|
|
|
text = "${pagerState.currentPage + 1}/${media.size}", |
|
|
|
|
|
color = Color.White, |
|
|
|
|
|
fontSize = 11.sp, |
|
|
|
|
|
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Composable |
|
|
|
|
|
private fun PostVideoPlayer(url: String, isActive: Boolean) { |
|
|
|
|
|
val context = LocalContext.current |
|
|
|
|
|
var isPlaying by remember { mutableStateOf(false) } |
|
|
|
|
|
|
|
|
|
|
|
val player = remember(url) { |
|
|
|
|
|
ExoPlayer.Builder(context).build().apply { |
|
|
|
|
|
setMediaItem(ExoMediaItem.fromUri(url)) |
|
|
|
|
|
repeatMode = Player.REPEAT_MODE_ONE |
|
|
|
|
|
prepare() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Pause when the user swipes to another page |
|
|
|
|
|
LaunchedEffect(isActive) { |
|
|
|
|
|
if (!isActive) player.pause() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
DisposableEffect(player) { |
|
|
|
|
|
val listener = object : Player.Listener { |
|
|
|
|
|
override fun onIsPlayingChanged(playing: Boolean) { isPlaying = playing } |
|
|
|
|
|
} |
|
|
|
|
|
player.addListener(listener) |
|
|
|
|
|
onDispose { |
|
|
|
|
|
player.removeListener(listener) |
|
|
|
|
|
player.release() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Box( |
|
|
|
|
|
modifier = Modifier |
|
|
|
|
|
.fillMaxSize() |
|
|
|
|
|
.clickable { if (player.isPlaying) player.pause() else player.play() }, |
|
|
|
|
|
contentAlignment = Alignment.Center |
|
|
|
|
|
) { |
|
|
|
|
|
AndroidView( |
|
|
|
|
|
factory = { ctx -> |
|
|
|
|
|
PlayerView(ctx).apply { |
|
|
|
|
|
this.player = player |
|
|
|
|
|
useController = false |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
modifier = Modifier.fillMaxSize() |
|
|
|
|
|
) |
|
|
|
|
|
if (!isPlaying) { |
|
|
|
|
|
Icon( |
|
|
|
|
|
imageVector = Icons.Default.PlayCircle, |
|
|
|
|
|
contentDescription = "Play", |
|
|
|
|
|
tint = Color.White.copy(alpha = 0.9f), |
|
|
|
|
|
modifier = Modifier.size(64.dp) |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
@Composable |
|
|
@Composable |
|
|
private fun MostMatchingSection( |
|
|
private fun MostMatchingSection( |
|
|
@ -460,19 +615,90 @@ private fun MostMatchingSection( |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@Preview( |
|
|
private val mockFeed = listOf( |
|
|
showBackground = true, |
|
|
ShowPostResponse( |
|
|
showSystemUi = true, |
|
|
id = 1, |
|
|
widthDp = 360, |
|
|
content = "Exploring the city and enjoying some coffee! ☕🏙️", |
|
|
heightDp = 800 |
|
|
postType = "image", |
|
|
|
|
|
contentFormat = "markdown", |
|
|
|
|
|
feeling = "happy 😊", |
|
|
|
|
|
location = Location(name = "Shanghai", latitude = 31.23, longitude = 121.47), |
|
|
|
|
|
author = Author( |
|
|
|
|
|
id = 49, |
|
|
|
|
|
full_name = "Alex Rivera", |
|
|
|
|
|
city = "Shanghai", |
|
|
|
|
|
country = "China", |
|
|
|
|
|
compatibilityPercentage = 95 |
|
|
|
|
|
), |
|
|
|
|
|
like_count = 12, |
|
|
|
|
|
comment_count = 3, |
|
|
|
|
|
liked_by_me = true, |
|
|
|
|
|
media = listOf( |
|
|
|
|
|
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" |
|
|
|
|
|
), |
|
|
|
|
|
ShowPostResponse( |
|
|
|
|
|
id = 2, |
|
|
|
|
|
content = "Text only post with no media.", |
|
|
|
|
|
postType = "text", |
|
|
|
|
|
contentFormat = "markdown", |
|
|
|
|
|
feeling = null, |
|
|
|
|
|
location = null, |
|
|
|
|
|
author = Author( |
|
|
|
|
|
id = 54, |
|
|
|
|
|
full_name = "Sarah", |
|
|
|
|
|
city = "Beijing", |
|
|
|
|
|
country = "China", |
|
|
|
|
|
compatibilityPercentage = 88 |
|
|
|
|
|
), |
|
|
|
|
|
like_count = 0, |
|
|
|
|
|
comment_count = 0, |
|
|
|
|
|
liked_by_me = false, |
|
|
|
|
|
media = emptyList(), |
|
|
|
|
|
createdAt = "2026-10-13T07:45:15" |
|
|
|
|
|
) |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
@Preview(showBackground = true, showSystemUi = true, widthDp = 360, heightDp = 800) |
|
|
@Composable |
|
|
@Composable |
|
|
fun VibeCheckHomeScreenPreview() { |
|
|
fun VibeCheckHomeScreenPreview() { |
|
|
MaterialTheme { |
|
|
DattingAPPTheme { |
|
|
Surface(modifier = Modifier.fillMaxSize()) { |
|
|
|
|
|
VibeCheckHomeScreen( |
|
|
VibeCheckHomeScreen( |
|
|
modifier = Modifier.fillMaxSize() |
|
|
feedResponse = mockFeed, |
|
|
|
|
|
isLoading = false, |
|
|
|
|
|
errorMessage = null |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Preview(showBackground = true, widthDp = 360, heightDp = 800) |
|
|
|
|
|
@Composable |
|
|
|
|
|
fun VibeCheckHomeScreenLoadingPreview() { |
|
|
|
|
|
DattingAPPTheme { |
|
|
|
|
|
VibeCheckHomeScreen( |
|
|
|
|
|
feedResponse = emptyList(), |
|
|
|
|
|
isLoading = true, |
|
|
|
|
|
errorMessage = null |
|
|
) |
|
|
) |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Preview(showBackground = true, widthDp = 360, heightDp = 800) |
|
|
|
|
|
@Composable |
|
|
|
|
|
fun VibeCheckHomeScreenErrorPreview() { |
|
|
|
|
|
DattingAPPTheme { |
|
|
|
|
|
VibeCheckHomeScreen( |
|
|
|
|
|
feedResponse = emptyList(), |
|
|
|
|
|
isLoading = false, |
|
|
|
|
|
errorMessage = "Failed to load feed (401)" |
|
|
|
|
|
) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |