diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..36070ff
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+DattingAPP
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index b49fb06..ca16a99 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -6,18 +6,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 5568588..2724bdb 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -83,6 +83,15 @@ dependencies {
implementation("androidx.security:security-crypto:1.1.0-alpha06")
+ // Core Hilt dependencies
+ implementation("com.google.dagger:hilt-android:2.51.1")
+
+ // For Hilt ViewModel support in Jetpack Compose (if using Compose)
+ implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
+
+ // For Hilt support in Fragment / Activity
+ implementation("androidx.fragment:fragment-ktx:1.8.3")
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 692fcac..18d8d0c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -13,6 +13,7 @@
+
-
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
-
-
-
+ android:exported="false"
+ android:label="@string/title_activity_upload_intro_video"
+ android:theme="@style/Theme.DattingAPP"
+ android:windowSoftInputMode="adjustResize" />
+
diff --git a/app/src/main/java/MainApplication.kt b/app/src/main/java/MainApplication.kt
new file mode 100644
index 0000000..c02ca22
--- /dev/null
+++ b/app/src/main/java/MainApplication.kt
@@ -0,0 +1,5 @@
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MainApplication : Application()
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/DatingApp.kt b/app/src/main/java/com/example/dattingapp/DatingApp.kt
new file mode 100644
index 0000000..48ca687
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/DatingApp.kt
@@ -0,0 +1,19 @@
+package com.example.dattingapp
+
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class DatingApp : Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+ instance = this
+ }
+
+ companion object {
+ lateinit var instance: DatingApp
+ private set
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/ExploreScreen.kt b/app/src/main/java/com/example/dattingapp/ExploreScreen.kt
deleted file mode 100644
index 4d50f0d..0000000
--- a/app/src/main/java/com/example/dattingapp/ExploreScreen.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-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 androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import com.example.dattingapp.ui.theme.DattingAPPTheme
-
-class ExploreScreen : ComponentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContent {
- DattingAPPTheme {
- Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
- Greeting2(
- name = "Android",
- modifier = Modifier.padding(innerPadding)
- )
- }
- }
- }
- }
-}
-
-@Composable
-fun Greeting2(name: String, modifier: Modifier = Modifier) {
- Text(
- text = "Hello $name!",
- modifier = modifier
- )
-}
-
-@Preview(showBackground = true)
-@Composable
-fun GreetingPreview2() {
- DattingAPPTheme {
- Greeting2("Android")
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/Login/IdVerification.kt b/app/src/main/java/com/example/dattingapp/Login/IdVerification.kt
index e18b17e..850bdeb 100644
--- a/app/src/main/java/com/example/dattingapp/Login/IdVerification.kt
+++ b/app/src/main/java/com/example/dattingapp/Login/IdVerification.kt
@@ -43,7 +43,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
-import com.example.dattingapp.HomeActivity
+import com.example.dattingapp.homes.HomeActivity
class IdVerification : ComponentActivity() {
diff --git a/app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt b/app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt
index 791e2cc..af091d0 100644
--- a/app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt
+++ b/app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt
@@ -1,7 +1,9 @@
package com.example.dattingapp.Login
+
import android.content.Intent
import android.os.Bundle
+import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
@@ -12,37 +14,50 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Visibility
+import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import com.example.dattingapp.ui.theme.DattingAPPTheme
+import com.example.dattingapp.homes.HomeActivity
import com.example.dattingapp.R
+import com.example.dattingapp.homes.networkandapi.TokenManager
+import com.example.dattingapp.registration.LoginRequest
+import com.example.dattingapp.registration.NetworkModule
import com.example.dattingapp.registration.RegistrationScreen
+import com.example.dattingapp.ui.theme.DattingAPPTheme
+import kotlinx.coroutines.launch
+
-// Renamed Activity to LoginActivity to fix the shadowing collision
class LoginActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
enableEdgeToEdge()
setContent {
DattingAPPTheme {
LoginScreenContent(
+ onLoginSuccess = {
+ // TODO: Navigate to your Home/Main Activity upon successful login
+ startActivity(Intent(this@LoginActivity, HomeActivity::class.java))
+ finish()
+ },
onJoinNowClick = {
- val intent = Intent(
- this@LoginActivity,
- RegistrationScreen::class.java
- )
+ val intent = Intent(this@LoginActivity, RegistrationScreen::class.java)
startActivity(intent)
finish()
}
@@ -54,12 +69,16 @@ class LoginActivity : ComponentActivity() {
@Composable
fun LoginScreenContent(
- onGoogleClick: () -> Unit = {},
- onWeChatClick: () -> Unit = {},
- onContinueClick: (String) -> Unit = {},
+ onLoginSuccess: () -> Unit = {},
onJoinNowClick: () -> Unit = {}
) {
var emailOrPhone by remember { mutableStateOf("") }
+ var password by remember { mutableStateOf("") }
+ var isPasswordVisible by remember { mutableStateOf(false) }
+ var isLoading by remember { mutableStateOf(false) }
+
+ val context = LocalContext.current
+ val coroutineScope = rememberCoroutineScope()
Box(
modifier = Modifier.fillMaxSize(),
@@ -107,7 +126,6 @@ fun LoginScreenContent(
Spacer(modifier = Modifier.height(16.dp))
- // App Name
Text(
text = "VibeCheck",
fontSize = 26.sp,
@@ -117,7 +135,6 @@ fun LoginScreenContent(
Spacer(modifier = Modifier.height(4.dp))
- // Tagline
Text(
text = "Find your frequency.",
fontSize = 13.sp,
@@ -126,13 +143,13 @@ fun LoginScreenContent(
)
}
- // BOTTOM OVERLAY CARD: Login Form & Options
+ // BOTTOM OVERLAY CARD: Login Form
Surface(
modifier = Modifier
.fillMaxWidth()
- .padding(bottom = 12.dp),
+ .padding(bottom = 60.dp),
shape = RoundedCornerShape(28.dp),
- color = Color.White.copy(alpha = 0.85f),
+ color = Color.White.copy(alpha = .70f),
shadowElevation = 12.dp
) {
Column(
@@ -141,82 +158,58 @@ fun LoginScreenContent(
.padding(start = 20.dp, top = 24.dp, end = 20.dp, bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
- // Google Sign In
- Button(
- onClick = onGoogleClick,
- modifier = Modifier
- .fillMaxWidth()
- .height(48.dp),
- shape = RoundedCornerShape(12.dp),
- colors = ButtonDefaults.buttonColors(
- containerColor = Color(0xFFEEEEEE).copy(alpha = 0.8f)
- ),
- elevation = ButtonDefaults.buttonElevation(defaultElevation = 0.dp)
- ) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- Icon(
- painter = painterResource(id = R.drawable.googleicon),
- contentDescription = "Google Logo",
- tint = Color.Unspecified,
- modifier = Modifier.size(20.dp)
- )
- Spacer(modifier = Modifier.width(8.dp))
- Text(
- text = "Continue with Google",
- color = Color.DarkGray,
- fontSize = 13.sp,
- fontWeight = FontWeight.SemiBold
+ // Email or Phone Input
+ OutlinedTextField(
+ value = emailOrPhone,
+ onValueChange = { emailOrPhone = it },
+ placeholder = {
+ Text(text = "Email or Phone Number", fontSize = 20.sp, color = Color.Gray
)
- }
- }
-
- Spacer(modifier = Modifier.height(10.dp))
-
- // WeChat Sign In
- Button(
- onClick = onWeChatClick,
+ },
modifier = Modifier
.fillMaxWidth()
- .height(48.dp),
+ .padding(5.dp),
shape = RoundedCornerShape(12.dp),
- colors = ButtonDefaults.buttonColors(
- containerColor = Color(0xFFD8F3E5)
+ singleLine = true,
+ colors = OutlinedTextFieldDefaults.colors(
+ focusedContainerColor = Color(0xFFEFEFEF).copy(alpha = 0.6f),
+ unfocusedContainerColor = Color(0xFFEFEFEF).copy(alpha = 0.6f),
+ focusedBorderColor = Color.Transparent,
+ unfocusedBorderColor = Color.Transparent,
+ focusedTextColor = Color.Black,
+ unfocusedTextColor = Color.Black
),
- elevation = ButtonDefaults.buttonElevation(defaultElevation = 0.dp)
- ) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- Icon(
- painter = painterResource(id = R.drawable.wechatlog),
- contentDescription = "WeChat Logo",
- tint = Color(0xFF07C160),
- modifier = Modifier.size(20.dp)
- )
- Spacer(modifier = Modifier.width(8.dp))
- Text(
- text = "Continue with WeChat",
- color = Color(0xFF07C160),
- fontSize = 13.sp,
- fontWeight = FontWeight.SemiBold
- )
- }
- }
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Email,
+ imeAction = ImeAction.Next
+ )
+ )
- Spacer(modifier = Modifier.height(16.dp))
+ Spacer(modifier = Modifier.height(12.dp))
- // Email or Phone Input
+ // Password Field
OutlinedTextField(
- value = emailOrPhone,
- onValueChange = { emailOrPhone = it },
+ value = password,
+ onValueChange = { password = it },
placeholder = {
Text(
- text = "Email or Phone Number",
- fontSize = 12.sp,
+ text = "Password",
+ fontSize =20.sp,
color = Color.Gray
)
},
+ visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(),
+ trailingIcon = {
+ IconButton(onClick = { isPasswordVisible = !isPasswordVisible }) {
+ Icon(
+ imageVector = if (isPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff,
+ contentDescription = "Toggle Password Visibility",
+ tint = Color.Gray
+ )
+ }
+ },
modifier = Modifier
- .fillMaxWidth()
- .height(52.dp),
+ .fillMaxWidth(),
shape = RoundedCornerShape(12.dp),
singleLine = true,
colors = OutlinedTextFieldDefaults.colors(
@@ -228,16 +221,72 @@ fun LoginScreenContent(
unfocusedTextColor = Color.Black
),
keyboardOptions = KeyboardOptions(
- keyboardType = KeyboardType.Email,
+ keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done
)
)
- Spacer(modifier = Modifier.height(14.dp))
+ Spacer(modifier = Modifier.height(16.dp))
- // Continue Button
+ // Continue (Login) Button
Button(
- onClick = { onContinueClick(emailOrPhone) },
+ onClick = {
+ if (emailOrPhone.isBlank() || password.isBlank()) {
+ Toast.makeText(context, "Please fill in all fields", Toast.LENGTH_SHORT).show()
+ return@Button
+ }
+
+ isLoading = true
+ coroutineScope.launch {
+ try {
+ // Use your NetworkModule
+ val apiService = NetworkModule.getApiService(context)
+ val response = apiService.login(
+ LoginRequest(
+ emailOrPhone = emailOrPhone,
+ password = password
+ )
+ )
+
+ isLoading = false
+
+ if (response.isSuccessful && response.body() != null) {
+ val loginData = response.body()!!
+
+// val accessToken = loginData.accessToken ?: ""
+// val refreshToken = loginData.refreshToken ?: ""
+
+// Log.d("LoginActivity", "Saving Access Token: $accessToken")
+// Log.d("LoginActivity", "Saving Refresh Token: $refreshToken")
+
+ // Save retrieved tokens via TokenManager
+ val tokenManager = TokenManager.getInstance(context)
+ tokenManager.saveTokens(
+ accessToken = loginData.accessToken ?: "",
+ refreshToken = loginData.refreshToken ?: "",
+
+ )
+
+ Toast.makeText(context, "Login successful!", Toast.LENGTH_SHORT).show()
+ onLoginSuccess()
+ } else {
+ Toast.makeText(
+ context,
+ "Login failed (${response.code()}): ${response.message()}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } catch (e: Exception) {
+ isLoading = false
+ Toast.makeText(
+ context,
+ "Error: ${e.localizedMessage ?: "Connection error"}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ },
+ enabled = !isLoading,
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
@@ -246,17 +295,25 @@ fun LoginScreenContent(
containerColor = Color(0xFFC2003B)
)
) {
- Text(
- text = "Continue",
- color = Color.White,
- fontSize = 14.sp,
- fontWeight = FontWeight.Bold
- )
+ if (isLoading) {
+ CircularProgressIndicator(
+ modifier = Modifier.size(22.dp),
+ color = Color.White,
+ strokeWidth = 2.dp
+ )
+ } else {
+ Text(
+ text = "Continue",
+ color = Color.White,
+ fontSize = 14.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
}
Spacer(modifier = Modifier.height(16.dp))
- // OR Divider
+ // Divider
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
@@ -281,7 +338,7 @@ fun LoginScreenContent(
Spacer(modifier = Modifier.height(14.dp))
- // Join Now Navigation Row
+ // Join Now
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
@@ -302,7 +359,6 @@ fun LoginScreenContent(
Spacer(modifier = Modifier.height(10.dp))
- // Terms and Conditions Disclaimer
Text(
text = "By continuing, you agree to our Terms of Service and Privacy Policy.",
fontSize = 9.sp,
diff --git a/app/src/main/java/com/example/dattingapp/MainActivity.kt b/app/src/main/java/com/example/dattingapp/MainActivity.kt
index 2ecae70..68d44b3 100644
--- a/app/src/main/java/com/example/dattingapp/MainActivity.kt
+++ b/app/src/main/java/com/example/dattingapp/MainActivity.kt
@@ -5,21 +5,32 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
-import androidx.compose.ui.platform.LocalContext
+import com.example.dattingapp.homes.HomeActivity
+import com.example.dattingapp.homes.networkandapi.TokenManager
import com.example.dattingapp.permission.NotificationPermission
import com.example.dattingapp.ui.theme.DattingAPPTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
+
+
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
DattingAPPTheme {
- val context = LocalContext.current
- val intent = Intent(context, NotificationPermission::class.java)
- context.startActivity(intent)
- finish()
+ val tokenManager = TokenManager.getInstance(this)
+
+ if (tokenManager.isLoggedIn()) {
+ // 1. User is logged in -> Navigate directly to Home
+ startActivity(Intent(this, HomeActivity::class.java))
+ } else {
+ // 2. User is NOT logged in -> Navigate to Notification Permission or Login
+ startActivity(Intent(this, NotificationPermission::class.java))
}
+
+ // Close MainActivity so it doesn't stay on the backstack
+ finish()
}
}
+ }
}
diff --git a/app/src/main/java/com/example/dattingapp/MainApplication.kt b/app/src/main/java/com/example/dattingapp/MainApplication.kt
new file mode 100644
index 0000000..22eeea9
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/MainApplication.kt
@@ -0,0 +1,7 @@
+package com.example.dattingapp
+
+import android.app.Application
+import dagger.hilt.android.HiltAndroidApp
+
+@HiltAndroidApp
+class MainApplication : Application()
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/MeetScreen.kt b/app/src/main/java/com/example/dattingapp/MeetScreen.kt
deleted file mode 100644
index 62dd879..0000000
--- a/app/src/main/java/com/example/dattingapp/MeetScreen.kt
+++ /dev/null
@@ -1,301 +0,0 @@
-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 androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import com.example.dattingapp.ui.theme.DattingAPPTheme
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-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.filled.*
-import androidx.compose.material.icons.outlined.Close
-import androidx.compose.material.icons.outlined.LocationOn
-import androidx.compose.material.icons.outlined.Search
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.draw.shadow
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import com.example.dattingapp.R
-import com.example.dattingapp.ui.theme.BrandPink
-import com.example.dattingapp.ui.theme.BrandPurple
-import com.example.dattingapp.ui.theme.DattingAPPTheme
-
-class MeetScreen : ComponentActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContent {
- DattingAPPTheme {
- MeetDiscoveryScreen()
- }
- }
- }
-}
-@Composable
-fun MeetDiscoveryScreen(
- modifier: Modifier = Modifier,
- onAddClick: () -> Unit = {},
- onSearchClick: () -> Unit = {},
- onSuperLikeClick: () -> Unit = {},
- onRejectClick: () -> Unit = {},
- onLikeClick: () -> Unit = {}
-) {
- Box(
- modifier = modifier
- .fillMaxSize()
- .background(Color(0xFFF8F9FA))
- ) {
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(horizontal = 16.dp),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- // TOP BAR
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 12.dp, bottom = 12.dp),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
- ) {
- IconButton(
- onClick = onAddClick,
- modifier = Modifier
- .size(38.dp)
- .clip(CircleShape)
- .background(Color(0xFFEBEBEB))
- ) {
- Icon(
- imageVector = Icons.Default.Add,
- contentDescription = "Add",
- tint = BrandPink,
- modifier = Modifier.size(20.dp)
- )
- }
-
- Text(
- text = "VibeCheck",
- fontSize = 24.sp,
- fontWeight = FontWeight.Bold,
- color = BrandPurple
- )
-
- IconButton(onClick = onSearchClick) {
- Icon(
- imageVector = Icons.Outlined.Search,
- contentDescription = "Search",
- tint = BrandPink,
- modifier = Modifier.size(26.dp)
- )
- }
- }
-
- // PROFILE SWIPE CARD
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .weight(1f)
- .padding(bottom = 16.dp)
- .clip(RoundedCornerShape(20.dp))
- ) {
- // Background Profile Image
- Image(
- painter = painterResource(id = R.drawable.locationpermissionbg),
- contentDescription = "User Profile Image",
- modifier = Modifier.fillMaxSize(),
- contentScale = ContentScale.Crop
- )
-
- // Dark Bottom Gradient Overlay
- Box(
- modifier = Modifier
- .fillMaxSize()
- .background(
- Brush.verticalGradient(
- colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.85f)),
- startY = 350f
- )
- )
- )
-
- // RIGHT-SIDE ACTION BUTTONS (Moved lower using BottomEnd alignment)
- Column(
- modifier = Modifier
- .align(Alignment.BottomEnd)
- .padding(end = 14.dp, bottom = 20.dp),
- verticalArrangement = Arrangement.spacedBy(10.dp),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- // Star (Super Like) Button
- IconButton(
- onClick = onSuperLikeClick,
- modifier = Modifier
- .size(50.dp)
- .shadow(6.dp, CircleShape)
- .clip(CircleShape)
- .background(Color.White)
- ) {
- Icon(
- imageVector = Icons.Default.Star,
- contentDescription = "Super Like",
- tint = BrandPurple,
- modifier = Modifier.size(24.dp)
- )
- }
-
- // Pass (X) Button
- IconButton(
- onClick = onRejectClick,
- modifier = Modifier
- .size(54.dp)
- .shadow(6.dp, CircleShape)
- .clip(CircleShape)
- .background(Color.White)
- ) {
- Icon(
- imageVector = Icons.Outlined.Close,
- contentDescription = "Reject",
- tint = Color(0xFF555555),
- modifier = Modifier.size(26.dp)
- )
- }
-
- // Like (Heart) Button
- IconButton(
- onClick = onLikeClick,
- modifier = Modifier
- .size(60.dp)
- .shadow(8.dp, CircleShape)
- .clip(CircleShape)
- .background(BrandPink)
- ) {
- Icon(
- imageVector = Icons.Default.Favorite,
- contentDescription = "Like",
- tint = Color.White,
- modifier = Modifier.size(30.dp)
- )
- }
- }
-
- // BOTTOM PROFILE DETAILS
- Column(
- modifier = Modifier
- .align(Alignment.BottomStart)
- .padding(16.dp)
- .fillMaxWidth(0.70f)
- ) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(8.dp)
- ) {
- Text(
- text = "Alex, 24",
- fontSize = 26.sp,
- fontWeight = FontWeight.Bold,
- color = Color.White
- )
-
- // 98% Match Badge
- Surface(
- shape = RoundedCornerShape(10.dp),
- color = BrandPink
- ) {
- Row(
- modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(2.dp)
- ) {
- Icon(
- imageVector = Icons.Default.Verified,
- contentDescription = null,
- tint = Color.White,
- modifier = Modifier.size(10.dp)
- )
- Text(
- text = "98%",
- color = Color.White,
- fontSize = 10.sp,
- fontWeight = FontWeight.Bold
- )
- }
- }
- }
-
- Spacer(modifier = Modifier.height(4.dp))
-
- Row(
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.spacedBy(4.dp)
- ) {
- Icon(
- imageVector = Icons.Outlined.LocationOn,
- contentDescription = null,
- tint = Color.White.copy(alpha = 0.9f),
- modifier = Modifier.size(14.dp)
- )
- Text(
- text = "2 miles away • Active recently",
- fontSize = 13.sp,
- color = Color.White.copy(alpha = 0.9f)
- )
- }
-
- Spacer(modifier = Modifier.height(10.dp))
-
- // INTEREST CHIPS
- Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
- InterestChip(text = "Photography")
- InterestChip(text = "Dogs")
- }
- Spacer(modifier = Modifier.height(6.dp))
- InterestChip(text = "Coffee Shop Lover")
- }
- }
- }
- }
-}
-
-@Composable
-private fun InterestChip(text: String) {
- Surface(
- shape = RoundedCornerShape(16.dp),
- color = Color.White.copy(alpha = 0.3f)
- ) {
- Text(
- text = text,
- color = Color.White,
- fontSize = 12.sp,
- fontWeight = FontWeight.Medium,
- modifier = Modifier.padding(horizontal = 12.dp, vertical = 5.dp)
- )
- }
-}
-
-@Preview(showBackground = true, showSystemUi = true)
-@Composable
-fun MeetDiscoveryScreenPreview() {
- DattingAPPTheme {
- MeetDiscoveryScreen()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/MyProfile.kt b/app/src/main/java/com/example/dattingapp/MyProfile.kt
deleted file mode 100644
index 94e96a9..0000000
--- a/app/src/main/java/com/example/dattingapp/MyProfile.kt
+++ /dev/null
@@ -1,931 +0,0 @@
-package com.example.dattingapp
-
-import android.os.Bundle
-import android.widget.Toast
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-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.filled.Check
-import androidx.compose.material.icons.outlined.Edit
-import androidx.compose.material.icons.outlined.GridView
-import androidx.compose.material.icons.outlined.PersonPin
-import androidx.compose.material.icons.outlined.Settings
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.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 com.example.dattingapp.ui.theme.BrandMagenta
-import com.example.dattingapp.ui.theme.BrandPurple
-import com.example.dattingapp.ui.theme.DattingAPPTheme
-import com.example.dattingapp.ui.theme.textDark
-import com.example.dattingapp.ui.theme.TextMuted
-
-
-// ============================================================
-// MY PROFILE ACTIVITY
-// ============================================================
-
-class MyProfile : ComponentActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
-
- super.onCreate(savedInstanceState)
-
- enableEdgeToEdge()
-
- setContent {
-
- DattingAPPTheme {
-
- MyProfileScreen(
-
- onEditClick = {
-
- Toast.makeText(
- this,
- "Edit Profile Clicked",
- Toast.LENGTH_SHORT
- ).show()
-
- // TODO:
- // startActivity(
- // Intent(
- // this,
- // EditProfileActivity::class.java
- // )
- // )
- },
-
- onUpgradeClick = {
-
- Toast.makeText(
- this,
- "Upgrade to Premium Clicked",
- Toast.LENGTH_SHORT
- ).show()
-
- },
-
- onSettingsClick = {
-
- Toast.makeText(
- this,
- "Settings Clicked",
- Toast.LENGTH_SHORT
- ).show()
-
- // TODO:
- // startActivity(
- // Intent(
- // this,
- // SettingsActivity::class.java
- // )
- // )
- }
- )
- }
- }
- }
-}
-
-
-// ============================================================
-// MY PROFILE SCREEN
-// ============================================================
-
-@Composable
-fun MyProfileScreen(
-
- onEditClick: () -> Unit = {},
-
- onUpgradeClick: () -> Unit = {},
-
- onSettingsClick: () -> Unit = {}
-
-) {
-
- var selectedTab by remember {
-
- mutableIntStateOf(0)
- }
-
-
- // ========================================================
- // FULL SCREEN SCROLL
- // ========================================================
-
- val scrollState =
- rememberScrollState()
-
-
- Column(
-
- modifier = Modifier
- .fillMaxSize()
- .background(Color.White)
- .verticalScroll(scrollState)
- ) {
-
-
- // ====================================================
- // HEADER
- // ====================================================
-
- Box(
-
- modifier = Modifier
- .fillMaxWidth()
- .height(260.dp)
- ) {
-
-
- // =================================================
- // COVER IMAGE
- // =================================================
-
- Image(
-
- painter =
- painterResource(
- id =
- R.drawable.locationpermissionbg
- ),
-
- contentDescription =
- "Cover Banner",
-
- modifier = Modifier
- .fillMaxWidth()
- .height(200.dp),
-
- contentScale =
- ContentScale.Crop
- )
-
-
- // =================================================
- // SETTINGS BUTTON
- // =================================================
-
- IconButton(
-
- onClick =
- onSettingsClick,
-
- modifier = Modifier
- .align(
- Alignment.TopEnd
- )
- .padding(
- top = 16.dp,
- end = 16.dp
- )
- .size(36.dp)
- .clip(
- CircleShape
- )
- .background(
- Color.Black.copy(
- alpha = 0.3f
- )
- )
- ) {
-
- Icon(
-
- imageVector =
- Icons.Outlined.Settings,
-
- contentDescription =
- "Settings",
-
- tint =
- Color.White,
-
- modifier =
- Modifier.size(20.dp)
- )
- }
-
-
- // =================================================
- // PROFILE IMAGE
- // =================================================
-
- Box(
-
- modifier = Modifier
- .align(
- Alignment.BottomStart
- )
- .padding(
- start = 24.dp
- )
- .size(110.dp)
- ) {
-
-
- Image(
-
- painter =
- painterResource(
- id =
- R.drawable.locationpermissionbg
- ),
-
- contentDescription =
- "Profile Picture",
-
- contentScale =
- ContentScale.Crop,
-
- modifier = Modifier
- .fillMaxSize()
- .clip(
- CircleShape
- )
- .border(
- width = 4.dp,
- color = Color.White,
- shape = CircleShape
- )
- )
-
-
- // =================================================
- // VERIFIED BADGE
- // =================================================
-
- Box(
-
- modifier = Modifier
- .align(
- Alignment.BottomEnd
- )
- .size(28.dp)
- .clip(
- CircleShape
- )
- .background(
- BrandMagenta
- )
- .border(
- width = 2.dp,
- color = Color.White,
- shape = CircleShape
- ),
-
- contentAlignment =
- Alignment.Center
- ) {
-
- Icon(
-
- imageVector =
- Icons.Default.Check,
-
- contentDescription =
- "Verified",
-
- tint =
- Color.White,
-
- modifier =
- Modifier.size(16.dp)
- )
- }
- }
- }
-
-
- // ====================================================
- // USER INFORMATION
- // ====================================================
-
- Column(
-
- modifier = Modifier
- .fillMaxWidth()
- .padding(
- horizontal = 24.dp,
- vertical = 8.dp
- )
- ) {
-
-
- // =================================================
- // NAME + EDIT
- // =================================================
-
- Row(
-
- verticalAlignment =
- Alignment.CenterVertically,
-
- horizontalArrangement =
- Arrangement.spacedBy(8.dp)
- ) {
-
- Text(
-
- text =
- "Alex, 28",
-
- fontSize =
- 22.sp,
-
- fontWeight =
- FontWeight.Bold,
-
- color =
- BrandMagenta
- )
-
-
- // EDIT BUTTON
-
- OutlinedButton(
-
- onClick =
- onEditClick,
-
- modifier =
- Modifier.height(30.dp),
-
- contentPadding =
- PaddingValues(
- horizontal = 10.dp,
- vertical = 0.dp
- ),
-
- border =
- ButtonDefaults
- .outlinedButtonBorder
- .copy(
- width = 1.dp
- )
- ) {
-
- Icon(
-
- imageVector =
- Icons.Outlined.Edit,
-
- contentDescription =
- null,
-
- tint =
- BrandMagenta,
-
- modifier =
- Modifier.size(14.dp)
- )
-
-
- Spacer(
- modifier =
- Modifier.width(4.dp)
- )
-
-
- Text(
-
- text =
- "Edit",
-
- fontSize =
- 12.sp,
-
- color =
- BrandMagenta,
-
- fontWeight =
- FontWeight.Bold
- )
- }
- }
-
-
- Spacer(
- modifier =
- Modifier.height(4.dp)
- )
-
-
- // =================================================
- // BIO
- // =================================================
-
- Text(
-
- text =
- "Adventure seeker, dog lover, and amateur chef. Let's find a vibe!",
-
- fontSize =
- 13.sp,
-
- color =
- TextMuted,
-
- lineHeight =
- 18.sp
- )
- }
-
-
- Spacer(
- modifier =
- Modifier.height(12.dp)
- )
-
-
- // ====================================================
- // PREMIUM BANNER
- // ====================================================
-
- Box(
-
- modifier = Modifier
- .fillMaxWidth()
- .padding(
- horizontal = 16.dp
- )
- .clip(
- RoundedCornerShape(16.dp)
- )
- .background(
-
- brush =
- Brush.horizontalGradient(
-
- colors =
- listOf(
- BrandMagenta,
- BrandPurple
- )
- )
- )
- .padding(16.dp)
- ) {
-
- Row(
-
- modifier =
- Modifier.fillMaxWidth(),
-
- horizontalArrangement =
- Arrangement.SpaceBetween,
-
- verticalAlignment =
- Alignment.CenterVertically
- ) {
-
- Column {
-
- Text(
-
- text =
- "VibeCheck Premium",
-
- color =
- Color.White,
-
- fontSize =
- 16.sp,
-
- fontWeight =
- FontWeight.Bold
- )
-
-
- Spacer(
- modifier =
- Modifier.height(2.dp)
- )
-
-
- Text(
-
- text =
- "Unlock unlimited likes & more",
-
- color =
- Color.White.copy(
- alpha = 0.8f
- ),
-
- fontSize =
- 12.sp
- )
- }
-
-
- // UPGRADE BUTTON
-
- Button(
-
- onClick =
- onUpgradeClick,
-
- colors =
- ButtonDefaults.buttonColors(
-
- containerColor =
- Color.White
- ),
-
- shape =
- RoundedCornerShape(20.dp),
-
- contentPadding =
- PaddingValues(
- horizontal = 14.dp,
- vertical = 4.dp
- )
- ) {
-
- Text(
-
- text =
- "Upgrade",
-
- color =
- BrandMagenta,
-
- fontSize =
- 12.sp,
-
- fontWeight =
- FontWeight.Bold
- )
- }
- }
- }
-
-
- Spacer(
- modifier =
- Modifier.height(16.dp)
- )
-
-
- // ====================================================
- // STATISTICS
- // ====================================================
-
- Row(
-
- modifier = Modifier
- .fillMaxWidth()
- .padding(
- horizontal = 16.dp
- ),
-
- horizontalArrangement =
- Arrangement.spacedBy(8.dp)
- ) {
-
- StatCard(
-
- number =
- "24",
-
- label =
- "Likes",
-
- modifier =
- Modifier.weight(1f)
- )
-
-
- StatCard(
-
- number =
- "12",
-
- label =
- "Liked",
-
- modifier =
- Modifier.weight(1f)
- )
-
-
- StatCard(
-
- number =
- "Top",
-
- label =
- "Match",
-
- numberColor =
- BrandMagenta,
-
- modifier =
- Modifier.weight(1f)
- )
-
-
- StatCard(
-
- number =
- "8",
-
- label =
- "Matches",
-
- modifier =
- Modifier.weight(1f)
- )
- }
-
-
- Spacer(
- modifier =
- Modifier.height(16.dp)
- )
-
-
- // ====================================================
- // TABS
- // ====================================================
-
- TabRow(
-
- selectedTabIndex =
- selectedTab,
-
- containerColor =
- Color.White,
-
- contentColor =
- BrandMagenta,
-
- divider = {
-
- HorizontalDivider(
- color =
- Color(0xFFEEEEEE)
- )
- }
- ) {
-
-
- // =================================================
- // GRID TAB
- // =================================================
-
- Tab(
-
- selected =
- selectedTab == 0,
-
- onClick = {
-
- selectedTab = 0
- },
-
- icon = {
-
- Icon(
-
- imageVector =
- Icons.Outlined.GridView,
-
- contentDescription =
- "Grid View"
- )
- }
- )
-
-
- // =================================================
- // TAGGED TAB
- // =================================================
-
- Tab(
-
- selected =
- selectedTab == 1,
-
- onClick = {
-
- selectedTab = 1
- },
-
- icon = {
-
- Icon(
-
- imageVector =
- Icons.Outlined.PersonPin,
-
- contentDescription =
- "Tagged View"
- )
- }
- )
- }
-
-
- // ====================================================
- // MEDIA GRID
- // ====================================================
-
- ProfileMediaGrid()
-
-
- Spacer(
- modifier =
- Modifier.height(30.dp)
- )
- }
-}
-
-
-// ============================================================
-// PROFILE MEDIA GRID
-// ============================================================
-
-@Composable
-private fun ProfileMediaGrid() {
-
- Column(
-
- modifier = Modifier
- .fillMaxWidth()
- .padding(2.dp)
- ) {
-
- // 6 rows × 3 columns = 18 images
-
- repeat(6) { rowIndex ->
-
- Row(
-
- modifier =
- Modifier.fillMaxWidth()
- ) {
-
- repeat(3) { columnIndex ->
-
- val imageIndex =
- rowIndex * 3 +
- columnIndex
-
-
- Image(
-
- painter =
- painterResource(
- id =
- R.drawable.locationpermissionbg
- ),
-
- contentDescription =
- "Profile Image $imageIndex",
-
- modifier = Modifier
- .weight(1f)
- .aspectRatio(1f)
- .padding(1.dp),
-
- contentScale =
- ContentScale.Crop
- )
- }
- }
- }
- }
-}
-
-
-// ============================================================
-// STAT CARD
-// ============================================================
-
-@Composable
-private fun StatCard(
-
- number: String,
-
- label: String,
-
- modifier: Modifier = Modifier,
-
- numberColor: Color = textDark
-
-) {
-
- Card(
-
- modifier =
- modifier,
-
- shape =
- RoundedCornerShape(12.dp),
-
- colors =
- CardDefaults.cardColors(
-
- containerColor =
- Color(0xFFFAFAFA)
- ),
-
- border =
- CardDefaults
- .outlinedCardBorder()
- .copy(
- width = 0.5.dp
- )
- ) {
-
- Column(
-
- modifier = Modifier
- .fillMaxWidth()
- .padding(
- vertical = 12.dp
- ),
-
- horizontalAlignment =
- Alignment.CenterHorizontally
- ) {
-
- Text(
-
- text =
- number,
-
- fontSize =
- 16.sp,
-
- fontWeight =
- FontWeight.Bold,
-
- color =
- numberColor
- )
-
-
- Spacer(
- modifier =
- Modifier.height(2.dp)
- )
-
-
- Text(
-
- text =
- label,
-
- fontSize =
- 11.sp,
-
- color =
- TextMuted
- )
- }
- }
-}
-
-
-// ============================================================
-// PREVIEW
-// ============================================================
-
-@Preview(
-
- showBackground = true,
-
- showSystemUi = true,
-
- name = "My Profile"
-)
-@Composable
-fun MyProfileScreenPreview() {
-
- DattingAPPTheme {
-
- MyProfileScreen()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/MeetActivity.kt b/app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt
similarity index 99%
rename from app/src/main/java/com/example/dattingapp/MeetActivity.kt
rename to app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt
index 662b725..8ce2a01 100644
--- a/app/src/main/java/com/example/dattingapp/MeetActivity.kt
+++ b/app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt
@@ -1,4 +1,4 @@
-package com.example.dattingapp
+package com.example.dattingapp.homes
import android.os.Bundle
import androidx.activity.ComponentActivity
diff --git a/app/src/main/java/com/example/dattingapp/HomeActivity.kt b/app/src/main/java/com/example/dattingapp/homes/HomeActivity.kt
similarity index 52%
rename from app/src/main/java/com/example/dattingapp/HomeActivity.kt
rename to app/src/main/java/com/example/dattingapp/homes/HomeActivity.kt
index 1269b0d..142a190 100644
--- a/app/src/main/java/com/example/dattingapp/HomeActivity.kt
+++ b/app/src/main/java/com/example/dattingapp/homes/HomeActivity.kt
@@ -1,6 +1,7 @@
-package com.example.dattingapp
+package com.example.dattingapp.homes
import android.os.Bundle
+import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
@@ -18,18 +19,27 @@ import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-
+import androidx.compose.ui.platform.LocalContext
+import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.dattingapp.ChatScreen
-import com.example.dattingapp.ExploreScreen
-
-import com.example.dattingapp.MyProfileScreen
import com.example.dattingapp.CreatePost
+import com.example.dattingapp.homes.networkandapi.HomeNetworkModule
+import com.example.dattingapp.homes.networkandapi.MeetUiState
+import com.example.dattingapp.homes.networkandapi.MeetUserProfile
+import com.example.dattingapp.homes.networkandapi.MeetViewModel
+import com.example.dattingapp.homes.networkandapi.MyProfileUiState
+import com.example.dattingapp.homes.networkandapi.MyProfileViewModel
+
import com.example.dattingapp.ui.theme.DattingAPPTheme
+import kotlin.coroutines.cancellation.CancellationException
class HomeActivity : ComponentActivity() {
@@ -53,65 +63,45 @@ class HomeActivity : ComponentActivity() {
@Composable
fun HomeScreen() {
- var selectedItem by remember {
- mutableIntStateOf(0)
- }
+ val appContext = LocalContext.current.applicationContext
- Scaffold(
- bottomBar = {
+ var selectedItem by remember { mutableIntStateOf(0) }
+ Scaffold(
+ bottomBar = {
NavigationBar {
-
// =========================================
// MEET
// =========================================
NavigationBarItem(
-
selected = selectedItem == 0,
-
- onClick = {
- selectedItem = 0
- },
-
+ onClick = { selectedItem = 0 },
icon = {
-
Icon(
imageVector = Icons.Default.SwapHoriz,
contentDescription = "Meet"
)
},
-
label = {
Text("Meet")
}
)
-
-
// =========================================
// EXPLORE
// =========================================
-
NavigationBarItem(
-
selected = selectedItem == 1,
-
- onClick = {
- selectedItem = 1
- },
-
+ onClick = { selectedItem = 1 },
icon = {
-
Icon(
imageVector = Icons.Default.Explore,
contentDescription = "Explore"
)
},
- label = {
- Text("Explore")
- }
+ label = { Text("Explore") }
)
@@ -120,134 +110,160 @@ fun HomeScreen() {
// =========================================
NavigationBarItem(
-
selected = selectedItem == 2,
-
- onClick = {
- selectedItem = 2
- },
-
- icon = {
-
- Icon(
+ onClick = { selectedItem = 2 },
+ icon = { Icon(
imageVector = Icons.Default.Add,
contentDescription = "Post"
)
},
-
label = {
Text("Post")
}
)
-
-
// =========================================
// CHAT
// =========================================
-
NavigationBarItem(
-
selected = selectedItem == 3,
-
- onClick = {
- selectedItem = 3
- },
-
+ onClick = { selectedItem = 3 },
icon = {
-
Icon(
imageVector = Icons.Default.ChatBubbleOutline,
contentDescription = "Chat"
)
},
-
- label = {
- Text("Chat")
- }
+ label = { Text("Chat") }
)
-
-
// =========================================
// PROFILE
// =========================================
-
NavigationBarItem(
-
selected = selectedItem == 4,
-
- onClick = {
- selectedItem = 4
- },
-
+ onClick = { selectedItem = 4 },
icon = {
-
Icon(
imageVector = Icons.Default.Person,
contentDescription = "Profile"
)
},
-
- label = {
- Text("Profile")
- }
+ label = { Text("Profile") }
)
}
}
) { innerPadding ->
-
// =========================================
// CONTENT AREA
// =========================================
- Box(
- modifier = Modifier.padding(innerPadding)
- ) {
+ val context = LocalContext.current
- when (selectedItem) {
+ // or via hiltViewModel() / factory
+ Box(modifier = Modifier.padding(innerPadding)) {
+ when (selectedItem) {
// =========================================
// MEET
// =========================================
-
0 -> {
- MeetScreen()
- }
+ // 1. Obtain instance of ViewModel and LocalContext
+ val viewModel: MeetViewModel = viewModel()
+ // 2. Collect state from ViewModel
+ val uiState by viewModel.meetUiState.collectAsState()
+
+ // 3. Trigger network call once when screen loads
+ LaunchedEffect(Unit) {
+ viewModel.meetUserResponse(context)
+ }
+
+ // 4. Map UI state to Composable parameters
+ when (val state = uiState) {
+ is MeetUiState.Loading, MeetUiState.Idle -> {
+ MeetDiscoveryScreen(
+ profiles = emptyList(),
+ isLoading = true,
+ errorMessage = null
+ )
+ }
+ is MeetUiState.Success -> {
+ MeetDiscoveryScreen(
+ profiles = state.response,
+ isLoading = false,
+ errorMessage = null
+ )
+ }
+ is MeetUiState.Error -> {
+ MeetDiscoveryScreen(
+ profiles = emptyList(),
+ isLoading = false,
+ errorMessage = state.message
+ )
+ }
+ }
+ }
// =========================================
// EXPLORE
// =========================================
-
1 -> {
- ExploreScreen()
+ VibeCheckHomeScreen()
}
-
-
// =========================================
// CREATE POST
// =========================================
-
2 -> {
CreatePost()
}
-
-
// =========================================
// CHAT
// =========================================
-
3 -> {
ChatScreen()
}
-
-
// =========================================
// PROFILE
// =========================================
-
4 -> {
- MyProfileScreen()
+ // 1. Obtain instance of ViewModel and LocalContext
+ val viewModel: MyProfileViewModel = viewModel()
+ val context = LocalContext.current
+
+ // 2. Collect state from ViewModel
+ val uiState by viewModel.myProfileUiState.collectAsState()
+
+ // 3. Trigger network call once when screen loads
+ LaunchedEffect(Unit) {
+ viewModel.meetUserResponse(context)
+ }
+
+ // 4. Map UI state to Composable parameters
+ when (val state = uiState) {
+ is MyProfileUiState.Loading, MyProfileUiState.Idle -> {
+ MyProfileScreen(
+ profile = null,
+ isLoading = true,
+ errorMessage = null,
+// onRetry = { viewModel.meetUserResponse(context) }
+ )
+ }
+ is MyProfileUiState.Success -> {
+ MyProfileScreen(
+ profile = state.myProfile, // or state.response depending on your data class naming
+ isLoading = false,
+ errorMessage = null,
+ )
+ }
+ is MyProfileUiState.Error -> {
+ MyProfileScreen(
+ profile = null,
+ isLoading = false,
+ errorMessage = state.message,
+ onRetry = { viewModel.meetUserResponse(context) }
+ )
+ }
+ }
}
}
}
diff --git a/app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt b/app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt
new file mode 100644
index 0000000..325ad45
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt
@@ -0,0 +1,385 @@
+package com.example.dattingapp.homes
+
+import android.util.Log
+import androidx.compose.foundation.background
+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.filled.*
+import androidx.compose.material.icons.outlined.Close
+import androidx.compose.material.icons.outlined.LocationOn
+import androidx.compose.material.icons.outlined.Search
+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.draw.shadow
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import coil.compose.AsyncImage
+import coil.request.ImageRequest
+import com.example.dattingapp.homes.networkandapi.MeetUserProfile
+import com.example.dattingapp.homes.networkandapi.TokenManager
+import com.example.dattingapp.ui.theme.BrandPink
+import com.example.dattingapp.ui.theme.BrandPurple
+import java.time.LocalDate
+import java.time.Period
+import java.time.format.DateTimeFormatter
+
+@Composable
+fun MeetDiscoveryScreen(
+ modifier: Modifier = Modifier,
+ profiles: List = emptyList(),
+ isLoading: Boolean = false,
+ errorMessage: String? = null,
+ onAddClick: () -> Unit = {},
+ onSearchClick: () -> Unit = {},
+ onSuperLikeClick: (MeetUserProfile) -> Unit = {},
+ onRejectClick: (MeetUserProfile) -> Unit = {},
+ onLikeClick: (MeetUserProfile) -> Unit = {}
+) {
+ // Index tracking current active user profile (starts at 0)
+ var currentIndex by remember { mutableIntStateOf(0) }
+
+ Box(
+ modifier = modifier
+ .fillMaxSize()
+ .background(Color(0xFFF8F9FA))
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ // TOP BAR
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 12.dp, bottom = 12.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ IconButton(
+ onClick = onAddClick,
+ modifier = Modifier
+ .size(38.dp)
+ .clip(CircleShape)
+ .background(Color(0xFFEBEBEB))
+ ) {
+ Icon(
+ imageVector = Icons.Default.Add,
+ contentDescription = "Add",
+ tint = BrandPink,
+ modifier = Modifier.size(20.dp)
+ )
+ }
+
+ Text(
+ text = "VibeCheck",
+ fontSize = 24.sp,
+ fontWeight = FontWeight.Bold,
+ color = BrandPurple
+ )
+
+ IconButton(onClick = onSearchClick) {
+ Icon(
+ imageVector = Icons.Outlined.Search,
+ contentDescription = "Search",
+ tint = BrandPink,
+ modifier = Modifier.size(26.dp)
+ )
+ }
+ }
+
+ // MAIN CONTENT CARD
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f)
+ .padding(bottom = 16.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ when {
+ isLoading -> {
+ CircularProgressIndicator(color = BrandPink)
+ }
+
+ errorMessage != null -> {
+ Text(
+ text = errorMessage,
+ color = MaterialTheme.colorScheme.error,
+ fontSize = 16.sp
+ )
+ }
+
+ profiles.isNotEmpty() && currentIndex < profiles.size -> {
+ // Current profile starting at index 0
+ val currentProfile = profiles[currentIndex]
+
+ ProfileCardItem(
+ profile = currentProfile,
+ onSuperLike = {
+ onSuperLikeClick(currentProfile)
+ currentIndex++
+ },
+ onReject = {
+ onRejectClick(currentProfile)
+ currentIndex++
+ },
+ onLike = {
+ onLikeClick(currentProfile)
+ currentIndex++
+ }
+ )
+ }
+
+ else -> {
+ Text(
+ text = "No more profiles available!",
+ fontSize = 16.sp,
+ color = Color.Gray
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun ProfileCardItem(
+ profile: MeetUserProfile,
+ onSuperLike: () -> Unit,
+ onReject: () -> Unit,
+ onLike: () -> Unit
+) {
+ // Extract primary photo URL from media list
+ val photoUrl = remember(profile) {
+ profile.media
+ ?.firstOrNull { it.mediaType.equals("image", ignoreCase = true) || it.url != null }
+ ?.fullMediaUrl
+ }
+
+ Log.d("photourl", "$photoUrl")
+ // Helper to calculate age from birth_date string (formatted e.g. "YYYY-MM-DD")
+ val age = remember(profile.birthDate) {
+ calculateAge(profile.birthDate)
+ }
+
+ // Location display formatting
+ val locationText = remember(profile) {
+ val loc = listOfNotNull(profile.city, profile.country).joinToString(", ")
+ val dist = profile.distanceKm?.let { "${it.toInt()} km away" }
+ listOfNotNull(dist, loc.takeIf { it.isNotBlank() }).joinToString(" • ")
+ }
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(RoundedCornerShape(20.dp))
+ ) {
+ // BACKEND PROFILE IMAGE
+ val context = LocalContext.current
+ val token = remember(context) {
+ TokenManager.getInstance(context).getAccessToken()
+ }
+
+ AsyncImage(
+ model = ImageRequest.Builder(context)
+ .data(photoUrl)
+ .apply {
+ if (!token.isNullOrBlank()) {
+ // Formatting matches "Bearer " or raw token based on backend preference
+ val authHeader = if (token.startsWith("Bearer ", ignoreCase = true)) token else "Bearer $token"
+ addHeader("Authorization", authHeader)
+ }
+ }
+ .crossfade(true)
+ .build(),
+ contentDescription = "Profile Image",
+ modifier = Modifier.fillMaxSize(),
+ contentScale = ContentScale.Crop,
+ onError = { errorState ->
+ Log.e("CoilStatus", "Failed to load image", errorState.result.throwable)
+ }
+ )
+
+ // DARK GRADIENT OVERLAY
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ Brush.verticalGradient(
+ colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.85f)),
+ startY = 350f
+ )
+ )
+ )
+
+ // ACTION BUTTONS (Right Side)
+ Column(
+ modifier = Modifier
+ .align(Alignment.BottomEnd)
+ .padding(end = 14.dp, bottom = 20.dp),
+ verticalArrangement = Arrangement.spacedBy(10.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ // Super Like (Star)
+ IconButton(
+ onClick = onSuperLike,
+ modifier = Modifier
+ .size(50.dp)
+ .shadow(6.dp, CircleShape)
+ .clip(CircleShape)
+ .background(Color.White)
+ ) {
+ Icon(
+ imageVector = Icons.Default.Star,
+ contentDescription = "Super Like",
+ tint = BrandPurple,
+ modifier = Modifier.size(24.dp)
+ )
+ }
+
+ // Reject (X)
+ IconButton(
+ onClick = onReject,
+ modifier = Modifier
+ .size(54.dp)
+ .shadow(6.dp, CircleShape)
+ .clip(CircleShape)
+ .background(Color.White)
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Close,
+ contentDescription = "Reject",
+ tint = Color(0xFF555555),
+ modifier = Modifier.size(26.dp)
+ )
+ }
+
+ // Like (Heart)
+ IconButton(
+ onClick = onLike,
+ modifier = Modifier
+ .size(60.dp)
+ .shadow(8.dp, CircleShape)
+ .clip(CircleShape)
+ .background(BrandPink)
+ ) {
+ Icon(
+ imageVector = Icons.Default.Favorite,
+ contentDescription = "Like",
+ tint = Color.White,
+ modifier = Modifier.size(30.dp)
+ )
+ }
+ }
+
+ // PROFILE DETAILS (Bottom Left)
+ Column(
+ modifier = Modifier
+ .align(Alignment.BottomStart)
+ .padding(16.dp)
+ .fillMaxWidth(0.70f)
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ // Name & Age
+ val displayName = profile.fullName ?: "User"
+ val nameAndAge = if (age != null) "$displayName, $age" else displayName
+
+ Text(
+ text = nameAndAge,
+ fontSize = 26.sp,
+ fontWeight = FontWeight.Bold,
+ color = Color.White
+ )
+
+ // Compatibility / Match Percentage Badge
+ profile.compatibilityPercentage?.let { match ->
+ Surface(
+ shape = RoundedCornerShape(10.dp),
+ color = BrandPink
+ ) {
+ Row(
+ modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(2.dp)
+ ) {
+ Icon(
+ imageVector = Icons.Default.Verified,
+ contentDescription = null,
+ tint = Color.White,
+ modifier = Modifier.size(10.dp)
+ )
+ Text(
+ text = "$match%",
+ color = Color.White,
+ fontSize = 10.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ // Location & Distance
+ if (locationText.isNotBlank()) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(4.dp)
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.LocationOn,
+ contentDescription = null,
+ tint = Color.White.copy(alpha = 0.9f),
+ modifier = Modifier.size(14.dp)
+ )
+ Text(
+ text = locationText,
+ fontSize = 13.sp,
+ color = Color.White.copy(alpha = 0.9f)
+ )
+ }
+ }
+
+ // Bio
+ profile.bio?.takeIf { it.isNotBlank() }?.let { bio ->
+ Spacer(modifier = Modifier.height(8.dp))
+ Text(
+ text = bio,
+ fontSize = 12.sp,
+ color = Color.White.copy(alpha = 0.85f),
+ maxLines = 2
+ )
+ }
+ }
+ }
+}
+
+// Utility to calculate age from date string (e.g. "1999-05-15")
+private fun calculateAge(birthDateStr: String?): Int? {
+ if (birthDateStr.isNull_or_blank()) return null
+ return try {
+ val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
+ val birthDate = LocalDate.parse(birthDateStr?.take(10), formatter)
+ Period.between(birthDate, LocalDate.now()).years
+ } catch (e: Exception) {
+ null
+ }
+}
+
+private fun String?.isNull_or_blank(): Boolean = this == null || this.trim().isEmpty()
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt b/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt
new file mode 100644
index 0000000..d7f6ede
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt
@@ -0,0 +1,445 @@
+package com.example.dattingapp.homes
+
+import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+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.filled.Check
+import androidx.compose.material.icons.outlined.Edit
+import androidx.compose.material.icons.outlined.GridView
+import androidx.compose.material.icons.outlined.PersonPin
+import androidx.compose.material.icons.outlined.Settings
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.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.homes.networkandapi.MyProfileResponse
+import com.example.dattingapp.ui.theme.BrandMagenta
+import com.example.dattingapp.ui.theme.BrandPurple
+import com.example.dattingapp.ui.theme.DattingAPPTheme
+import com.example.dattingapp.ui.theme.textDark
+import com.example.dattingapp.ui.theme.TextMuted
+
+// ============================================================
+// MY PROFILE SCREEN
+// ============================================================
+
+@Composable
+fun MyProfileScreen(
+
+ profile: MyProfileResponse?, // Change from MyProfileData? to MyProfileResponse?
+ isLoading: Boolean,
+ errorMessage: String?,
+ onRetry: () -> Unit = {},
+ onEditClick: () -> Unit = {},
+ onUpgradeClick: () -> Unit = {},
+ onSettingsClick: () -> Unit = {}
+
+) {
+
+
+ // ========================================================
+ // FULL SCREEN SCROLL
+ // ========================================================
+
+ val scrollState = rememberScrollState()
+
+
+ Column(
+
+ modifier = Modifier.fillMaxSize().background(Color.White).verticalScroll(scrollState)) {
+
+
+ // ====================================================
+ // HEADER
+ // ====================================================
+
+ Box(
+
+ modifier = Modifier.fillMaxWidth().height(260.dp)
+ ) {
+ // =================================================
+ // COVER IMAGE
+ // =================================================
+
+ Image(
+ painter = painterResource(
+ id = R.drawable.locationpermissionbg
+ ),
+
+ contentDescription = "Cover Banner",
+
+ modifier = Modifier.fillMaxWidth().height(200.dp),
+ contentScale = ContentScale.Crop
+ )
+ // =================================================
+ // SETTINGS BUTTON
+ // =================================================
+
+ IconButton(
+ onClick = onSettingsClick,
+ modifier = Modifier.align(Alignment.TopEnd)
+ .padding(top = 16.dp, end = 16.dp)
+ .size(36.dp)
+ .clip(CircleShape)
+ .background(Color.Black.copy(alpha = 0.3f)
+ )
+ ) {
+
+ Icon(
+
+ imageVector = Icons.Outlined.Settings,
+
+ contentDescription = "Settings",
+ tint = Color.White,
+ modifier = Modifier.size(20.dp)
+ )
+ }
+ // =================================================
+ // PROFILE IMAGE
+ // =================================================
+ Box(
+ modifier = Modifier.align(Alignment.BottomStart)
+ .padding(start = 24.dp)
+ .size(110.dp)
+ ) {
+
+
+ AsyncImage(
+ // Access nested inner 'profile' first, then 'profileImageUrl'
+ model = profile?.profile?.profileImageUrl,
+ contentDescription = "Profile Picture",
+ contentScale = ContentScale.Crop,
+ placeholder = painterResource(id = R.drawable.locationpermissionbg),
+ error = painterResource(id = R.drawable.locationpermissionbg),
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(CircleShape)
+ .border(width = 4.dp, color = Color.White, shape = CircleShape)
+ )
+
+ // =================================================
+ // VERIFIED BADGE
+ // =================================================
+ Box(
+ modifier = Modifier.align(Alignment.BottomEnd).size(28.dp).clip(CircleShape)
+ .background(BrandMagenta)
+ .border(width = 2.dp, color = Color.White, shape = CircleShape
+ ),
+
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = "Verified",
+ tint = Color.White,
+ modifier = Modifier.size(16.dp)
+ )
+ }
+ }
+ }
+ // ====================================================
+ // USER INFORMATION
+ // ====================================================
+
+ Column(
+
+ modifier = Modifier.fillMaxWidth()
+ .padding(horizontal = 24.dp, vertical = 8.dp)
+ ) {
+ // =================================================
+ // NAME + EDIT
+ // =================================================
+
+ Row(
+
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+
+ Text(
+ text = "Alex, 28",
+ fontSize = 22.sp,
+ fontWeight = FontWeight.Bold,
+ color = BrandMagenta
+ )
+
+
+ // EDIT BUTTON
+
+ OutlinedButton(
+ onClick = onEditClick,
+ modifier = Modifier.height(30.dp),
+ contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
+ border = ButtonDefaults
+ .outlinedButtonBorder
+ .copy(width = 1.dp)
+ ) {
+
+ Icon(
+ imageVector = Icons.Outlined.Edit,
+ contentDescription = null,
+ tint = BrandMagenta,
+ modifier = Modifier.size(14.dp)
+ )
+
+ Spacer(modifier = Modifier.width(4.dp))
+
+ Text(
+ text = "Edit",
+ fontSize = 12.sp,
+ color = BrandMagenta,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(4.dp))
+ // =================================================
+ // BIO
+ // =================================================
+
+ Text(
+
+ text = "Adventure seeker, dog lover, and amateur chef. Let's find a vibe!",
+ fontSize = 13.sp,
+ color = TextMuted,
+ lineHeight = 18.sp
+ )
+ }
+
+
+ Spacer(
+ modifier = Modifier.height(12.dp)
+ )
+ // ====================================================
+ // PREMIUM BANNER
+ // ====================================================
+ Box(
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp)
+ .clip(RoundedCornerShape(16.dp))
+ .background(
+ brush = Brush.horizontalGradient(
+ colors = listOf(BrandMagenta, BrandPurple)
+ )
+ )
+ .padding(16.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column {
+
+ Text(
+ text = "VibeCheck Premium",
+ color = Color.White,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Bold
+ )
+
+ Spacer(
+ modifier = Modifier.height(2.dp)
+ )
+
+ Text(
+
+ text = "Unlock unlimited likes & more",
+ color = Color.White.copy(alpha = 0.8f),
+ fontSize = 12.sp
+ )
+ }
+ // UPGRADE BUTTON
+
+ Button(
+
+ onClick = onUpgradeClick,
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.White
+ ),
+ shape = RoundedCornerShape(20.dp),
+ contentPadding = PaddingValues(
+ horizontal = 14.dp,
+ vertical = 4.dp
+ )
+ ) {
+
+ Text(
+
+ text = "Upgrade",
+ color = BrandMagenta,
+ fontSize = 12.sp,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+
+
+ Spacer(
+ modifier = Modifier.height(16.dp)
+ )
+
+ // ====================================================
+ // STATISTICS
+ // ====================================================
+
+ Row(
+
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+
+ StatCard(
+ number = "24",
+ label = "Likes",
+ modifier = Modifier.weight(1f)
+ )
+
+ StatCard(
+
+ number = "12",
+ label = "Liked",
+ modifier = Modifier.weight(1f)
+ )
+
+
+ StatCard(
+ number = "Top",
+ label = "Match",
+ numberColor = BrandMagenta,
+ modifier = Modifier.weight(1f)
+ )
+ StatCard(
+ number = "8",
+ label = "Matches",
+ modifier = Modifier.weight(1f)
+ )
+ }
+ Spacer(
+ modifier = Modifier.height(16.dp)
+ )
+
+ ProfileMediaGrid()
+
+ Spacer(
+ modifier = Modifier.height(30.dp)
+ )
+ }
+}
+
+
+// ============================================================
+// PROFILE MEDIA GRID
+// ============================================================
+
+@Composable
+private fun ProfileMediaGrid() {
+
+ Column(
+
+ modifier = Modifier.fillMaxWidth().padding(2.dp)
+ ) {
+ // 6 rows × 3 columns = 18 images
+
+ repeat(6) { rowIndex ->
+
+ Row(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+
+ repeat(3) { columnIndex ->
+
+ val imageIndex = rowIndex * 3 + columnIndex
+
+
+ Image(
+ painter = painterResource(
+ id = R.drawable.locationpermissionbg
+ ),
+
+ contentDescription = "Profile Image $imageIndex",
+ modifier = Modifier.weight(1f).aspectRatio(1f).padding(1.dp),
+ contentScale = ContentScale.Crop
+ )
+ }
+ }
+ }
+ }
+}
+
+
+// ============================================================
+// STAT CARD
+// ============================================================
+
+@Composable
+private fun StatCard(
+ number: String,
+ label: String,
+ modifier: Modifier = Modifier,
+ numberColor: Color = textDark
+
+) {
+
+ Card(
+ modifier = modifier,
+ shape = RoundedCornerShape(12.dp),
+
+ colors = CardDefaults.cardColors(
+ containerColor = Color(0xFFFAFAFA)
+ ),
+
+ border =
+ CardDefaults.outlinedCardBorder()
+ .copy(width = 0.5.dp)
+ ) {
+
+ Column(
+ modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = number,
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Bold,
+ color = numberColor
+ )
+ Spacer(
+ modifier = Modifier.height(2.dp)
+ )
+
+ Text(
+ text = label,
+ fontSize = 11.sp,
+ color = TextMuted
+ )
+ }
+ }
+}
+
+// ============================================================
+// PREVIEW
+// ============================================================
diff --git a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt
new file mode 100644
index 0000000..e86a721
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt
@@ -0,0 +1,17 @@
+package com.example.dattingapp.homes.networkandapi
+
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface HomeApiService {
+
+ @GET("api/v1/meet")
+ suspend fun getMeetProfiles(
+ @Query("limit") limit: Int = 20
+ ): Response>
+
+ @GET("api/v1/users/me")
+ suspend fun getCurrentUserProfile(): Response
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeDataClass.kt b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeDataClass.kt
new file mode 100644
index 0000000..6163283
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeDataClass.kt
@@ -0,0 +1,93 @@
+package com.example.dattingapp.homes.networkandapi
+
+import com.google.gson.annotations.SerializedName
+
+private const val MEDIA_BASE_URL = "https://melove.youthome.com.cn"
+
+private fun resolveMediaUrl(value: String?): String? {
+ val path = value?.trim()?.takeIf { it.isNotBlank() } ?: return null
+
+ return when {
+ path.startsWith("https://", ignoreCase = true) -> path
+ path.startsWith("http://", ignoreCase = true) -> path // (Note: original code duplicated https:// twice)
+ path.startsWith("//") -> "https:$path"
+ else -> "$MEDIA_BASE_URL/${path.trimStart('/')}"
+ }
+}
+
+data class MeetUserProfile(
+ @SerializedName("id")
+ val id: Int,
+
+ @SerializedName("full_name")
+ val fullName: String?,
+
+ @SerializedName("gender")
+ val gender: String?,
+
+ @SerializedName("birth_date")
+ val birthDate: String?,
+
+ @SerializedName("city")
+ val city: String?,
+
+ @SerializedName("country")
+ val country: String?,
+
+ @SerializedName("bio")
+ val bio: String?,
+
+ @SerializedName("video_url")
+ val videoUrl: String?,
+
+ @SerializedName("distance_km")
+ val distanceKm: Double?,
+
+ @SerializedName("compatibility_percentage")
+ val compatibilityPercentage: Int?,
+
+ @SerializedName("media")
+ val media: List?
+) {
+ val fullVideoUrl: String?
+ get() = resolveMediaUrl(videoUrl)
+}
+
+
+/**
+ * Standard API wrapper format for MyProfile
+ */
+data class MyProfileResponse(
+ @SerializedName("id") val id: Int?,
+ @SerializedName("email_or_phone") val emailOrPhone: String?,
+ @SerializedName("status") val status: String?,
+ @SerializedName("profile") val profile: UserProfileDetails?, // Nested profile details
+ @SerializedName("created_at") val createdAt: String?
+)
+
+data class UserProfileDetails(
+ @SerializedName("full_name") val fullName: String?,
+ @SerializedName("gender") val gender: String?,
+ @SerializedName("city") val city: String?,
+ @SerializedName("country") val country: String?,
+ @SerializedName("bio") val bio: String?,
+ @SerializedName("profile_image_url") val profileImageUrl: String?, // Resolved field
+ @SerializedName("video_url") val videoUrl: String?
+)
+
+
+
+
+data class MediaItem(
+ @SerializedName("id")
+ val id: Int,
+
+ @SerializedName("media_type")
+ val mediaType: String?,
+
+ @SerializedName("url")
+ val url: String?
+) {
+ val fullMediaUrl: String?
+ get() = resolveMediaUrl(url)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeNetwokModule.kt b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeNetwokModule.kt
new file mode 100644
index 0000000..ba2753d
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeNetwokModule.kt
@@ -0,0 +1,68 @@
+package com.example.dattingapp.homes.networkandapi
+
+import android.content.Context
+import android.util.Log
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import java.util.concurrent.TimeUnit
+
+object HomeNetworkModule {
+
+ private const val BASE_URL = "https://melove.youthome.com.cn/"
+
+ @Volatile
+ private var apiService: HomeApiService? = null
+
+ fun getApiService(context: Context): HomeApiService {
+ return apiService ?: synchronized(this) {
+ apiService ?: createApi(context.applicationContext)
+ .also { apiService = it }
+ }
+ }
+
+ private fun createApi(context: Context): HomeApiService {
+ val tokenManager = TokenManager.getInstance(context)
+
+ val loggingInterceptor = HttpLoggingInterceptor { message ->
+ Log.d("MeetHttp", message)
+ }.apply {
+ // Logs request/status lines without profile bodies.
+ level = HttpLoggingInterceptor.Level.BASIC
+ redactHeader("Authorization")
+ redactHeader("Cookie")
+ }
+
+ val client = OkHttpClient.Builder()
+ .connectTimeout(30, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS)
+ .callTimeout(60, TimeUnit.SECONDS)
+ .addInterceptor { chain ->
+ val token = tokenManager.getAccessToken()
+ ?.replace(Regex("^Bearer\\s+", RegexOption.IGNORE_CASE), "")?.trim()?.takeIf { it.isNotBlank() }
+
+ Log.d("MeetApi", "Access token available: ${token != null}")
+
+ val request = chain.request().newBuilder()
+ .header("Accept", "application/json")
+ .apply {
+ if (token != null) {
+ header("Authorization", "Bearer $token")
+ }
+ }
+ .build()
+
+ chain.proceed(request)
+ }
+ .addInterceptor(loggingInterceptor)
+ .build()
+
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(client)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(HomeApiService::class.java)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt
new file mode 100644
index 0000000..f2bf549
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt
@@ -0,0 +1,155 @@
+package com.example.dattingapp.homes.networkandapi
+
+import android.content.Context
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+
+sealed class MeetUiState {
+ object Idle : MeetUiState()
+ object Loading : MeetUiState()
+ data class Success(val response: List) : MeetUiState()
+ data class Error(val message: String) : MeetUiState()
+}
+
+class MeetViewModel : ViewModel() {
+
+ private val _meetUiState = MutableStateFlow(MeetUiState.Idle)
+ val meetUiState: StateFlow = _meetUiState.asStateFlow()
+
+ fun meetUserResponse(context: Context) {
+ if (_meetUiState.value is MeetUiState.Loading) return
+
+ val appContext = context.applicationContext
+
+ viewModelScope.launch {
+ _meetUiState.value = MeetUiState.Loading
+
+ if (TokenManager.getInstance(appContext).getAccessToken().isNullOrBlank()) {
+ Log.e("MeetApi", "No access token in user_tokens preferences")
+ _meetUiState.value = MeetUiState.Error("No access token found. Please log in again.")
+ return@launch
+ }
+
+ try {
+ val response = HomeNetworkModule
+ .getApiService(appContext)
+ .getMeetProfiles()
+
+ Log.d("MeetApi", "HTTP Response Code: ${response.code()}")
+
+ if (!response.isSuccessful) {
+ response.errorBody()?.close()
+ val message = when (response.code()) {
+ 401 -> "Authentication failed. Please log in again."
+ 403 -> "The server refused access to these profiles."
+ 404 -> "Meet endpoint was not found."
+ else -> "Request failed: HTTP ${response.code()}."
+ }
+ _meetUiState.value = MeetUiState.Error(message)
+ return@launch
+ }
+
+ val profiles = response.body()
+
+ if (profiles.isNullOrEmpty()) {
+ _meetUiState.value = MeetUiState.Error("No profiles available.")
+ } else {
+ // -------------------------------------------------------------
+ // LOGGING IMAGE DATA FROM BACKEND RESPONSE
+ // -------------------------------------------------------------
+ profiles.forEachIndexed { index, profile ->
+ val name = profile.fullName ?: "No Name (ID: ${profile.id})"
+ val mediaList = profile.media
+
+ if (mediaList.isNullOrEmpty()) {
+ Log.w("MeetMediaCheck", "[$index] Profile: $name -> No media items returned from backend (media is empty/null)")
+ } else {
+ Log.d("MeetMediaCheck", "[$index] Profile: $name -> Found ${mediaList.size} media item(s)")
+ mediaList.forEachIndexed { mediaIdx, media ->
+ Log.d(
+ "MeetMediaCheck",
+ " └── Item #$mediaIdx: type='${media.mediaType}', raw_url='${media.url}', resolved_full_url='${media.fullMediaUrl}'"
+ )
+ }
+ }
+ }
+ // -------------------------------------------------------------
+
+ _meetUiState.value = MeetUiState.Success(profiles)
+ }
+
+ } catch (e: Exception) {
+ Log.e("MeetApi", "Unexpected request failure", e)
+ _meetUiState.value = MeetUiState.Error("Request failed: ${e.javaClass.simpleName}")
+ }
+ }
+ }
+}
+
+
+
+sealed interface MyProfileUiState {
+ object Idle : MyProfileUiState
+ object Loading : MyProfileUiState
+ data class Success(val myProfile: MyProfileResponse) : MyProfileUiState // Ensure this is MyProfileData, not MyProfileResponse
+ data class Error(val message: String) : MyProfileUiState
+}
+class MyProfileViewModel : ViewModel() {
+
+ private val _myProfileUiState = MutableStateFlow(MyProfileUiState.Idle)
+ val myProfileUiState: StateFlow = _myProfileUiState.asStateFlow()
+
+ fun meetUserResponse(context: Context) {
+ if (_myProfileUiState.value is MyProfileUiState.Loading) return
+
+ val appContext = context.applicationContext
+
+ viewModelScope.launch {
+ _myProfileUiState.value = MyProfileUiState.Loading
+
+ if (TokenManager.getInstance(appContext).getAccessToken().isNullOrBlank()) {
+ Log.e("MeetApi", "No access token in user_tokens preferences")
+ _myProfileUiState.value = MyProfileUiState.Error("No access token found. Please log in again.")
+ return@launch
+ }
+
+ try {
+ val response = HomeNetworkModule
+ .getApiService(appContext)
+ .getCurrentUserProfile()
+
+ if (!response.isSuccessful) {
+ response.errorBody()?.close()
+ val message = when (response.code()) {
+ 401 -> "Authentication failed. Please log in again."
+ 403 -> "The server refused access to these profiles."
+ 404 -> "Meet endpoint was not found."
+ else -> "Request failed: HTTP ${response.code()}."
+ }
+ _myProfileUiState.value = MyProfileUiState.Error(message)
+ return@launch
+ }
+
+ // FIX: Use response.body() directly as profileData
+ val profileData = response.body()
+
+ Log.d("my profiler", "my data $profileData")
+
+ if (profileData != null) {
+ _myProfileUiState.value = MyProfileUiState.Success(profileData)
+ } else {
+ _myProfileUiState.value = MyProfileUiState.Error("Profile data is empty.")
+ }
+
+ } catch (e: Exception) {
+ Log.e("MeetApi", "Unexpected request failure", e)
+ _myProfileUiState.value = MyProfileUiState.Error("Request failed: ${e.javaClass.simpleName}")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/homes/networkandapi/TokenManager.kt b/app/src/main/java/com/example/dattingapp/homes/networkandapi/TokenManager.kt
new file mode 100644
index 0000000..400dc95
--- /dev/null
+++ b/app/src/main/java/com/example/dattingapp/homes/networkandapi/TokenManager.kt
@@ -0,0 +1,66 @@
+package com.example.dattingapp.homes.networkandapi
+
+import android.content.Context
+import android.util.Log
+
+class TokenManager private constructor(context: Context) {
+
+ private val preferences =
+ context.applicationContext.getSharedPreferences(
+ PREF_NAME,
+ Context.MODE_PRIVATE
+ )
+
+ fun saveTokens(accessToken: String, refreshToken: String?) {
+ val cleanToken = accessToken.trim()
+ .replace(
+ Regex("^Bearer\\s+", RegexOption.IGNORE_CASE),
+ ""
+ )
+ .trim()
+
+ val saved = preferences.edit()
+ .putString(KEY_ACCESS_TOKEN, cleanToken)
+ .putString(KEY_REFRESH_TOKEN, refreshToken)
+ .commit()
+
+ // Do not log the token itself.
+ Log.d("TokenManager", "Tokens saved: $saved")
+ }
+
+ fun getAccessToken(): String? {
+ return preferences.getString(KEY_ACCESS_TOKEN, null)
+ ?.trim()
+ ?.takeIf { it.isNotBlank() }
+ }
+
+ fun getRefreshToken(): String? {
+ return preferences.getString(KEY_REFRESH_TOKEN, null)
+ ?.takeIf { it.isNotBlank() }
+ }
+
+ // Checks local token presence, not server validity or expiration.
+ fun isLoggedIn(): Boolean {
+ return !getAccessToken().isNullOrBlank()
+ }
+
+ fun clearTokens() {
+ preferences.edit().clear().commit()
+ }
+
+ companion object {
+ private const val PREF_NAME = "user_tokens"
+ private const val KEY_ACCESS_TOKEN = "access_token"
+ private const val KEY_REFRESH_TOKEN = "refresh_token"
+
+ @Volatile
+ private var INSTANCE: TokenManager? = null
+
+ fun getInstance(context: Context): TokenManager {
+ return INSTANCE ?: synchronized(this) {
+ INSTANCE ?: TokenManager(context.applicationContext)
+ .also { INSTANCE = it }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt b/app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt
index ca51452..b88b627 100644
--- a/app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt
+++ b/app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt
@@ -16,4 +16,16 @@ interface AuthApiService {
suspend fun uploadProfileImage(
@Part image: MultipartBody.Part
): Response
+
+ @Multipart
+ @POST("api/v1/users/me/video")
+ suspend fun uploadIntroVideo(
+ @Part video: MultipartBody.Part
+ ): Response // Reusing response model containing data.url
+
+
+ @POST("/api/v1/auth/login")
+ suspend fun login(
+ @Body request: LoginRequest
+ ): Response
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/registration/NetworkModule.kt b/app/src/main/java/com/example/dattingapp/registration/NetworkModule.kt
index 701b495..f6c7eb1 100644
--- a/app/src/main/java/com/example/dattingapp/registration/NetworkModule.kt
+++ b/app/src/main/java/com/example/dattingapp/registration/NetworkModule.kt
@@ -2,6 +2,7 @@ package com.example.dattingapp.registration
import android.content.Context
import android.net.Uri
+import com.example.dattingapp.homes.networkandapi.TokenManager
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
@@ -11,6 +12,7 @@ import retrofit2.converter.gson.GsonConverterFactory
import java.io.File
import java.io.FileOutputStream
+
object NetworkModule {
private const val BASE_URL = "https://melove.youthome.com.cn"
@@ -73,4 +75,27 @@ object NetworkModule {
null
}
}
+
+ fun prepareVideoPart(context: Context, uri: Uri, partName: String = "video"): MultipartBody.Part? {
+ return try {
+ val contentResolver = context.contentResolver
+ val mimeType = contentResolver.getType(uri) ?: "video/mp4"
+
+ val tempFile = File.createTempFile("upload_video_", ".mp4", context.cacheDir).apply {
+ deleteOnExit()
+ }
+
+ contentResolver.openInputStream(uri)?.use { input ->
+ FileOutputStream(tempFile).use { output ->
+ input.copyTo(output)
+ }
+ } ?: return null
+
+ val requestFile = tempFile.asRequestBody(mimeType.toMediaTypeOrNull())
+ MultipartBody.Part.createFormData(partName, tempFile.name, requestFile)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ null
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt b/app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt
index 6307624..d9b14fd 100644
--- a/app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt
+++ b/app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt
@@ -42,4 +42,21 @@ data class ProfileImageData(
data class ImageUploadResponse(
val message: String,
val imageUrl: String?
+)
+
+// If the backend expects "email" instead of "emailOrPhone":
+data class LoginRequest(
+ @SerializedName("email_or_phone") val emailOrPhone: String,
+ @SerializedName("password") val password: String
+)
+
+data class LoginResponse(
+ @SerializedName("access_token")
+ val accessToken: String?,
+
+ @SerializedName("refresh_token")
+ val refreshToken: String?,
+
+ @SerializedName("token_type")
+ val tokenType: String?
)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/registration/RegistrationViewModel.kt b/app/src/main/java/com/example/dattingapp/registration/RegistrationViewModel.kt
index 112cfb9..608f794 100644
--- a/app/src/main/java/com/example/dattingapp/registration/RegistrationViewModel.kt
+++ b/app/src/main/java/com/example/dattingapp/registration/RegistrationViewModel.kt
@@ -5,6 +5,7 @@ import android.net.Uri
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import com.example.dattingapp.homes.networkandapi.TokenManager
import com.example.dattingapp.registration.NetworkModule.prepareImagePart
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -14,9 +15,10 @@ import kotlinx.coroutines.launch
sealed class RegistrationUiState {
object Idle : RegistrationUiState()
object Loading : RegistrationUiState()
- data class Success(val response: RegisterResponse) : RegistrationUiState()
+ data class Success (val response: RegisterResponse) : RegistrationUiState()
data class ImageUploadSuccess(val response: ProfileImageUploadResponse) : RegistrationUiState()
- data class Error(val message: String) : RegistrationUiState()
+ data class VideoUploadSuccess(val videoUrl: String?) : RegistrationUiState()
+ data class Error ( val message: String) : RegistrationUiState()
}
class RegistrationViewModel : ViewModel() {
@@ -68,7 +70,6 @@ class RegistrationViewModel : ViewModel() {
viewModelScope.launch {
_uiState.value = RegistrationUiState.Loading
Log.d(TAG, "Preparing image for upload from URI: $imageUri")
-
val imagePart = prepareImagePart(context, imageUri, partName = "image")
if (imagePart == null) {
Log.e(TAG, "Failed to create MultipartBody.Part from Uri.")
@@ -97,6 +98,35 @@ class RegistrationViewModel : ViewModel() {
}
}
+
+
+ // Add uploadIntroVideo function in RegistrationViewModel
+ fun uploadIntroVideo(context: Context, videoUri: Uri) {
+ viewModelScope.launch {
+ _uiState.value = RegistrationUiState.Loading
+
+ val videoPart = NetworkModule.prepareVideoPart(context, videoUri, partName = "video")
+ if (videoPart == null) {
+ _uiState.value = RegistrationUiState.Error("Failed to prepare video file.")
+ return@launch
+ }
+
+ try {
+ val apiService = NetworkModule.getApiService(context)
+ val response = apiService.uploadIntroVideo(videoPart)
+
+ if (response.isSuccessful && response.body() != null) {
+ val videoUrl = response.body()?.data?.url
+ _uiState.value = RegistrationUiState.VideoUploadSuccess(videoUrl)
+ } else {
+ val errorMsg = response.errorBody()?.string() ?: "Video upload failed"
+ _uiState.value = RegistrationUiState.Error(errorMsg)
+ }
+ } catch (e: Exception) {
+ _uiState.value = RegistrationUiState.Error(e.localizedMessage ?: "Network error during video upload")
+ }
+ }
+ }
fun resetUiState() {
_uiState.value = RegistrationUiState.Idle
}
diff --git a/app/src/main/java/com/example/dattingapp/registration/TokenManager.kt b/app/src/main/java/com/example/dattingapp/registration/TokenManager.kt
deleted file mode 100644
index 2bab6df..0000000
--- a/app/src/main/java/com/example/dattingapp/registration/TokenManager.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.example.dattingapp.registration
-
-import android.content.Context
-import android.content.SharedPreferences
-
-class TokenManager private constructor(context: Context) {
-
- private val sharedPreferences: SharedPreferences = context.applicationContext.getSharedPreferences(
- PREF_NAME,
- Context.MODE_PRIVATE
- )
-
- fun saveTokens(accessToken: String, refreshToken: String?) {
- sharedPreferences.edit().apply {
- putString(KEY_ACCESS_TOKEN, accessToken)
- putString(KEY_REFRESH_TOKEN, refreshToken)
- apply()
- }
- }
-
- fun getAccessToken(): String? {
- return sharedPreferences.getString(KEY_ACCESS_TOKEN, null)
- }
-
- fun getRefreshToken(): String? {
- return sharedPreferences.getString(KEY_REFRESH_TOKEN, null)
- }
-
- fun clearTokens() {
- sharedPreferences.edit().apply {
- remove(KEY_ACCESS_TOKEN)
- remove(KEY_REFRESH_TOKEN)
- apply()
- }
- }
-
- companion object {
- private const val PREF_NAME = "user_tokens"
- private const val KEY_ACCESS_TOKEN = "access_token"
- private const val KEY_REFRESH_TOKEN = "refresh_token"
-
- @Volatile
- private var INSTANCE: TokenManager? = null
-
- fun getInstance(context: Context): TokenManager {
- return INSTANCE ?: synchronized(this) {
- INSTANCE ?: TokenManager(context.applicationContext).also { INSTANCE = it }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/dattingapp/registration/UploadIntroVideoActivity.kt b/app/src/main/java/com/example/dattingapp/registration/UploadIntroVideoActivity.kt
index 2da99d0..301e50d 100644
--- a/app/src/main/java/com/example/dattingapp/registration/UploadIntroVideoActivity.kt
+++ b/app/src/main/java/com/example/dattingapp/registration/UploadIntroVideoActivity.kt
@@ -1,6 +1,7 @@
package com.example.dattingapp.registration
import android.content.Context
+import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
@@ -9,6 +10,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -40,6 +42,7 @@ import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import coil.compose.rememberAsyncImagePainter
+import com.example.dattingapp.homes.HomeActivity
import com.example.dattingapp.ui.theme.DattingAPPTheme
import java.io.File
@@ -50,23 +53,55 @@ enum class DisplayMode {
}
class UploadIntroVideoActivity : ComponentActivity() {
+
+ private val viewModel: RegistrationViewModel by viewModels()
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
- // Retrieve Profile Image URI string
val profileImageUriString = intent.getStringExtra("EXTRA_PROFILE_IMAGE_URI")
val profileImageUri: Uri? = profileImageUriString?.let { Uri.parse(it) }
setContent {
DattingAPPTheme {
+ val uiState by viewModel.uiState.collectAsState()
+ val context = LocalContext.current
+
+ LaunchedEffect(uiState) {
+ when (val state = uiState) {
+ is RegistrationUiState.Loading -> {
+ // Show loading dialog or bar
+ }
+ is RegistrationUiState.VideoUploadSuccess -> {
+ Toast.makeText(context, "Video uploaded successfully!", Toast.LENGTH_SHORT).show()
+ val uploadedVideoUrl = state.videoUrl
+ // Pass uploadedVideoUrl to complete registration flow
+
+ val intent = Intent(this@UploadIntroVideoActivity, HomeActivity::class.java)
+ startActivity(intent)
+ finish()
+ }
+ is RegistrationUiState.Error -> {
+ Toast.makeText(context, state.message, Toast.LENGTH_LONG).show()
+ }
+ else -> {}
+ }
+ }
+
UploadIntroVideoScreen(
profileImageUri = profileImageUri,
onBackClick = { finish() },
- onSkipClick = { /* Handle skip */ },
+ onSkipClick = { finish() },
onSubmit = { videoUri, bioText ->
- // Final registration submission logic
- Toast.makeText(this, "Registration Data Ready!", Toast.LENGTH_SHORT).show()
+ if (videoUri != null) {
+ // Video provided -> Upload video to backend
+ viewModel.uploadIntroVideo(this, videoUri)
+ } else {
+ // Bio text only -> No video upload needed
+ Toast.makeText(this, "Bio saved locally!", Toast.LENGTH_SHORT).show()
+ finish()
+ }
}
)
}
@@ -74,7 +109,6 @@ class UploadIntroVideoActivity : ComponentActivity() {
}
}
-
@Composable
fun UploadIntroVideoScreen(
profileImageUri: Uri?,
diff --git a/app/src/main/java/com/example/dattingapp/registration/UploadProfileImageActivity.kt b/app/src/main/java/com/example/dattingapp/registration/UploadProfileImageActivity.kt
index b7bc219..c41508f 100644
--- a/app/src/main/java/com/example/dattingapp/registration/UploadProfileImageActivity.kt
+++ b/app/src/main/java/com/example/dattingapp/registration/UploadProfileImageActivity.kt
@@ -6,7 +6,6 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
-import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..731bafa
--- /dev/null
+++ b/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file