Browse Source

profile

master
osman160128 4 weeks ago
parent
commit
75e6691d1e
  1. 1
      .idea/.name
  2. 12
      .idea/deploymentTargetSelector.xml
  3. 9
      app/build.gradle.kts
  4. 59
      app/src/main/AndroidManifest.xml
  5. 5
      app/src/main/java/MainApplication.kt
  6. 19
      app/src/main/java/com/example/dattingapp/DatingApp.kt
  7. 47
      app/src/main/java/com/example/dattingapp/ExploreScreen.kt
  8. 2
      app/src/main/java/com/example/dattingapp/Login/IdVerification.kt
  9. 218
      app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt
  10. 19
      app/src/main/java/com/example/dattingapp/MainActivity.kt
  11. 7
      app/src/main/java/com/example/dattingapp/MainApplication.kt
  12. 301
      app/src/main/java/com/example/dattingapp/MeetScreen.kt
  13. 931
      app/src/main/java/com/example/dattingapp/MyProfile.kt
  14. 2
      app/src/main/java/com/example/dattingapp/homes/ExploreScreen.kt
  15. 196
      app/src/main/java/com/example/dattingapp/homes/HomeActivity.kt
  16. 385
      app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt
  17. 445
      app/src/main/java/com/example/dattingapp/homes/MyProfile.kt
  18. 17
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt
  19. 93
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeDataClass.kt
  20. 68
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeNetwokModule.kt
  21. 155
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt
  22. 66
      app/src/main/java/com/example/dattingapp/homes/networkandapi/TokenManager.kt
  23. 12
      app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt
  24. 25
      app/src/main/java/com/example/dattingapp/registration/NetworkModule.kt
  25. 17
      app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt
  26. 32
      app/src/main/java/com/example/dattingapp/registration/RegistrationViewModel.kt
  27. 51
      app/src/main/java/com/example/dattingapp/registration/TokenManager.kt
  28. 44
      app/src/main/java/com/example/dattingapp/registration/UploadIntroVideoActivity.kt
  29. 1
      app/src/main/java/com/example/dattingapp/registration/UploadProfileImageActivity.kt
  30. 11
      app/src/main/res/xml/file_paths.xml

1
.idea/.name

@ -0,0 +1 @@
DattingAPP

12
.idea/deploymentTargetSelector.xml

@ -6,18 +6,6 @@
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DialogSelection /> <DialogSelection />
</SelectionState> </SelectionState>
<SelectionState runConfigName="main">
<option name="selectionMode" value="DROPDOWN" />
<DialogSelection />
</SelectionState>
<SelectionState runConfigName="unitTest">
<option name="selectionMode" value="DROPDOWN" />
<DialogSelection />
</SelectionState>
<SelectionState runConfigName="androidTest">
<option name="selectionMode" value="DROPDOWN" />
<DialogSelection />
</SelectionState>
</selectionStates> </selectionStates>
</component> </component>
</project> </project>

9
app/build.gradle.kts

@ -83,6 +83,15 @@ dependencies {
implementation("androidx.security:security-crypto:1.1.0-alpha06") 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")

59
app/src/main/AndroidManifest.xml

@ -13,6 +13,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".MainApplication"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
@ -21,6 +22,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.DattingAPP"> android:theme="@style/Theme.DattingAPP">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
@ -29,113 +31,138 @@
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".permission.LocationPermision" android:name=".permission.LocationPermision"
android:exported="false" android:exported="false"
android:label="@string/title_activity_location_permission" android:label="@string/title_activity_location_permission"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".permission.NotificationPermission" android:name=".permission.NotificationPermission"
android:exported="false" android:exported="false"
android:label="@string/title_activity_notification_permission" android:label="@string/title_activity_notification_permission"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".permission.OtherAppPermission" android:name=".permission.OtherAppPermission"
android:exported="false" android:exported="false"
android:label="@string/title_activity_other_app_permission" android:label="@string/title_activity_other_app_permission"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".permission.GallaryPermission" android:name=".permission.GallaryPermission"
android:exported="false" android:exported="false"
android:label="@string/title_activity_gallery_permission" android:label="@string/title_activity_gallery_permission"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".registration.RegistrationScreen" android:name=".registration.RegistrationScreen"
android:exported="false" android:exported="false"
android:label="@string/title_activity_registration_screen" android:label="@string/title_activity_registration_screen"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".Login.LoginActivity" android:name=".Login.LoginActivity"
android:exported="false" android:exported="false"
android:label="@string/title_activity_upload_profile_image" android:label="@string/title_activity_upload_profile_image"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".Login.IdVerification" android:name=".Login.IdVerification"
android:exported="false" android:exported="false"
android:label="@string/title_activity_id_verification" android:label="@string/title_activity_id_verification"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".MyProfile" android:name=".homes.MyProfile"
android:exported="false" android:exported="false"
android:label="@string/title_activity_my_profile" android:label="@string/title_activity_my_profile"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".CreatePost" android:name=".CreatePost"
android:exported="false" android:exported="false"
android:label="@string/title_activity_create_post" android:label="@string/title_activity_create_post"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".MeetScreen" android:name=".homes.MeetScreen"
android:exported="false" android:exported="false"
android:label="@string/title_activity_meet_screen" android:label="@string/title_activity_meet_screen"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ExploreScreen"
android:exported="false"
android:label="@string/title_activity_explore_screen"
android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".ChatScreen" android:name=".ChatScreen"
android:exported="false" android:exported="false"
android:label="@string/title_activity_chat_screen" android:label="@string/title_activity_chat_screen"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".HomeActivity" android:name=".homes.HomeActivity"
android:exported="false" android:exported="false"
android:label="@string/title_activity_chat_screen" android:label="@string/title_activity_chat_screen"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".FriendProfile" android:name=".FriendProfile"
android:exported="false" android:exported="false"
android:label="@string/title_activity_friend_profile" android:label="@string/title_activity_friend_profile"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".MeetActivity" android:name=".homes.MeetActivity"
android:exported="false" android:exported="false"
android:label="@string/title_activity_meet" android:label="@string/title_activity_meet"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<!-- FileProvider Configuration -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.dattingapp.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<activity <activity
android:name=".registration.UploadProfileImageActivity" android:name=".registration.UploadProfileImageActivity"
android:exported="false" android:exported="false"
android:label="@string/title_activity_meet" android:label="@string/title_activity_upload_image"
android:theme="@style/Theme.DattingAPP" android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity <activity
android:name=".registration.UploadIntroVideoActivity" android:name=".registration.UploadIntroVideoActivity"
android:exported="false" /> android:exported="false"
android:label="@string/title_activity_upload_intro_video"
android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".DatingApp"
android:exported="false"
android:label="@string/app_name"
android:theme="@style/Theme.DattingAPP"
android:windowSoftInputMode="adjustResize" />
</application> </application>

5
app/src/main/java/MainApplication.kt

@ -0,0 +1,5 @@
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MainApplication : Application()

19
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
}
}

47
app/src/main/java/com/example/dattingapp/ExploreScreen.kt

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

2
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.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import com.example.dattingapp.HomeActivity import com.example.dattingapp.homes.HomeActivity
class IdVerification : ComponentActivity() { class IdVerification : ComponentActivity() {

218
app/src/main/java/com/example/dattingapp/Login/LoginActivity.kt

@ -1,7 +1,9 @@
package com.example.dattingapp.Login package com.example.dattingapp.Login
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
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
@ -12,37 +14,50 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite 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.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType 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.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.example.dattingapp.ui.theme.DattingAPPTheme import com.example.dattingapp.homes.HomeActivity
import com.example.dattingapp.R 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.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() { class LoginActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
DattingAPPTheme { DattingAPPTheme {
LoginScreenContent( LoginScreenContent(
onLoginSuccess = {
// TODO: Navigate to your Home/Main Activity upon successful login
startActivity(Intent(this@LoginActivity, HomeActivity::class.java))
finish()
},
onJoinNowClick = { onJoinNowClick = {
val intent = Intent( val intent = Intent(this@LoginActivity, RegistrationScreen::class.java)
this@LoginActivity,
RegistrationScreen::class.java
)
startActivity(intent) startActivity(intent)
finish() finish()
} }
@ -54,12 +69,16 @@ class LoginActivity : ComponentActivity() {
@Composable @Composable
fun LoginScreenContent( fun LoginScreenContent(
onGoogleClick: () -> Unit = {}, onLoginSuccess: () -> Unit = {},
onWeChatClick: () -> Unit = {},
onContinueClick: (String) -> Unit = {},
onJoinNowClick: () -> Unit = {} onJoinNowClick: () -> Unit = {}
) { ) {
var emailOrPhone by remember { mutableStateOf("") } 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( Box(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
@ -107,7 +126,6 @@ fun LoginScreenContent(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// App Name
Text( Text(
text = "VibeCheck", text = "VibeCheck",
fontSize = 26.sp, fontSize = 26.sp,
@ -117,7 +135,6 @@ fun LoginScreenContent(
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
// Tagline
Text( Text(
text = "Find your frequency.", text = "Find your frequency.",
fontSize = 13.sp, fontSize = 13.sp,
@ -126,13 +143,13 @@ fun LoginScreenContent(
) )
} }
// BOTTOM OVERLAY CARD: Login Form & Options // BOTTOM OVERLAY CARD: Login Form
Surface( Surface(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(bottom = 12.dp), .padding(bottom = 60.dp),
shape = RoundedCornerShape(28.dp), shape = RoundedCornerShape(28.dp),
color = Color.White.copy(alpha = 0.85f), color = Color.White.copy(alpha = .70f),
shadowElevation = 12.dp shadowElevation = 12.dp
) { ) {
Column( Column(
@ -141,82 +158,58 @@ fun LoginScreenContent(
.padding(start = 20.dp, top = 24.dp, end = 20.dp, bottom = 16.dp), .padding(start = 20.dp, top = 24.dp, end = 20.dp, bottom = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
// Google Sign In // Email or Phone Input
Button( OutlinedTextField(
onClick = onGoogleClick, value = emailOrPhone,
modifier = Modifier onValueChange = { emailOrPhone = it },
.fillMaxWidth() placeholder = {
.height(48.dp), Text(text = "Email or Phone Number", fontSize = 20.sp, color = Color.Gray
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
) )
} },
}
Spacer(modifier = Modifier.height(10.dp))
// WeChat Sign In
Button(
onClick = onWeChatClick,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(48.dp), .padding(5.dp),
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors( singleLine = true,
containerColor = Color(0xFFD8F3E5) 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) keyboardOptions = KeyboardOptions(
) { keyboardType = KeyboardType.Email,
Row(verticalAlignment = Alignment.CenterVertically) { imeAction = ImeAction.Next
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
) )
}
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(12.dp))
// Email or Phone Input // Password Field
OutlinedTextField( OutlinedTextField(
value = emailOrPhone, value = password,
onValueChange = { emailOrPhone = it }, onValueChange = { password = it },
placeholder = { placeholder = {
Text( Text(
text = "Email or Phone Number", text = "Password",
fontSize = 12.sp, fontSize =20.sp,
color = Color.Gray 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 modifier = Modifier
.fillMaxWidth() .fillMaxWidth(),
.height(52.dp),
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
singleLine = true, singleLine = true,
colors = OutlinedTextFieldDefaults.colors( colors = OutlinedTextFieldDefaults.colors(
@ -228,16 +221,72 @@ fun LoginScreenContent(
unfocusedTextColor = Color.Black unfocusedTextColor = Color.Black
), ),
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email, keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done imeAction = ImeAction.Done
) )
) )
Spacer(modifier = Modifier.height(14.dp)) Spacer(modifier = Modifier.height(16.dp))
// Continue Button // Continue (Login) Button
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 modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(48.dp), .height(48.dp),
@ -246,6 +295,13 @@ fun LoginScreenContent(
containerColor = Color(0xFFC2003B) containerColor = Color(0xFFC2003B)
) )
) { ) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(22.dp),
color = Color.White,
strokeWidth = 2.dp
)
} else {
Text( Text(
text = "Continue", text = "Continue",
color = Color.White, color = Color.White,
@ -253,10 +309,11 @@ fun LoginScreenContent(
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
} }
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
// OR Divider // Divider
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@ -281,7 +338,7 @@ fun LoginScreenContent(
Spacer(modifier = Modifier.height(14.dp)) Spacer(modifier = Modifier.height(14.dp))
// Join Now Navigation Row // Join Now
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center horizontalArrangement = Arrangement.Center
@ -302,7 +359,6 @@ fun LoginScreenContent(
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
// Terms and Conditions Disclaimer
Text( Text(
text = "By continuing, you agree to our Terms of Service and Privacy Policy.", text = "By continuing, you agree to our Terms of Service and Privacy Policy.",
fontSize = 9.sp, fontSize = 9.sp,

19
app/src/main/java/com/example/dattingapp/MainActivity.kt

@ -5,19 +5,30 @@ import android.os.Bundle
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.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.permission.NotificationPermission
import com.example.dattingapp.ui.theme.DattingAPPTheme import com.example.dattingapp.ui.theme.DattingAPPTheme
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
DattingAPPTheme { DattingAPPTheme {
val context = LocalContext.current val tokenManager = TokenManager.getInstance(this)
val intent = Intent(context, NotificationPermission::class.java)
context.startActivity(intent) 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() finish()
} }
} }

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

301
app/src/main/java/com/example/dattingapp/MeetScreen.kt

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

931
app/src/main/java/com/example/dattingapp/MyProfile.kt

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

2
app/src/main/java/com/example/dattingapp/MeetActivity.kt → 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 android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity

196
app/src/main/java/com/example/dattingapp/HomeActivity.kt → 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.os.Bundle
import android.util.Log
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
@ -18,18 +19,27 @@ import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
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.Modifier 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.ChatScreen
import com.example.dattingapp.ExploreScreen
import com.example.dattingapp.MyProfileScreen
import com.example.dattingapp.CreatePost 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 com.example.dattingapp.ui.theme.DattingAPPTheme
import kotlin.coroutines.cancellation.CancellationException
class HomeActivity : ComponentActivity() { class HomeActivity : ComponentActivity() {
@ -53,65 +63,45 @@ class HomeActivity : ComponentActivity() {
@Composable @Composable
fun HomeScreen() { fun HomeScreen() {
var selectedItem by remember { val appContext = LocalContext.current.applicationContext
mutableIntStateOf(0)
}
Scaffold(
bottomBar = { var selectedItem by remember { mutableIntStateOf(0) }
Scaffold(
bottomBar = {
NavigationBar { NavigationBar {
// ========================================= // =========================================
// MEET // MEET
// ========================================= // =========================================
NavigationBarItem( NavigationBarItem(
selected = selectedItem == 0, selected = selectedItem == 0,
onClick = { selectedItem = 0 },
onClick = {
selectedItem = 0
},
icon = { icon = {
Icon( Icon(
imageVector = Icons.Default.SwapHoriz, imageVector = Icons.Default.SwapHoriz,
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
},
icon = { icon = {
Icon( Icon(
imageVector = Icons.Default.Explore, imageVector = Icons.Default.Explore,
contentDescription = "Explore" contentDescription = "Explore"
) )
}, },
label = { label = { Text("Explore") }
Text("Explore")
}
) )
@ -120,134 +110,160 @@ fun HomeScreen() {
// ========================================= // =========================================
NavigationBarItem( NavigationBarItem(
selected = selectedItem == 2, selected = selectedItem == 2,
onClick = { selectedItem = 2 },
onClick = { icon = { Icon(
selectedItem = 2
},
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
},
icon = { icon = {
Icon( Icon(
imageVector = Icons.Default.ChatBubbleOutline, imageVector = Icons.Default.ChatBubbleOutline,
contentDescription = "Chat" contentDescription = "Chat"
) )
}, },
label = { Text("Chat") }
label = {
Text("Chat")
}
) )
// ========================================= // =========================================
// PROFILE // PROFILE
// ========================================= // =========================================
NavigationBarItem( NavigationBarItem(
selected = selectedItem == 4, selected = selectedItem == 4,
onClick = { selectedItem = 4 },
onClick = {
selectedItem = 4
},
icon = { icon = {
Icon( Icon(
imageVector = Icons.Default.Person, imageVector = Icons.Default.Person,
contentDescription = "Profile" contentDescription = "Profile"
) )
}, },
label = { Text("Profile") }
label = {
Text("Profile")
}
) )
} }
} }
) { innerPadding -> ) { innerPadding ->
// ========================================= // =========================================
// CONTENT AREA // CONTENT AREA
// ========================================= // =========================================
Box( val context = LocalContext.current
modifier = Modifier.padding(innerPadding)
) {
when (selectedItem) { // or via hiltViewModel() / factory
Box(modifier = Modifier.padding(innerPadding)) {
when (selectedItem) {
// ========================================= // =========================================
// MEET // MEET
// ========================================= // =========================================
0 -> { 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 // EXPLORE
// ========================================= // =========================================
1 -> { 1 -> {
ExploreScreen() VibeCheckHomeScreen()
} }
// ========================================= // =========================================
// CREATE POST // CREATE POST
// ========================================= // =========================================
2 -> { 2 -> {
CreatePost() CreatePost()
} }
// ========================================= // =========================================
// CHAT // CHAT
// ========================================= // =========================================
3 -> { 3 -> {
ChatScreen() ChatScreen()
} }
// ========================================= // =========================================
// PROFILE // PROFILE
// ========================================= // =========================================
4 -> { 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) }
)
}
}
} }
} }
} }

385
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<MeetUserProfile> = 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 <token>" 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()

445
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
// ============================================================

17
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<List<MeetUserProfile>>
@GET("api/v1/users/me")
suspend fun getCurrentUserProfile(): Response<MyProfileResponse>
}

93
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<MediaItem>?
) {
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)
}

68
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)
}
}

155
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<MeetUserProfile>) : MeetUiState()
data class Error(val message: String) : MeetUiState()
}
class MeetViewModel : ViewModel() {
private val _meetUiState = MutableStateFlow<MeetUiState>(MeetUiState.Idle)
val meetUiState: StateFlow<MeetUiState> = _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>(MyProfileUiState.Idle)
val myProfileUiState: StateFlow<MyProfileUiState> = _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}")
}
}
}
}

66
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 }
}
}
}
}

12
app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt

@ -16,4 +16,16 @@ interface AuthApiService {
suspend fun uploadProfileImage( suspend fun uploadProfileImage(
@Part image: MultipartBody.Part @Part image: MultipartBody.Part
): Response<ProfileImageUploadResponse> ): Response<ProfileImageUploadResponse>
@Multipart
@POST("api/v1/users/me/video")
suspend fun uploadIntroVideo(
@Part video: MultipartBody.Part
): Response<ProfileImageUploadResponse> // Reusing response model containing data.url
@POST("/api/v1/auth/login")
suspend fun login(
@Body request: LoginRequest
): Response<LoginResponse>
} }

25
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.content.Context
import android.net.Uri import android.net.Uri
import com.example.dattingapp.homes.networkandapi.TokenManager
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody import okhttp3.MultipartBody
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -11,6 +12,7 @@ import retrofit2.converter.gson.GsonConverterFactory
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
object NetworkModule { object NetworkModule {
private const val BASE_URL = "https://melove.youthome.com.cn" private const val BASE_URL = "https://melove.youthome.com.cn"
@ -73,4 +75,27 @@ object NetworkModule {
null 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
}
}
} }

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

@ -43,3 +43,20 @@ data class ImageUploadResponse(
val message: String, val message: String,
val imageUrl: 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?
)

32
app/src/main/java/com/example/dattingapp/registration/RegistrationViewModel.kt

@ -5,6 +5,7 @@ import android.net.Uri
import android.util.Log import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.example.dattingapp.homes.networkandapi.TokenManager
import com.example.dattingapp.registration.NetworkModule.prepareImagePart import com.example.dattingapp.registration.NetworkModule.prepareImagePart
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -16,6 +17,7 @@ sealed class RegistrationUiState {
object Loading : 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 ImageUploadSuccess(val response: ProfileImageUploadResponse) : RegistrationUiState()
data class VideoUploadSuccess(val videoUrl: String?) : RegistrationUiState()
data class Error ( val message: String) : RegistrationUiState() data class Error ( val message: String) : RegistrationUiState()
} }
@ -68,7 +70,6 @@ class RegistrationViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
_uiState.value = RegistrationUiState.Loading _uiState.value = RegistrationUiState.Loading
Log.d(TAG, "Preparing image for upload from URI: $imageUri") Log.d(TAG, "Preparing image for upload from URI: $imageUri")
val imagePart = prepareImagePart(context, imageUri, partName = "image") val imagePart = prepareImagePart(context, imageUri, partName = "image")
if (imagePart == null) { if (imagePart == null) {
Log.e(TAG, "Failed to create MultipartBody.Part from Uri.") 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() { fun resetUiState() {
_uiState.value = RegistrationUiState.Idle _uiState.value = RegistrationUiState.Idle
} }

51
app/src/main/java/com/example/dattingapp/registration/TokenManager.kt

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

44
app/src/main/java/com/example/dattingapp/registration/UploadIntroVideoActivity.kt

@ -1,6 +1,7 @@
package com.example.dattingapp.registration package com.example.dattingapp.registration
import android.content.Context import android.content.Context
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
@ -9,6 +10,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
@ -40,6 +42,7 @@ import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView import androidx.media3.ui.PlayerView
import coil.compose.rememberAsyncImagePainter import coil.compose.rememberAsyncImagePainter
import com.example.dattingapp.homes.HomeActivity
import com.example.dattingapp.ui.theme.DattingAPPTheme import com.example.dattingapp.ui.theme.DattingAPPTheme
import java.io.File import java.io.File
@ -50,23 +53,55 @@ enum class DisplayMode {
} }
class UploadIntroVideoActivity : ComponentActivity() { class UploadIntroVideoActivity : ComponentActivity() {
private val viewModel: RegistrationViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
// Retrieve Profile Image URI string
val profileImageUriString = intent.getStringExtra("EXTRA_PROFILE_IMAGE_URI") val profileImageUriString = intent.getStringExtra("EXTRA_PROFILE_IMAGE_URI")
val profileImageUri: Uri? = profileImageUriString?.let { Uri.parse(it) } val profileImageUri: Uri? = profileImageUriString?.let { Uri.parse(it) }
setContent { setContent {
DattingAPPTheme { 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( UploadIntroVideoScreen(
profileImageUri = profileImageUri, profileImageUri = profileImageUri,
onBackClick = { finish() }, onBackClick = { finish() },
onSkipClick = { /* Handle skip */ }, onSkipClick = { finish() },
onSubmit = { videoUri, bioText -> onSubmit = { videoUri, bioText ->
// Final registration submission logic if (videoUri != null) {
Toast.makeText(this, "Registration Data Ready!", Toast.LENGTH_SHORT).show() // 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 @Composable
fun UploadIntroVideoScreen( fun UploadIntroVideoScreen(
profileImageUri: Uri?, profileImageUri: Uri?,

1
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.content.pm.PackageManager
import android.net.Uri import android.net.Uri
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.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult

11
app/src/main/res/xml/file_paths.xml

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Grants access to the app's internal cache directory -->
<cache-path name="cache_files" path="." />
<!-- Grants access to internal storage app-specific files directory -->
<files-path name="internal_files" path="." />
<!-- Grants access to external app storage directory -->
<external-files-path name="external_files" path="." />
</paths>
Loading…
Cancel
Save