Browse Source

10/22/2026

master
osman160128 5 days ago
parent
commit
16900acfcc
  1. 1
      app/build.gradle.kts
  2. 18
      app/src/main/AndroidManifest.xml
  3. 441
      app/src/main/java/com/example/dattingapp/CreateReelActivity.kt
  4. 805
      app/src/main/java/com/example/dattingapp/EditMyProfileActivty.kt
  5. 24
      app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt
  6. 216
      app/src/main/java/com/example/dattingapp/homes/HomeActivity.kt
  7. 2
      app/src/main/java/com/example/dattingapp/homes/MyProfile.kt
  8. 4
      app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt
  9. 1
      app/src/main/res/values/strings.xml
  10. 2
      gradle/libs.versions.toml

1
app/build.gradle.kts

@ -42,6 +42,7 @@ dependencies {
implementation(libs.androidx.activity.ktx) implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.androidx.compose.foundation) implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.foundation.layout)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.graphics)

18
app/src/main/AndroidManifest.xml

@ -146,15 +146,27 @@
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".homes.profile.EditProfileActivity" android:name=".EditMyProfileActivty"
android:exported="false" android:exported="false"
android:label="@string/title_activity_edit_profile" android:label="@string/title_activity_edit_profile"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".EditProfileActivty" android:name=".CreateReel"
android:exported="false" android:exported="false"
android:label="@string/title_activity_edit_profile_activty" android:label="@string/title_activity_create_reel"
android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".post.CreatePostActivity"
android:exported="false"
android:label="@string/title_activity_create_post"
android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".CreateReelActivity"
android:exported="false"
android:label="@string/title_activity_create_reel"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
</application> </application>

441
app/src/main/java/com/example/dattingapp/CreateReelActivity.kt

@ -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 = {}
)
}
}

805
app/src/main/java/com/example/dattingapp/EditMyProfileActivty.kt

@ -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()
}
}

24
app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt

@ -176,9 +176,7 @@ private fun VibeCheckTopAppBar(
onSearchClick: () -> Unit onSearchClick: () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
val tokenManager = remember { TokenManager.getInstance(context) }
val currentUserName = remember { tokenManager.currentUserName() }
val currentProfileImage = remember { tokenManager.currentProfileImage() }
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -195,21 +193,17 @@ private fun VibeCheckTopAppBar(
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
AsyncImage(
model = currentProfileImage,
contentDescription = "Post author image",
placeholder = painterResource(id = R.drawable.locationpermissionbg),
error = painterResource(id = R.drawable.locationpermissionbg),
modifier = Modifier
.size(40.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.width(2.dp)) Text(
text = "Feed",
color = primaryColor,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.width(10.dp))
Text( Text(
text = currentUserName ?: "", text = "Reel",
color = primaryColor, color = primaryColor,
fontSize = 20.sp, fontSize = 20.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold

216
app/src/main/java/com/example/dattingapp/homes/HomeActivity.kt

@ -2,21 +2,27 @@ package com.example.dattingapp.homes
import ChatScreen import ChatScreen
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ChatBubbleOutline import androidx.compose.material.icons.filled.ChatBubbleOutline
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Explore import androidx.compose.material.icons.filled.Explore
import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.SwapHoriz import androidx.compose.material.icons.filled.SwapHoriz
import androidx.compose.material.icons.filled.Videocam
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@ -31,19 +37,27 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
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.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext 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 androidx.lifecycle.viewmodel.compose.viewModel
import com.example.dattingapp.CreateReelScreen
import com.example.dattingapp.homes.networkandapi.ExploreFeedViewModel import com.example.dattingapp.homes.networkandapi.ExploreFeedViewModel
import com.example.dattingapp.homes.networkandapi.FeedUiState import com.example.dattingapp.homes.networkandapi.FeedUiState
import com.example.dattingapp.homes.networkandapi.MeetUiState import com.example.dattingapp.homes.networkandapi.MeetUiState
import com.example.dattingapp.homes.networkandapi.MeetViewModel import com.example.dattingapp.homes.networkandapi.MeetViewModel
import com.example.dattingapp.homes.networkandapi.ProfileViewModel
import com.example.dattingapp.homes.networkandapi.ProfileUiState import com.example.dattingapp.homes.networkandapi.ProfileUiState
import com.example.dattingapp.homes.networkandapi.ProfileViewModel
import com.example.dattingapp.permission.PermissionHelper import com.example.dattingapp.permission.PermissionHelper
import com.example.dattingapp.ui.theme.DattingAPPTheme import com.example.dattingapp.ui.theme.DattingAPPTheme
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
class HomeActivity : ComponentActivity() { class HomeActivity : ComponentActivity() {
private lateinit var permissionHelper: PermissionHelper private lateinit var permissionHelper: PermissionHelper
@ -77,7 +91,7 @@ class HomeActivity : ComponentActivity() {
// 10-second delay trigger inside Compose lifecycle // 10-second delay trigger inside Compose lifecycle
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
delay(10_000) // Wait 10 seconds delay(10_000)
checkAndRequestPermission(PermissionHelper.PermissionType.OVERLAY) checkAndRequestPermission(PermissionHelper.PermissionType.OVERLAY)
} }
@ -132,9 +146,6 @@ class HomeActivity : ComponentActivity() {
} }
} }
@Composable @Composable
fun PermissionRationaleDialog( fun PermissionRationaleDialog(
title: String, title: String,
@ -164,16 +175,25 @@ fun HomeScreen() {
val appContext = LocalContext.current.applicationContext val appContext = LocalContext.current.applicationContext
var selectedItem by remember { mutableIntStateOf(0) } var selectedItem by remember { mutableIntStateOf(0) }
var showCreateSheet by remember { mutableStateOf(false) }
var showReel by remember { mutableStateOf(false) }
// Reel screen opens directly, full-screen, in place of the tabs and bottom bar
if (showReel) {
CreateReelScreen(
onClose = {
showReel = false
selectedItem = 1 // back to Explore after sharing or closing
}
)
return
}
Scaffold( Scaffold(
bottomBar = { bottomBar = {
NavigationBar { NavigationBar {
// =========================================
// MEET // MEET
// =========================================
NavigationBarItem( NavigationBarItem(
selected = selectedItem == 0, selected = selectedItem == 0,
onClick = { selectedItem = 0 }, onClick = { selectedItem = 0 },
@ -183,13 +203,10 @@ fun HomeScreen() {
contentDescription = "Meet" contentDescription = "Meet"
) )
}, },
label = { label = { Text("Meet") }
Text("Meet")
}
) )
// =========================================
// EXPLORE // EXPLORE
// =========================================
NavigationBarItem( NavigationBarItem(
selected = selectedItem == 1, selected = selectedItem == 1,
onClick = { selectedItem = 1 }, onClick = { selectedItem = 1 },
@ -199,30 +216,23 @@ fun HomeScreen() {
contentDescription = "Explore" contentDescription = "Explore"
) )
}, },
label = { Text("Explore") } label = { Text("Explore") }
) )
// POST: opens the Create Post / Create Reel sheet
// =========================================
// POST
// =========================================
NavigationBarItem( NavigationBarItem(
selected = selectedItem == 2, selected = selectedItem == 2,
onClick = { selectedItem = 2 }, onClick = { showCreateSheet = true },
icon = { Icon( icon = {
Icon(
imageVector = Icons.Default.Add, imageVector = Icons.Default.Add,
contentDescription = "Post" contentDescription = "Post"
) )
}, },
label = { label = { Text("Post") }
Text("Post")
}
) )
// =========================================
// CHAT // CHAT
// =========================================
NavigationBarItem( NavigationBarItem(
selected = selectedItem == 3, selected = selectedItem == 3,
onClick = { selectedItem = 3 }, onClick = { selectedItem = 3 },
@ -234,9 +244,8 @@ fun HomeScreen() {
}, },
label = { Text("Chat") } label = { Text("Chat") }
) )
// =========================================
// PROFILE // PROFILE
// =========================================
NavigationBarItem( NavigationBarItem(
selected = selectedItem == 4, selected = selectedItem == 4,
onClick = { selectedItem = 4 }, onClick = { selectedItem = 4 },
@ -250,35 +259,21 @@ fun HomeScreen() {
) )
} }
} }
) { innerPadding -> ) { innerPadding ->
// =========================================
// CONTENT AREA
// =========================================
val context = LocalContext.current val context = LocalContext.current
// or via hiltViewModel() / factory
Box(modifier = Modifier.padding(innerPadding)) { Box(modifier = Modifier.padding(innerPadding)) {
when (selectedItem) { when (selectedItem) {
// =========================================
// MEET // MEET
// =========================================
0 -> { 0 -> {
// 1. Obtain instance of ViewModel and LocalContext
val viewModel: MeetViewModel = viewModel() val viewModel: MeetViewModel = viewModel()
// 2. Collect state from ViewModel
val uiState by viewModel.meetUiState.collectAsState() val uiState by viewModel.meetUiState.collectAsState()
// 3. Trigger network call once when screen loads
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.meetUserResponse(context) viewModel.meetUserResponse(context)
} }
// 4. Map UI state to Composable parameters
when (val state = uiState) { when (val state = uiState) {
is MeetUiState.Loading, MeetUiState.Idle -> { is MeetUiState.Loading, MeetUiState.Idle -> {
MeetDiscoveryScreen( MeetDiscoveryScreen(
@ -303,38 +298,29 @@ fun HomeScreen() {
} }
} }
} }
// =========================================
// EXPLORE // EXPLORE
// =========================================
1 -> { 1 -> {
// 1. Obtain instance of ViewModel and LocalContext
val feedViewModel: ExploreFeedViewModel = viewModel() val feedViewModel: ExploreFeedViewModel = viewModel()
val context = LocalContext.current
// 2. Collect state from ViewModel
val uiState by feedViewModel.uiState.collectAsState() val uiState by feedViewModel.uiState.collectAsState()
// 3. Trigger network call once when screen loads
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
feedViewModel.loadFeed(context) feedViewModel.loadFeed(context)
} }
// 4. Map UI state to Composable parameters
when (val state = uiState) { when (val state = uiState) {
is FeedUiState.Loading, FeedUiState.Idle -> { is FeedUiState.Loading, FeedUiState.Idle -> {
VibeCheckHomeScreen( VibeCheckHomeScreen(
feedResponse = emptyList(), feedResponse = emptyList(),
isLoading = true, isLoading = true,
errorMessage = null, errorMessage = null
// onRetry = { viewModel.meetUserResponse(context) }
) )
} }
is FeedUiState.Success -> { is FeedUiState.Success -> {
VibeCheckHomeScreen( VibeCheckHomeScreen(
feedResponse = state.posts, // or state.response depending on your data class naming feedResponse = state.posts,
isLoading = false, isLoading = false,
errorMessage = null, errorMessage = null
) )
} }
is FeedUiState.Error -> { is FeedUiState.Error -> {
@ -342,39 +328,32 @@ fun HomeScreen() {
feedResponse = emptyList(), feedResponse = emptyList(),
isLoading = false, isLoading = false,
errorMessage = state.message, errorMessage = state.message,
onRetry = { feedViewModel.loadFeed(context) } onRetry = { feedViewModel.loadFeed(context) }
) )
} }
} }
} }
// =========================================
// CREATE POST // CREATE POST
// =========================================
2 -> { 2 -> {
CreateVibePostScreen( CreateVibePostScreen(
onShareClick = { selectedItem = 1 } // 1 = Explore tab onShareClick = { selectedItem = 1 } // 1 = Explore tab
) )
} }
// =========================================
// CHAT // CHAT
// =========================================
3 -> { 3 -> {
ChatScreen() ChatScreen()
} }
// =========================================
// PROFILE // PROFILE
// =========================================
// =========================================
// PROFILE
// =========================================
4 -> { 4 -> {
val profileViewModel: ProfileViewModel = viewModel() val profileViewModel: ProfileViewModel = viewModel()
val context = LocalContext.current
val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current
val uiState by profileViewModel.profileUiState.collectAsState() val uiState by profileViewModel.profileUiState.collectAsState()
// Automatically reload profile data when entering this tab OR resuming from another activity // Reload profile data when entering this tab OR resuming from another activity
DisposableEffect(lifecycleOwner) { DisposableEffect(lifecycleOwner) {
val observer = androidx.lifecycle.LifecycleEventObserver { _, event -> val observer = androidx.lifecycle.LifecycleEventObserver { _, event ->
if (event == androidx.lifecycle.Lifecycle.Event.ON_RESUME) { if (event == androidx.lifecycle.Lifecycle.Event.ON_RESUME) {
@ -392,14 +371,14 @@ fun HomeScreen() {
MyProfileScreen( MyProfileScreen(
profileResponse = null, profileResponse = null,
isLoading = true, isLoading = true,
errorMessage = null, errorMessage = null
) )
} }
is ProfileUiState.Success -> { is ProfileUiState.Success -> {
MyProfileScreen( MyProfileScreen(
profileResponse = state.profileData, profileResponse = state.profileData,
isLoading = false, isLoading = false,
errorMessage = null, errorMessage = null
) )
} }
is ProfileUiState.Error -> { is ProfileUiState.Error -> {
@ -415,4 +394,93 @@ fun HomeScreen() {
} }
} }
} }
// Create Post / Create Reel chooser
if (showCreateSheet) {
CreateChoiceSheet(
onDismiss = { showCreateSheet = false },
onCreatePost = {
showCreateSheet = false
selectedItem = 2
},
onCreateReel = {
// opens the reel screen directly
}
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CreateChoiceSheet(
onDismiss: () -> Unit,
onCreatePost: () -> Unit,
onCreateReel: () -> Unit
) {
ModalBottomSheet(
onDismissRequest = onDismiss,
containerColor = Color.White
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 24.dp)
.padding(bottom = 32.dp)
) {
Text(
text = "Create",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(bottom = 12.dp)
)
CreateChoiceRow(
icon = Icons.Default.Edit,
title = "Create Post",
subtitle = "Share text, photos or videos",
tint = Color(0xFFE91E63),
onClick = onCreatePost
)
CreateChoiceRow(
icon = Icons.Default.Videocam,
title = "Create Reel",
subtitle = "Record or upload a short video",
tint = Color(0xFF7B1FA2),
onClick = onCreateReel
)
}
}
}
@Composable
private fun CreateChoiceRow(
icon: ImageVector,
title: String,
subtitle: String,
tint: Color,
onClick: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.clickable { onClick() }
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(44.dp)
.clip(CircleShape)
.background(tint.copy(alpha = 0.12f)),
contentAlignment = Alignment.Center
) {
Icon(icon, contentDescription = null, tint = tint)
}
Spacer(Modifier.width(14.dp))
Column {
Text(title, fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
Text(subtitle, fontSize = 12.sp, color = Color.Gray)
}
}
} }

2
app/src/main/java/com/example/dattingapp/homes/MyProfile.kt

@ -42,6 +42,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.example.dattingapp.EditMyProfileActivty import com.example.dattingapp.EditMyProfileActivty
import com.example.dattingapp.LikeANdMatchActivity import com.example.dattingapp.LikeANdMatchActivity
import com.example.dattingapp.MatchesActivity import com.example.dattingapp.MatchesActivity
import com.example.dattingapp.R import com.example.dattingapp.R
@ -61,6 +62,7 @@ import com.example.dattingapp.ui.theme.textDark
import java.time.LocalDate import java.time.LocalDate
import java.time.Period import java.time.Period
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import kotlin.jvm.java
private fun calculateAge(birthDateStr: String?): Int? { private fun calculateAge(birthDateStr: String?): Int? {
if (birthDateStr.isNullOrBlank()) return null if (birthDateStr.isNullOrBlank()) return null

4
app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt

@ -40,10 +40,6 @@ data class ProfileImageData(
val url: String? // Adjust field name according to your API response JSON structure val url: String? // Adjust field name according to your API response JSON structure
) )
data class ImageUploadResponse(
val message: String,
val imageUrl: String?
)
// If the backend expects "email" instead of "emailOrPhone": // If the backend expects "email" instead of "emailOrPhone":
data class LoginRequest( data class LoginRequest(

1
app/src/main/res/values/strings.xml

@ -94,6 +94,7 @@
<string name="title_activity_matches">MatchesActivity</string> <string name="title_activity_matches">MatchesActivity</string>
<string name="title_activity_edit_profile">EditProfileActivity</string> <string name="title_activity_edit_profile">EditProfileActivity</string>
<string name="title_activity_edit_profile_activty">EditProfileActivty</string> <string name="title_activity_edit_profile_activty">EditProfileActivty</string>
<string name="title_activity_create_reel">CreateReel</string>
</resources> </resources>

2
gradle/libs.versions.toml

@ -19,6 +19,7 @@ navigationUiKtx = "2.6.0"
material3 = "1.4.0" material3 = "1.4.0"
ui = "1.12.0" ui = "1.12.0"
foundation = "1.12.1" foundation = "1.12.1"
foundationLayout = "1.12.1"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -46,6 +47,7 @@ media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3UiVersi
media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3ExoplayerVersion" } media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3ExoplayerVersion" }
androidx-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "ui" } androidx-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "ui" }
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" } androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "foundation" }
androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayout" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }

Loading…
Cancel
Save