10 changed files with 1418 additions and 96 deletions
@ -0,0 +1,441 @@ |
|||
package com.example.dattingapp |
|||
|
|||
import android.content.Context |
|||
import android.media.MediaMetadataRetriever |
|||
import android.net.Uri |
|||
import android.os.Bundle |
|||
import android.widget.Toast |
|||
import androidx.activity.ComponentActivity |
|||
import androidx.activity.compose.BackHandler |
|||
import androidx.activity.compose.rememberLauncherForActivityResult |
|||
import androidx.activity.compose.setContent |
|||
import androidx.activity.enableEdgeToEdge |
|||
import androidx.activity.result.PickVisualMediaRequest |
|||
import androidx.activity.result.contract.ActivityResultContracts |
|||
import androidx.compose.foundation.background |
|||
import androidx.compose.foundation.border |
|||
import androidx.compose.foundation.layout.* |
|||
import androidx.compose.foundation.layout.statusBarsPadding |
|||
import androidx.compose.foundation.shape.CircleShape |
|||
import androidx.compose.foundation.shape.RoundedCornerShape |
|||
import androidx.compose.foundation.text.BasicTextField |
|||
import androidx.compose.material.icons.Icons |
|||
import androidx.compose.material.icons.filled.Close |
|||
import androidx.compose.material.icons.filled.PhotoLibrary |
|||
import androidx.compose.material.icons.filled.Send |
|||
import androidx.compose.material.icons.filled.SwapHoriz |
|||
import androidx.compose.material.icons.filled.Videocam |
|||
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.graphics.SolidColor |
|||
import androidx.compose.ui.platform.LocalContext |
|||
import androidx.compose.ui.text.TextStyle |
|||
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.core.content.FileProvider |
|||
import androidx.lifecycle.viewmodel.compose.viewModel |
|||
import com.example.dattingapp.homes.VideoPlayerItem |
|||
import com.example.dattingapp.homes.networkandapi.CreatePostRequest |
|||
import com.example.dattingapp.homes.networkandapi.CreatePostUiState |
|||
import com.example.dattingapp.homes.networkandapi.CreatePostViewModel |
|||
import com.example.dattingapp.homes.networkandapi.PostMediaViewModel |
|||
import com.example.dattingapp.ui.theme.DattingAPPTheme |
|||
import kotlinx.coroutines.async |
|||
import java.io.File |
|||
|
|||
private const val MAX_REEL_SECONDS = 60 |
|||
private const val MAX_CAPTION = 300 |
|||
|
|||
class CreateReelActivity : ComponentActivity() { |
|||
override fun onCreate(savedInstanceState: Bundle?) { |
|||
super.onCreate(savedInstanceState) |
|||
enableEdgeToEdge() |
|||
setContent { |
|||
DattingAPPTheme { |
|||
CreateReelScreen(onClose = { finish() }) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Composable |
|||
fun CreateReelScreen(onClose: () -> Unit) { |
|||
val context = LocalContext.current |
|||
val createVm: CreatePostViewModel = viewModel() |
|||
val mediaVm: PostMediaViewModel = viewModel() |
|||
val uiState by createVm.cretePpostUiState.collectAsState() |
|||
|
|||
var videoUri by remember { mutableStateOf<Uri?>(null) } |
|||
var caption by remember { mutableStateOf("") } |
|||
var cameraUri by remember { mutableStateOf<Uri?>(null) } |
|||
var askDiscard by remember { mutableStateOf(false) } |
|||
|
|||
val isPosting = uiState is CreatePostUiState.Loading || uiState is CreatePostUiState.Success |
|||
|
|||
// Accept a video only if it is within the length limit |
|||
fun accept(uri: Uri) { |
|||
if (reelDurationSeconds(context, uri) > MAX_REEL_SECONDS) { |
|||
Toast.makeText(context, "Reels can be up to $MAX_REEL_SECONDS seconds", Toast.LENGTH_LONG).show() |
|||
} else { |
|||
videoUri = uri |
|||
} |
|||
} |
|||
|
|||
val pickLauncher = rememberLauncherForActivityResult( |
|||
ActivityResultContracts.PickVisualMedia() |
|||
) { uri -> if (uri != null) accept(uri) } |
|||
|
|||
val recordLauncher = rememberLauncherForActivityResult( |
|||
ActivityResultContracts.CaptureVideo() |
|||
) { ok -> if (ok) cameraUri?.let { accept(it) } } |
|||
|
|||
fun startRecording() { |
|||
val file = File.createTempFile("reel_", ".mp4", context.cacheDir) |
|||
val uri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file) |
|||
cameraUri = uri |
|||
recordLauncher.launch(uri) |
|||
} |
|||
|
|||
// Create the post, then upload the video (same flow as CreateVibePostScreen) |
|||
LaunchedEffect(uiState) { |
|||
when (val s = uiState) { |
|||
is CreatePostUiState.Success -> { |
|||
try { |
|||
val uri = videoUri |
|||
if (uri != null) { |
|||
val err = mediaVm.uploadMedia(context, s.postData.id, 0, uri) |
|||
if (err != null) Toast.makeText(context, err, Toast.LENGTH_LONG).show() |
|||
} |
|||
onClose() |
|||
} finally { |
|||
createVm.reset() |
|||
} |
|||
} |
|||
is CreatePostUiState.Error -> { |
|||
Toast.makeText(context, s.message, Toast.LENGTH_LONG).show() |
|||
createVm.reset() |
|||
} |
|||
else -> {} |
|||
} |
|||
} |
|||
|
|||
BackHandler(enabled = videoUri != null && !isPosting) { askDiscard = true } |
|||
|
|||
Box( |
|||
modifier = Modifier |
|||
.fillMaxSize() |
|||
.background(Color.Black) |
|||
) { |
|||
val uri = videoUri |
|||
if (uri == null) { |
|||
// ---------------- STEP 1: CAPTURE ---------------- |
|||
CaptureStep( |
|||
onClose = onClose, |
|||
onRecord = { startRecording() }, |
|||
onUpload = { |
|||
pickLauncher.launch( |
|||
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly) |
|||
) |
|||
} |
|||
) |
|||
} else { |
|||
// ---------------- STEP 2: PREVIEW + DETAILS ---------------- |
|||
VideoPlayerItem(videoUri = uri) // fills the screen |
|||
|
|||
// Bottom scrim |
|||
Box( |
|||
modifier = Modifier |
|||
.align(Alignment.BottomCenter) |
|||
.fillMaxWidth() |
|||
.height(300.dp) |
|||
.background( |
|||
Brush.verticalGradient(listOf(Color.Transparent, Color.Black.copy(alpha = 0.85f))) |
|||
) |
|||
) |
|||
|
|||
// Top bar |
|||
Row( |
|||
modifier = Modifier |
|||
.align(Alignment.TopCenter) |
|||
.fillMaxWidth() |
|||
.statusBarsPadding() |
|||
.padding(12.dp), |
|||
horizontalArrangement = Arrangement.SpaceBetween, |
|||
verticalAlignment = Alignment.CenterVertically |
|||
) { |
|||
RoundIconButton(Icons.Default.Close, "Discard", enabled = !isPosting) { askDiscard = true } |
|||
Surface( |
|||
onClick = { |
|||
if (!isPosting) pickLauncher.launch( |
|||
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly) |
|||
) |
|||
}, |
|||
shape = RoundedCornerShape(20.dp), |
|||
color = Color.Black.copy(alpha = 0.45f) |
|||
) { |
|||
Row( |
|||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), |
|||
verticalAlignment = Alignment.CenterVertically |
|||
) { |
|||
Icon(Icons.Default.SwapHoriz, null, tint = Color.White, modifier = Modifier.size(16.dp)) |
|||
Spacer(Modifier.width(6.dp)) |
|||
Text("Change video", color = Color.White, fontSize = 12.sp) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Caption + Share |
|||
Column( |
|||
modifier = Modifier |
|||
.align(Alignment.BottomCenter) |
|||
.fillMaxWidth() |
|||
.navigationBarsPadding() |
|||
.imePadding() |
|||
.padding(16.dp) |
|||
) { |
|||
Box( |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.clip(RoundedCornerShape(16.dp)) |
|||
.background(Color.White.copy(alpha = 0.14f)) |
|||
.border(1.dp, Color.White.copy(alpha = 0.25f), RoundedCornerShape(16.dp)) |
|||
.padding(14.dp) |
|||
) { |
|||
if (caption.isEmpty()) { |
|||
Text("Write a caption...", color = Color.White.copy(alpha = 0.6f), fontSize = 15.sp) |
|||
} |
|||
BasicTextField( |
|||
value = caption, |
|||
onValueChange = { if (it.length <= MAX_CAPTION) caption = it }, |
|||
enabled = !isPosting, |
|||
textStyle = TextStyle(color = Color.White, fontSize = 15.sp), |
|||
cursorBrush = SolidColor(Color.White), |
|||
maxLines = 4, |
|||
modifier = Modifier.fillMaxWidth() |
|||
) |
|||
} |
|||
Text( |
|||
text = "${caption.length}/$MAX_CAPTION", |
|||
color = Color.White.copy(alpha = 0.6f), |
|||
fontSize = 11.sp, |
|||
modifier = Modifier |
|||
.align(Alignment.End) |
|||
.padding(top = 4.dp, bottom = 10.dp) |
|||
) |
|||
|
|||
Button( |
|||
enabled = !isPosting, |
|||
onClick = { |
|||
createVm.createPost( |
|||
context, |
|||
CreatePostRequest( |
|||
content = caption.trim().ifBlank { " " }, |
|||
post_type = "video", // change to "reel" if your backend has it |
|||
content_format = "plain", |
|||
feeling = null, |
|||
location = null |
|||
) |
|||
) |
|||
}, |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.height(50.dp), |
|||
shape = RoundedCornerShape(25.dp), |
|||
colors = ButtonDefaults.buttonColors( |
|||
containerColor = Color(0xFFE91E63), |
|||
disabledContainerColor = Color(0xFFE91E63).copy(alpha = 0.5f) |
|||
) |
|||
) { |
|||
Text( |
|||
if (isPosting) "Posting..." else "Share Reel", |
|||
fontWeight = FontWeight.Bold, |
|||
fontSize = 16.sp, |
|||
color = Color.White |
|||
) |
|||
Spacer(Modifier.width(8.dp)) |
|||
Icon(Icons.Default.Send, null, tint = Color.White, modifier = Modifier.size(18.dp)) |
|||
} |
|||
} |
|||
|
|||
// ---------------- STEP 3: POSTING PROGRESS ---------------- |
|||
if (isPosting) { |
|||
LinearProgressIndicator( |
|||
modifier = Modifier |
|||
.align(Alignment.TopCenter) |
|||
.fillMaxWidth() |
|||
.statusBarsPadding() |
|||
.height(3.dp), |
|||
color = Color(0xFFE91E63), |
|||
trackColor = Color.White.copy(alpha = 0.2f) |
|||
) |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (askDiscard) { |
|||
AlertDialog( |
|||
onDismissRequest = { askDiscard = false }, |
|||
title = { Text("Discard reel?") }, |
|||
text = { Text("Your video and caption will be lost.") }, |
|||
confirmButton = { |
|||
TextButton(onClick = { |
|||
askDiscard = false |
|||
videoUri = null |
|||
caption = "" |
|||
}) { Text("Discard", color = Color.Red) } |
|||
}, |
|||
dismissButton = { TextButton(onClick = { askDiscard = false }) { Text("Keep editing") } } |
|||
) |
|||
} |
|||
} |
|||
|
|||
@Composable |
|||
private fun CaptureStep( |
|||
onClose: () -> Unit, |
|||
onRecord: () -> Unit, |
|||
onUpload: () -> Unit |
|||
) { |
|||
Box(modifier = Modifier.fillMaxSize()) { |
|||
RoundIconButton( |
|||
icon = Icons.Default.Close, |
|||
description = "Close", |
|||
modifier = Modifier |
|||
.align(Alignment.TopStart) |
|||
.statusBarsPadding() |
|||
.padding(12.dp), |
|||
onClick = onClose |
|||
) |
|||
|
|||
Column( |
|||
modifier = Modifier |
|||
.align(Alignment.Center) |
|||
.padding(horizontal = 32.dp), |
|||
horizontalAlignment = Alignment.CenterHorizontally |
|||
) { |
|||
Box( |
|||
modifier = Modifier |
|||
.size(88.dp) |
|||
.clip(CircleShape) |
|||
.background( |
|||
Brush.linearGradient(listOf(Color(0xFFC2185B), Color(0xFF7B1FA2))) |
|||
), |
|||
contentAlignment = Alignment.Center |
|||
) { |
|||
Icon(Icons.Default.Videocam, null, tint = Color.White, modifier = Modifier.size(40.dp)) |
|||
} |
|||
Spacer(Modifier.height(20.dp)) |
|||
Text("Create a reel", color = Color.White, fontSize = 24.sp, fontWeight = FontWeight.Bold) |
|||
Spacer(Modifier.height(6.dp)) |
|||
Text( |
|||
"Share a short video up to $MAX_REEL_SECONDS seconds", |
|||
color = Color.White.copy(alpha = 0.7f), |
|||
fontSize = 14.sp |
|||
) |
|||
Spacer(Modifier.height(32.dp)) |
|||
|
|||
Button( |
|||
onClick = onRecord, |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.height(52.dp), |
|||
shape = RoundedCornerShape(26.dp), |
|||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE91E63)) |
|||
) { |
|||
Icon(Icons.Default.Videocam, null, tint = Color.White) |
|||
Spacer(Modifier.width(8.dp)) |
|||
Text("Record", fontWeight = FontWeight.Bold, fontSize = 16.sp, color = Color.White) |
|||
} |
|||
Spacer(Modifier.height(12.dp)) |
|||
OutlinedButton( |
|||
onClick = onUpload, |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.height(52.dp), |
|||
shape = RoundedCornerShape(26.dp), |
|||
border = androidx.compose.foundation.BorderStroke(1.dp, Color.White.copy(alpha = 0.5f)) |
|||
) { |
|||
Icon(Icons.Default.PhotoLibrary, null, tint = Color.White) |
|||
Spacer(Modifier.width(8.dp)) |
|||
Text("Upload from gallery", fontSize = 16.sp, color = Color.White) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Composable |
|||
private fun RoundIconButton( |
|||
icon: androidx.compose.ui.graphics.vector.ImageVector, |
|||
description: String, |
|||
modifier: Modifier = Modifier, |
|||
enabled: Boolean = true, |
|||
onClick: () -> Unit |
|||
) { |
|||
IconButton( |
|||
onClick = onClick, |
|||
enabled = enabled, |
|||
modifier = modifier |
|||
.size(40.dp) |
|||
.clip(CircleShape) |
|||
.background(Color.Black.copy(alpha = 0.45f)) |
|||
) { |
|||
Icon(icon, description, tint = Color.White, modifier = Modifier.size(20.dp)) |
|||
} |
|||
} |
|||
|
|||
private fun reelDurationSeconds(context: Context, uri: Uri): Long { |
|||
return try { |
|||
val r = MediaMetadataRetriever() |
|||
r.setDataSource(context, uri) |
|||
val ms = r.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() ?: 0L |
|||
r.release() |
|||
ms / 1000 |
|||
} catch (e: Exception) { |
|||
0L |
|||
} |
|||
} |
|||
|
|||
|
|||
// 1. Preview for the initial Capture step (No video selected yet) |
|||
@Preview(showBackground = true, backgroundColor = 0xFF000000) |
|||
@Composable |
|||
fun CaptureStepPreview() { |
|||
DattingAPPTheme { |
|||
Box( |
|||
modifier = Modifier |
|||
.fillMaxSize() |
|||
.background(Color.Black) |
|||
) { |
|||
CaptureStep( |
|||
onClose = {}, |
|||
onRecord = {}, |
|||
onUpload = {} |
|||
) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 2. Preview for the Capture step using a Dark/Light device frame |
|||
@Preview( |
|||
name = "Phone Preview", |
|||
showBackground = true, |
|||
backgroundColor = 0xFF000000 |
|||
) |
|||
@Composable |
|||
fun CreateReelScreenCapturePreview() { |
|||
DattingAPPTheme { |
|||
// Since the real screen uses ViewModels that require a live backend/context, |
|||
// you can preview the CaptureStep layout directly. |
|||
CaptureStep( |
|||
onClose = {}, |
|||
onRecord = {}, |
|||
onUpload = {} |
|||
) |
|||
} |
|||
} |
|||
@ -0,0 +1,805 @@ |
|||
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.Scaffold |
|||
import androidx.compose.material3.Text |
|||
import androidx.compose.runtime.Composable |
|||
import com.example.dattingapp.ui.theme.DattingAPPTheme |
|||
import android.content.Intent |
|||
import androidx.compose.foundation.BorderStroke |
|||
import androidx.compose.foundation.background |
|||
import androidx.compose.foundation.border |
|||
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.filled.Add |
|||
import androidx.compose.material.icons.filled.ArrowBack |
|||
import androidx.compose.material.icons.filled.Check |
|||
import androidx.compose.material.icons.filled.Close |
|||
import androidx.compose.material.icons.filled.Favorite |
|||
import androidx.compose.material.icons.filled.KeyboardArrowDown |
|||
import androidx.compose.material.icons.filled.KeyboardArrowUp |
|||
import androidx.compose.material.icons.filled.Videocam |
|||
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 |
|||
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.tooling.preview.Preview |
|||
import androidx.compose.ui.unit.dp |
|||
import androidx.compose.ui.unit.sp |
|||
import coil.compose.AsyncImage |
|||
import com.example.dattingapp.R |
|||
import com.example.dattingapp.ui.theme.* |
|||
import android.widget.Toast |
|||
import androidx.lifecycle.viewmodel.compose.viewModel |
|||
import com.example.dattingapp.homes.networkandapi.EditProfileUiState |
|||
import com.example.dattingapp.homes.networkandapi.EditProfileViewModel |
|||
import com.example.dattingapp.homes.networkandapi.ProfileUiState |
|||
import com.example.dattingapp.homes.networkandapi.ProfileViewModel |
|||
import com.example.dattingapp.homes.profile.ProfileData |
|||
import com.example.dattingapp.homes.profile.UpdateProfileRequest |
|||
import androidx.compose.foundation.layout.ExperimentalLayoutApi |
|||
class EditMyProfileActivty : ComponentActivity() { |
|||
override fun onCreate(savedInstanceState: Bundle?) { |
|||
super.onCreate(savedInstanceState) |
|||
enableEdgeToEdge() |
|||
setContent { |
|||
DattingAPPTheme { |
|||
val context = LocalContext.current |
|||
val profileVm: ProfileViewModel = viewModel() |
|||
val editVm: EditProfileViewModel = viewModel() |
|||
val profileState by profileVm.profileUiState.collectAsState() |
|||
val saveState by editVm.state.collectAsState() |
|||
|
|||
LaunchedEffect(Unit) { profileVm.profileResponse(context) } |
|||
|
|||
LaunchedEffect(saveState) { |
|||
when (val s = saveState) { |
|||
is EditProfileUiState.Saved -> { |
|||
Toast.makeText(context, "Profile updated", Toast.LENGTH_SHORT).show() |
|||
editVm.reset() |
|||
finish() |
|||
} |
|||
is EditProfileUiState.Error -> { |
|||
Toast.makeText(context, s.message, Toast.LENGTH_LONG).show() |
|||
editVm.reset() |
|||
} |
|||
else -> {} |
|||
} |
|||
} |
|||
|
|||
when (val s = profileState) { |
|||
is ProfileUiState.Success -> EditProfileScreen( |
|||
profile = s.profileData.profile, |
|||
isSaving = saveState is EditProfileUiState.Saving, |
|||
onBackClick = { finish() }, |
|||
onSaveClick = { request -> editVm.save(context, request) } |
|||
) |
|||
is ProfileUiState.Error -> Box(Modifier.fillMaxSize(), Alignment.Center) { |
|||
Text(s.message, color = Color.Red) |
|||
} |
|||
else -> Box(Modifier.fillMaxSize(), Alignment.Center) { |
|||
CircularProgressIndicator() |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@OptIn(ExperimentalMaterial3Api::class) |
|||
@Composable |
|||
fun EditProfileScreen( |
|||
profile: ProfileData? = null, |
|||
isSaving: Boolean = false, |
|||
onBackClick: () -> Unit = {}, |
|||
onSaveClick: (UpdateProfileRequest) -> Unit = {} |
|||
) { |
|||
val context = LocalContext.current |
|||
val scrollState = rememberScrollState() |
|||
|
|||
// Prefilled from the server response |
|||
var fullName by remember(profile) { mutableStateOf(profile?.full_name.orEmpty()) } |
|||
var birthDate by remember(profile) { mutableStateOf(profile?.birth_date.orEmpty()) } |
|||
var gender by remember(profile) { mutableStateOf(profile?.gender.orEmpty()) } |
|||
var city by remember(profile) { mutableStateOf(profile?.city.orEmpty()) } |
|||
var country by remember(profile) { mutableStateOf(profile?.country.orEmpty()) } |
|||
var datingIntent by remember(profile) { mutableStateOf(profile?.relationship_goal.orEmpty()) } |
|||
var bioText by remember(profile) { mutableStateOf(profile?.bio.orEmpty()) } |
|||
val interests = remember(profile) { |
|||
mutableStateListOf<String>().apply { addAll(profile?.interests.orEmpty()) } |
|||
} |
|||
var newInterest by remember { mutableStateOf("") } |
|||
|
|||
var isBeliefsExpanded by rememberSaveable { mutableStateOf(true) } |
|||
var selectedBelief by rememberSaveable { mutableStateOf("Spiritual / Agnostic") } |
|||
|
|||
val submit: () -> Unit = { |
|||
val date = birthDate.trim() |
|||
val dateOk = date.isEmpty() || try { |
|||
java.time.LocalDate.parse(date); true |
|||
} catch (e: Exception) { false } |
|||
|
|||
if (!dateOk) { |
|||
Toast.makeText(context, "Birthday must look like 1998-08-12", Toast.LENGTH_LONG).show() |
|||
} else { |
|||
onSaveClick( |
|||
UpdateProfileRequest( |
|||
full_name = fullName.trim().ifBlank { null }, |
|||
gender = gender.trim().ifBlank { null }, |
|||
birth_date = date.ifBlank { null }, |
|||
city = city.trim().ifBlank { null }, |
|||
country = country.trim().ifBlank { null }, |
|||
relationship_goal = datingIntent.ifBlank { null }, |
|||
bio = bioText.trim().ifBlank { null }, |
|||
interests = interests.toList() |
|||
) |
|||
) |
|||
} |
|||
} |
|||
|
|||
Scaffold( |
|||
topBar = { |
|||
TopAppBar( |
|||
title = { Text("Edit Profile", fontWeight = FontWeight.Bold, fontSize = 18.sp, color = textDark) }, |
|||
navigationIcon = { |
|||
IconButton(onClick = onBackClick) { |
|||
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back", tint = textDark) |
|||
} |
|||
}, |
|||
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.White) |
|||
) |
|||
}, |
|||
bottomBar = { |
|||
Surface( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
color = Color.White, |
|||
shadowElevation = 8.dp |
|||
) { |
|||
Box(modifier = Modifier.padding(16.dp)) { |
|||
Button( |
|||
onClick = submit, |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.height(50.dp), |
|||
shape = RoundedCornerShape(25.dp), |
|||
colors = ButtonDefaults.buttonColors(containerColor = BrandMagenta) |
|||
) { |
|||
Icon(imageVector = Icons.Default.Check, contentDescription = null, tint = Color.White) |
|||
Spacer(modifier = Modifier.width(8.dp)) |
|||
Text(text = "Save Changes", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color.White) |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
containerColor = Color.White |
|||
) { paddingValues -> |
|||
Column( |
|||
modifier = Modifier |
|||
.fillMaxSize() |
|||
.padding(paddingValues) |
|||
.verticalScroll(scrollState) |
|||
.padding(16.dp), |
|||
verticalArrangement = Arrangement.spacedBy(16.dp) |
|||
) { |
|||
// ==================================================== |
|||
// 1. PROFILE COMPLETION CARD |
|||
// ==================================================== |
|||
Card( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
shape = RoundedCornerShape(16.dp), |
|||
colors = CardDefaults.cardColors(containerColor = Color(0xFFFFF9FA)), |
|||
border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp)// color = BrandMagenta.copy(alpha = 0.3f)) |
|||
) { |
|||
Column(modifier = Modifier.padding(16.dp)) { |
|||
Row( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
horizontalArrangement = Arrangement.SpaceBetween, |
|||
verticalAlignment = Alignment.CenterVertically |
|||
) { |
|||
Row(verticalAlignment = Alignment.CenterVertically,){//} horizontalAlignment = Arrangement.spacedBy(6.dp)) { |
|||
Box( |
|||
modifier = Modifier |
|||
.size(10.dp) |
|||
.clip(CircleShape) |
|||
.background(BrandMagenta) |
|||
) |
|||
Text(text = "Profile 92% Complete", fontWeight = FontWeight.Bold, fontSize = 14.sp, color = textDark) |
|||
} |
|||
Surface( |
|||
shape = RoundedCornerShape(12.dp), |
|||
color = BrandMagenta.copy(alpha = 0.1f) |
|||
) { |
|||
Text( |
|||
text = "+5% to 100%", |
|||
fontSize = 10.sp, |
|||
fontWeight = FontWeight.Bold, |
|||
color = BrandMagenta, |
|||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp) |
|||
) |
|||
} |
|||
} |
|||
Spacer(modifier = Modifier.height(6.dp)) |
|||
Text( |
|||
text = "Add a 30s voice intro to unlock the Super Listener verified badge!", |
|||
fontSize = 12.sp, |
|||
color = TextMuted |
|||
) |
|||
Spacer(modifier = Modifier.height(10.dp)) |
|||
Row( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
horizontalArrangement = Arrangement.SpaceBetween, |
|||
verticalAlignment = Alignment.CenterVertically |
|||
) { |
|||
Row(verticalAlignment = Alignment.CenterVertically){// horizontalAlignment = Arrangement.spacedBy(4.dp)) { |
|||
Text(text = "🎙️", fontSize = 12.sp) |
|||
Text(text = "Voice prompt pending", fontSize = 12.sp, fontWeight = FontWeight.Medium, color = textDark) |
|||
} |
|||
TextButton(onClick = {}) { |
|||
Text(text = "Record now ›", fontSize = 12.sp, fontWeight = FontWeight.Bold, color = BrandMagenta) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// ==================================================== |
|||
// 2. PROFILE PHOTO SECTION |
|||
// ==================================================== |
|||
Column(modifier = Modifier.fillMaxWidth()) { |
|||
Row( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
horizontalArrangement = Arrangement.SpaceBetween, |
|||
verticalAlignment = Alignment.CenterVertically |
|||
) { |
|||
Text(text = "Profile Photos", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark) |
|||
Text(text = "4 max", fontSize = 12.sp, color = BrandMagenta, fontWeight = FontWeight.Bold) |
|||
} |
|||
Spacer(modifier = Modifier.height(4.dp)) |
|||
Text(text = "Verified is matches-up your main avatar", fontSize = 11.sp, color = TextMuted) |
|||
Spacer(modifier = Modifier.height(10.dp)) |
|||
|
|||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { |
|||
Box( |
|||
modifier = Modifier |
|||
.size(90.dp, 120.dp) |
|||
.clip(RoundedCornerShape(12.dp)) |
|||
) { |
|||
AsyncImage( |
|||
model = "https://picsum.photos/300/400", |
|||
contentDescription = "Main Photo", |
|||
contentScale = ContentScale.Crop, |
|||
modifier = Modifier.fillMaxSize() |
|||
) |
|||
Box( |
|||
modifier = Modifier |
|||
.size(20.dp) |
|||
.clip(CircleShape) |
|||
.background(BrandMagenta) |
|||
.align(Alignment.BottomEnd) |
|||
.padding(4.dp), |
|||
contentAlignment = Alignment.Center |
|||
) { |
|||
Icon(imageVector = Icons.Default.Check, contentDescription = null, tint = Color.White, modifier = Modifier.size(12.dp)) |
|||
} |
|||
} |
|||
|
|||
Column( |
|||
modifier = Modifier |
|||
.weight(1f) |
|||
.height(120.dp), |
|||
verticalArrangement = Arrangement.spacedBy(8.dp) |
|||
) { |
|||
OutlinedButton( |
|||
onClick = {}, |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.weight(1f), |
|||
shape = RoundedCornerShape(12.dp), |
|||
colors = ButtonDefaults.outlinedButtonColors(contentColor = BrandMagenta) |
|||
) { |
|||
Icon(imageVector = Icons.Default.Add, contentDescription = null, modifier = Modifier.size(16.dp)) |
|||
Spacer(modifier = Modifier.width(6.dp)) |
|||
Text(text = "Upload New Photo", fontSize = 12.sp, fontWeight = FontWeight.Bold) |
|||
} |
|||
OutlinedButton( |
|||
onClick = {}, |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.weight(1f), |
|||
shape = RoundedCornerShape(12.dp), |
|||
colors = ButtonDefaults.outlinedButtonColors(contentColor = textDark) |
|||
) { |
|||
Text(text = "AI Camera", fontSize = 12.sp, fontWeight = FontWeight.Bold) |
|||
} |
|||
} |
|||
} |
|||
Spacer(modifier = Modifier.height(6.dp)) |
|||
Text(text = "⚠️ Cropping only protocol to keep rules & connections.", fontSize = 10.sp, color = Color(0xFFD32F2F)) |
|||
} |
|||
|
|||
// ==================================================== |
|||
// 3. VIDEO INTRO CARD |
|||
// ==================================================== |
|||
Card( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
shape = RoundedCornerShape(16.dp), |
|||
colors = CardDefaults.cardColors(containerColor = Color(0xFFF9F7FB)), |
|||
border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) |
|||
) { |
|||
Row( |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.padding(12.dp), |
|||
verticalAlignment = Alignment.CenterVertically, |
|||
horizontalArrangement = Arrangement.SpaceBetween |
|||
) { |
|||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) { |
|||
Box( |
|||
modifier = Modifier |
|||
.size(50.dp, 60.dp) |
|||
.clip(RoundedCornerShape(8.dp)) |
|||
.background(Color.Black) |
|||
) { |
|||
AsyncImage( |
|||
model = "https://picsum.photos/200/300", |
|||
contentDescription = null, |
|||
contentScale = ContentScale.Crop, |
|||
modifier = Modifier.fillMaxSize() |
|||
) |
|||
} |
|||
Column { |
|||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) { |
|||
Text(text = "Video Intro", fontSize = 13.sp, fontWeight = FontWeight.Bold, color = textDark) |
|||
Text(text = "💖", fontSize = 11.sp) |
|||
} |
|||
Text(text = "0:45sec • 456 total views", fontSize = 11.sp, color = TextMuted) |
|||
} |
|||
} |
|||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { |
|||
Surface( |
|||
shape = RoundedCornerShape(12.dp), |
|||
color = Color(0xFFEFE8F7) |
|||
) { |
|||
Text(text = "Replace", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = BrandMagenta, modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp)) |
|||
} |
|||
IconButton(onClick = {}, modifier = Modifier.size(28.dp)) { |
|||
Icon(imageVector = Icons.Default.Close, contentDescription = "Delete", tint = TextMuted, modifier = Modifier.size(16.dp)) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// ==================================================== |
|||
// 4. BASIC INFORMATION FIELDS |
|||
// ==================================================== |
|||
Column( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
verticalArrangement = Arrangement.spacedBy(10.dp) |
|||
) { |
|||
OutlinedTextField( |
|||
value = fullName, |
|||
onValueChange = { if (it.length <= 30) fullName = it }, |
|||
label = { Text("Display Name") }, |
|||
modifier = Modifier.fillMaxWidth(), |
|||
shape = RoundedCornerShape(12.dp), |
|||
colors = OutlinedTextFieldDefaults.colors( |
|||
focusedBorderColor = BrandMagenta, unfocusedBorderColor = Color(0xFFE0E0E0) |
|||
), |
|||
trailingIcon = { Text("${fullName.length} / 30", fontSize = 10.sp, color = TextMuted) } |
|||
) |
|||
OutlinedTextField( |
|||
value = birthDate, |
|||
onValueChange = { birthDate = it }, |
|||
label = { Text("Birthday (YYYY-MM-DD)") }, |
|||
singleLine = true, |
|||
modifier = Modifier.fillMaxWidth(), |
|||
shape = RoundedCornerShape(12.dp), |
|||
colors = OutlinedTextFieldDefaults.colors( |
|||
focusedBorderColor = BrandMagenta, unfocusedBorderColor = Color(0xFFE0E0E0) |
|||
) |
|||
) |
|||
OutlinedTextField( |
|||
value = gender, |
|||
onValueChange = { gender = it }, |
|||
label = { Text("Gender") }, |
|||
singleLine = true, |
|||
modifier = Modifier.fillMaxWidth(), |
|||
shape = RoundedCornerShape(12.dp), |
|||
colors = OutlinedTextFieldDefaults.colors( |
|||
focusedBorderColor = BrandMagenta, unfocusedBorderColor = Color(0xFFE0E0E0) |
|||
) |
|||
) |
|||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { |
|||
OutlinedTextField( |
|||
value = city, onValueChange = { city = it }, |
|||
label = { Text("City") }, singleLine = true, |
|||
modifier = Modifier.weight(1f), shape = RoundedCornerShape(12.dp), |
|||
colors = OutlinedTextFieldDefaults.colors( |
|||
focusedBorderColor = BrandMagenta, unfocusedBorderColor = Color(0xFFE0E0E0) |
|||
) |
|||
) |
|||
OutlinedTextField( |
|||
value = country, onValueChange = { country = it }, |
|||
label = { Text("Country") }, singleLine = true, |
|||
modifier = Modifier.weight(1f), shape = RoundedCornerShape(12.dp), |
|||
colors = OutlinedTextFieldDefaults.colors( |
|||
focusedBorderColor = BrandMagenta, unfocusedBorderColor = Color(0xFFE0E0E0) |
|||
) |
|||
) |
|||
} |
|||
} |
|||
|
|||
// ==================================================== |
|||
// 5. DATING INTENT |
|||
// ==================================================== |
|||
Column( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
verticalArrangement = Arrangement.spacedBy(8.dp) |
|||
) { |
|||
Row( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
horizontalArrangement = Arrangement.SpaceBetween, |
|||
verticalAlignment = Alignment.CenterVertically |
|||
) { |
|||
Text(text = "Dating Intent", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark) |
|||
Text(text = "Visible on profile", fontSize = 11.sp, color = TextMuted) |
|||
} |
|||
|
|||
DatingIntentOption( |
|||
title = "Long-term relationship", |
|||
subtitle = "Looking for a true connection or serious dating", |
|||
isSelected = true, |
|||
onClick = { datingIntent = "Long-term relationship" } |
|||
) |
|||
DatingIntentOption( |
|||
title = "Casual dates", |
|||
subtitle = "Fun dinners, socializing, and great talks", |
|||
isSelected = false, |
|||
onClick = { datingIntent = "Casual dates" } |
|||
) |
|||
DatingIntentOption( |
|||
title = "New Friends & Vibes", |
|||
subtitle = "Expanding my creative circle in the city", |
|||
isSelected = false, |
|||
onClick = { datingIntent = "New Friends & Vibes" } |
|||
) |
|||
DatingIntentOption( |
|||
title = "Marriage / Life Partner", |
|||
subtitle = "Looking for true degree and building a life together", |
|||
isSelected = false, |
|||
onClick = { datingIntent = "Marriage / Life Partner" } |
|||
) |
|||
} |
|||
|
|||
// ==================================================== |
|||
// 6. ABOUT BIO SECTION |
|||
// ==================================================== |
|||
Column(modifier = Modifier.fillMaxWidth()) { |
|||
Row( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
horizontalArrangement = Arrangement.SpaceBetween, |
|||
verticalAlignment = Alignment.CenterVertically |
|||
) { |
|||
Text(text = "About Alex (Bio)", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark) |
|||
Text(text = "124 / 500", fontSize = 10.sp, color = TextMuted) |
|||
} |
|||
Spacer(modifier = Modifier.height(6.dp)) |
|||
OutlinedTextField( |
|||
value = bioText, |
|||
onValueChange = { bioText = it }, |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.height(110.dp), |
|||
shape = RoundedCornerShape(12.dp), |
|||
colors = OutlinedTextFieldDefaults.colors( |
|||
focusedBorderColor = BrandMagenta, |
|||
unfocusedBorderColor = Color(0xFFE0E0E0) |
|||
) |
|||
) |
|||
Spacer(modifier = Modifier.height(6.dp)) |
|||
Row( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
horizontalArrangement = Arrangement.SpaceBetween, |
|||
verticalAlignment = Alignment.CenterVertically |
|||
) { |
|||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp)) { |
|||
Text(text = "💡", fontSize = 12.sp) |
|||
Text(text = "Tip: Add a question to get more replies!", fontSize = 11.sp, color = TextMuted) |
|||
} |
|||
TextButton(onClick = {}) { |
|||
Text(text = "Suggest AI • Polish", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = BrandMagenta) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// ==================================================== |
|||
// 7. PASSIONS & INTERESTS |
|||
// ==================================================== |
|||
Column(modifier = Modifier.fillMaxWidth()) { |
|||
Row( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
horizontalArrangement = Arrangement.SpaceBetween, |
|||
verticalAlignment = Alignment.CenterVertically |
|||
) { |
|||
Text(text = "Passions & Interests", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark) |
|||
Text(text = "8 selected", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = BrandMagenta) |
|||
} |
|||
Spacer(modifier = Modifier.height(2.dp)) |
|||
Text(text = "Tap to remove or add more tailored tags", fontSize = 11.sp, color = TextMuted) |
|||
Spacer(modifier = Modifier.height(8.dp)) |
|||
|
|||
val interestList = listOf( |
|||
"📷 Photography", "🐶 Dogs", "☕ Coffee Culture", "✈️ Travel", |
|||
"🎨 Art & Design", "🎵 Live Concerts", "🍷 Natural Wine", "🍳 Cooking" |
|||
) |
|||
|
|||
OptFlowRowEdit(interests = interestList) |
|||
|
|||
Spacer(modifier = Modifier.height(8.dp)) |
|||
OutlinedButton( |
|||
onClick = {}, |
|||
shape = RoundedCornerShape(16.dp), |
|||
colors = ButtonDefaults.outlinedButtonColors(contentColor = BrandMagenta), |
|||
modifier = Modifier.height(36.dp) |
|||
) { |
|||
Icon(imageVector = Icons.Default.Add, contentDescription = null, modifier = Modifier.size(14.dp)) |
|||
Spacer(modifier = Modifier.width(4.dp)) |
|||
Text(text = "Add More Interests", fontSize = 11.sp, fontWeight = FontWeight.Bold) |
|||
} |
|||
} |
|||
|
|||
// ==================================================== |
|||
// 8. KEY STATS & LIFESTYLE |
|||
// ==================================================== |
|||
Column( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
verticalArrangement = Arrangement.spacedBy(10.dp) |
|||
) { |
|||
Text(text = "Key Stats & Lifestyle", fontSize = 15.sp, fontWeight = FontWeight.Bold, color = textDark) |
|||
|
|||
StatEditRow(icon = "💼", title = "Work / Occupation", value = "Creative Director at Studio Drift") |
|||
StatEditRow(icon = "🎓", title = "Education", value = "NYU Gallatin School") |
|||
StatEditRow(icon = "📏", title = "Height", value = "5'9\" (175 cm)") |
|||
|
|||
// Beliefs Expandable Section |
|||
Card( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
shape = RoundedCornerShape(12.dp), |
|||
colors = CardDefaults.cardColors(containerColor = Color(0xFFFAFAFA)), |
|||
border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) |
|||
) { |
|||
Column(modifier = Modifier.padding(12.dp)) { |
|||
Row( |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.clickable { isBeliefsExpanded = !isBeliefsExpanded }, |
|||
horizontalArrangement = Arrangement.SpaceBetween, |
|||
verticalAlignment = Alignment.CenterVertically |
|||
) { |
|||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) { |
|||
Text(text = "🏛️", fontSize = 16.sp) |
|||
Column { |
|||
Text(text = "Beliefs / Values", fontSize = 10.sp, color = TextMuted) |
|||
Text(text = selectedBelief, fontSize = 12.sp, fontWeight = FontWeight.Bold, color = textDark) |
|||
} |
|||
} |
|||
Icon( |
|||
imageVector = if (isBeliefsExpanded) Icons.Default.KeyboardArrowUp else Icons.Default.KeyboardArrowDown, |
|||
contentDescription = "Toggle", |
|||
tint = TextMuted |
|||
) |
|||
} |
|||
|
|||
if (isBeliefsExpanded) { |
|||
Spacer(modifier = Modifier.height(10.dp)) |
|||
Divider(color = Color(0xFFEEEEEE), thickness = 0.5.dp) |
|||
Spacer(modifier = Modifier.height(8.dp)) |
|||
Text(text = "SELECT BELIEFS", fontSize = 9.sp, fontWeight = FontWeight.Bold, color = TextMuted) |
|||
Spacer(modifier = Modifier.height(6.dp)) |
|||
|
|||
BeliefOptionItem(title = "Muslim", desc =" Islam", isSelected = selectedBelief == "Muslim", onClick = { selectedBelief = "Muslim" }) |
|||
BeliefOptionItem(title = "Christian", desc = "Christian", isSelected = selectedBelief == "Christian", onClick = { selectedBelief = "Christian" }) |
|||
BeliefOptionItem(title = "Hindu", desc = "Hinduism", isSelected = selectedBelief == "Hindu", onClick = { selectedBelief = "Hindu" }) |
|||
BeliefOptionItem(title = "Buddhist", desc = "Buddhism", isSelected = selectedBelief == "Buddhist", onClick = { selectedBelief = "Buddhist" }) |
|||
BeliefOptionItem(title = "Spiritual / Agnostic", desc = "Open to universal healing & values", isSelected = selectedBelief == "Spiritual / Agnostic", onClick = { selectedBelief = "Spiritual / Agnostic" }) |
|||
BeliefOptionItem(title = "Other", desc = "Specify or leave open", isSelected = selectedBelief == "Other", onClick = { selectedBelief = "Other" }) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Lifestyle Grid Edit Tiles |
|||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) { |
|||
LifestyleEditTile(icon = "🚭", label = "Smoking", detail = "Non-smoker", modifier = Modifier.weight(1f)) |
|||
LifestyleEditTile(icon = "🍷", label = "Drinking", detail = "Socially", modifier = Modifier.weight(1f)) |
|||
} |
|||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) { |
|||
LifestyleEditTile(icon = "🐾", label = "Pets", detail = "Golden Retriever", modifier = Modifier.weight(1f)) |
|||
LifestyleEditTile(icon = "🏃", label = "Exercise", detail = "Active 4x a week", modifier = Modifier.weight(1f)) |
|||
} |
|||
|
|||
Spacer(modifier = Modifier.height(4.dp)) |
|||
OutlinedButton( |
|||
onClick = {}, |
|||
shape = RoundedCornerShape(16.dp), |
|||
colors = ButtonDefaults.outlinedButtonColors(contentColor = BrandMagenta), |
|||
modifier = Modifier.height(36.dp) |
|||
) { |
|||
Icon(imageVector = Icons.Default.Add, contentDescription = null, modifier = Modifier.size(14.dp)) |
|||
Spacer(modifier = Modifier.width(4.dp)) |
|||
Text(text = "Add More Lifestyle Details (Hobby, Diet, Sleep)", fontSize = 11.sp, fontWeight = FontWeight.Bold) |
|||
} |
|||
} |
|||
|
|||
Spacer(modifier = Modifier.height(40.dp)) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// ==================================================== |
|||
// SUB-COMPONENTS FOR EDIT PROFILE SCREEN |
|||
// ==================================================== |
|||
|
|||
@Composable |
|||
private fun DatingIntentOption(title: String, subtitle: String, isSelected: Boolean, onClick: () -> Unit) { |
|||
Card( |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.clickable { onClick() }, |
|||
shape = RoundedCornerShape(12.dp), |
|||
colors = CardDefaults.cardColors(containerColor = if (isSelected) Color(0xFFFFF9FA) else Color(0xFFFAFAFA)), |
|||
border = CardDefaults.outlinedCardBorder().copy(width = if (isSelected) 1.dp else 0.5.dp)// color = if (isSelected) BrandMagenta else Color(0xFFE0E0E0)) |
|||
) { |
|||
Row( |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.padding(12.dp), |
|||
verticalAlignment = Alignment.CenterVertically, |
|||
horizontalArrangement = Arrangement.SpaceBetween |
|||
) { |
|||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) { |
|||
Box( |
|||
modifier = Modifier |
|||
.size(30.dp) |
|||
.clip(CircleShape) |
|||
.background(if (isSelected) BrandMagenta.copy(alpha = 0.15f) else Color(0xFFEFEFEF)), |
|||
contentAlignment = Alignment.Center |
|||
) { |
|||
Icon( |
|||
imageVector = Icons.Default.Favorite, |
|||
contentDescription = null, |
|||
tint = if (isSelected) BrandMagenta else TextMuted, |
|||
modifier = Modifier.size(14.dp) |
|||
) |
|||
} |
|||
Column { |
|||
Text(text = title, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = textDark) |
|||
Text(text = subtitle, fontSize = 11.sp, color = TextMuted) |
|||
} |
|||
} |
|||
RadioButton(selected = isSelected, onClick = onClick, colors = RadioButtonDefaults.colors(selectedColor = BrandMagenta)) |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Composable |
|||
private fun StatEditRow(icon: String, title: String, value: String) { |
|||
Card( |
|||
modifier = Modifier.fillMaxWidth(), |
|||
shape = RoundedCornerShape(12.dp), |
|||
colors = CardDefaults.cardColors(containerColor = Color(0xFFFAFAFA)), |
|||
border = CardDefaults.outlinedCardBorder().copy(width = 0.5.dp) |
|||
) { |
|||
Row( |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.padding(12.dp), |
|||
verticalAlignment = Alignment.CenterVertically, |
|||
horizontalArrangement = Arrangement.SpaceBetween |
|||
) { |
|||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) { |
|||
Text(text = icon, fontSize = 16.sp) |
|||
Column { |
|||
Text(text = title, fontSize = 10.sp, color = TextMuted) |
|||
Text(text = value, fontSize = 12.sp, fontWeight = FontWeight.Bold, color = textDark) |
|||
} |
|||
} |
|||
Text(text = "›", fontSize = 16.sp, color = TextMuted) |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Composable |
|||
private fun BeliefOptionItem(title: String, desc: String, isSelected: Boolean, onClick: () -> Unit) { |
|||
Row( |
|||
modifier = Modifier |
|||
.fillMaxWidth() |
|||
.clickable { onClick() } |
|||
.padding(vertical = 6.dp), |
|||
verticalAlignment = Alignment.CenterVertically, |
|||
horizontalArrangement = Arrangement.SpaceBetween |
|||
) { |
|||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { |
|||
RadioButton(selected = isSelected, onClick = onClick, colors = RadioButtonDefaults.colors(selectedColor = BrandMagenta)) |
|||
Column { |
|||
Text(text = title, fontSize = 12.sp, fontWeight = FontWeight.Bold, color = textDark) |
|||
Text(text = desc, fontSize = 10.sp, color = TextMuted) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Composable |
|||
private fun LifestyleEditTile(icon: String, label: String, detail: String, modifier: Modifier = Modifier) { |
|||
Surface( |
|||
modifier = modifier, |
|||
shape = RoundedCornerShape(12.dp), |
|||
color = Color(0xFFFAFAFA), |
|||
border = BorderStroke(0.5.dp, Color(0xFFE0E0E0)) |
|||
) { |
|||
Row( |
|||
modifier = Modifier.padding(10.dp), |
|||
verticalAlignment = Alignment.CenterVertically, |
|||
horizontalArrangement = Arrangement.SpaceBetween |
|||
) { |
|||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { |
|||
Text(text = icon, fontSize = 16.sp) |
|||
Column { |
|||
Text(text = label, fontSize = 9.sp, color = TextMuted) |
|||
Text(text = detail, fontSize = 11.sp, fontWeight = FontWeight.Bold, color = textDark) |
|||
} |
|||
} |
|||
Text(text = "✎", fontSize = 11.sp, color = TextMuted) |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Composable |
|||
private fun OptFlowRowEdit(interests: List<String>) { |
|||
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { |
|||
val chunked = interests.chunked(3) |
|||
chunked.forEach { rowItems -> |
|||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { |
|||
rowItems.forEach { item -> |
|||
Surface( |
|||
shape = RoundedCornerShape(16.dp), |
|||
color = Color(0xFFF4F4F4), |
|||
border = BorderStroke(0.5.dp, Color(0xFFDDDDDD)) |
|||
) { |
|||
Row( |
|||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp), |
|||
verticalAlignment = Alignment.CenterVertically, |
|||
horizontalArrangement = Arrangement.spacedBy(4.dp) |
|||
) { |
|||
Text(text = item, fontSize = 11.sp, fontWeight = FontWeight.Medium, color = textDark) |
|||
Icon(imageVector = Icons.Default.Close, contentDescription = "Remove", tint = TextMuted, modifier = Modifier.size(12.dp)) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Preview(showBackground = true, showSystemUi = true) |
|||
@Composable |
|||
fun EditProfileScreenPreview() { |
|||
DattingAPPTheme { |
|||
EditProfileScreen() |
|||
} |
|||
} |
|||
Loading…
Reference in new issue