Browse Source

friend profile

master
osman160128 3 weeks ago
parent
commit
17930a1671
  1. 278
      app/src/main/java/com/example/dattingapp/FriendProfile.kt
  2. 71
      app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt
  3. 9
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt
  4. 74
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeDataClass.kt
  5. 69
      app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt
  6. 30
      app/src/main/java/com/example/dattingapp/permission/PermissionHelper.kt
  7. 1
      app/src/main/java/com/example/dattingapp/registration/RegisterRequest.kt
  8. 64
      app/src/main/java/com/example/dattingapp/registration/RegistrationScreen.kt
  9. 8
      app/src/main/java/com/example/dattingapp/ui/theme/Color.kt

278
app/src/main/java/com/example/dattingapp/FriendProfile.kt

@ -1,5 +1,6 @@
package com.example.dattingapp package com.example.dattingapp
import android.content.Context
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@ -17,46 +18,107 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.ChatBubble
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.MoreHoriz import androidx.compose.material.icons.filled.MoreHoriz
import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.outlined.LocationOn import androidx.compose.material.icons.outlined.LocationOn
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
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.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
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.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import com.example.dattingapp.homes.networkandapi.FriendProfileMedia
import com.example.dattingapp.homes.networkandapi.FriendProfileResponse
import com.example.dattingapp.homes.networkandapi.FriendProfileUiState
import com.example.dattingapp.homes.networkandapi.FriendProfileViewModel
import com.example.dattingapp.homes.networkandapi.MeetUserProfile
import com.example.dattingapp.homes.networkandapi.SwipeRequest
import com.example.dattingapp.homes.networkandapi.SwipeViewModel
import com.example.dattingapp.ui.theme.AccentPurple import com.example.dattingapp.ui.theme.AccentPurple
import com.example.dattingapp.ui.theme.DattingAPPTheme import com.example.dattingapp.ui.theme.DattingAPPTheme
import com.example.dattingapp.ui.theme.textDark import com.example.dattingapp.ui.theme.textDark
import com.example.dattingapp.ui.theme.BadgePink import com.example.dattingapp.ui.theme.BadgePink
import com.example.dattingapp.ui.theme.gradientBrush
import com.example.dattingapp.ui.theme.purpleButtonColor
import java.nio.file.WatchEvent import java.nio.file.WatchEvent
import java.time.LocalDate
import java.time.Period
import java.time.format.DateTimeFormatter
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.ui.unit.dp
class FriendProfile : ComponentActivity() { class FriendProfile : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
// Read extras passed from the Intent
val userId = intent.getIntExtra("USER_ID", -1)
setContent { setContent {
DattingAPPTheme { DattingAPPTheme {
val context = LocalContext.current
val viewModel: FriendProfileViewModel = viewModel()
FriendProfileScreen() // 2. Collect state from ViewModel
val uiState by viewModel.friendProfileUiState.collectAsState()
// 3. Trigger network call once when screen loads
LaunchedEffect(Unit) {
viewModel.fetchUserProfile(context,userId)
}
when (val state = uiState) {
is FriendProfileUiState.Loading -> {
// Show loading indicator
}
is FriendProfileUiState.Success -> {
val friendProfileData = state.friendProfile
FriendProfileScreen(friendProfileData,context)
}
is FriendProfileUiState.Error -> {
// Show error message
}
FriendProfileUiState.Idle -> {
// Idle state
}
}
} }
} }
@ -65,13 +127,24 @@ class FriendProfile : ComponentActivity() {
@Composable @Composable
fun FriendProfileScreen( fun FriendProfileScreen(
friendProfileData: FriendProfileResponse,
context: Context,
swipeViewModel: SwipeViewModel= viewModel() ,
onBackClick:()->Unit={}, onBackClick:()->Unit={},
onMoreClick:()->Unit={}, onMoreClick:()->Unit={},
onMessageClick:()->Unit={}, onMessageClick:()->Unit={},
onLikeClick:()->Unit={}, onLikeClick:()->Unit={},
onStarClick:()->Unit={}, onStarClick:()->Unit={},
onCancelClick:()->Unit={},
){ ){
val age = remember(friendProfileData.birthDate) {
calculateAge(friendProfileData.birthDate)
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@ -119,11 +192,14 @@ fun FriendProfileScreen(
} }
} }
Image( // Assuming 'profileImageUrl' is passed into your composable from FriendProfileResponse
painter = painterResource(id = R.drawable.locationpermissionbg), AsyncImage(
model = friendProfileData?.profileImageUrl, // Pass the String URL from FriendProfileResponse
contentDescription = "Profile Avatar", contentDescription = "Profile Avatar",
placeholder = painterResource(id = R.drawable.locationpermissionbg), // Placeholder while loading
error = painterResource(id = R.drawable.locationpermissionbg), // Fallback if URL fails or is null
modifier = Modifier modifier = Modifier
.padding(start = 20.dp) // Move padding to the VERY TOP of modifier chain .padding(start = 20.dp)
.align(Alignment.BottomStart) .align(Alignment.BottomStart)
.size(100.dp) .size(100.dp)
.clip(CircleShape) .clip(CircleShape)
@ -139,12 +215,16 @@ fun FriendProfileScreen(
.padding(horizontal = 20.dp) .padding(horizontal = 20.dp)
){ ){
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
val displayName = friendProfileData.fullName ?: "User"
val nameAndAge = if (age != null) "$displayName, $age" else displayName
Text( Text(
text = "Elena", text = nameAndAge,
fontSize = 24.sp, fontSize = 24.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = textDark color = textDark
@ -213,6 +293,8 @@ fun FriendProfileScreen(
color = Color.Gray color = Color.Gray
) )
} }
Surface( Surface(
onClick = { onClick = {
// Handle bio click action here (e.g., expand bio or navigate) // Handle bio click action here (e.g., expand bio or navigate)
@ -222,25 +304,207 @@ fun FriendProfileScreen(
color = Color(0xFFF9F9F9), color = Color(0xFFF9F9F9),
shadowElevation = 1.dp shadowElevation = 1.dp
) { ) {
val displayBio= friendProfileData.bio ?: ""
Text( Text(
text = "\"Always looking for the best flat white in the city. Amateur photographer, professional dog petter. Let...\"", text = displayBio,
fontSize = 13.sp, fontSize = 13.sp,
color = Color(0xFF4A4A4A), color = Color(0xFF4A4A4A),
modifier = Modifier.padding(12.dp), modifier = Modifier.padding(12.dp),
maxLines = 2,
lineHeight = 18.sp lineHeight = 18.sp
) )
} }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Message Button (Gradient Background)
Button(
onClick = onMessageClick,
modifier = Modifier
.weight(1f)
.height(48.dp)
.background(
brush = gradientBrush,
shape = RoundedCornerShape(10.dp)
),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = Color.White
),
shape = RoundedCornerShape(10.dp)
) {
Icon(
imageVector = Icons.Default.ChatBubble,
contentDescription = "Message",
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Message",
fontSize = 16.sp,
fontWeight = FontWeight.Bold
)
}
// Heart Button
IconButton(
onClick = {
// Option 1: Invoke callback directly if handleSwipeAction is inside the parent screen
onLikeClick()
// Option 2: Call handleSwipeAction directly here
handleLikeAndDislikeAction(
context = context,
direction = "right", // Or "like", matching your API endpoint expected value
profile = friendProfileData, // Your MeetUserProfile instance
swipeViewModel = swipeViewModel
)
},
modifier = Modifier
.size(48.dp)
.background(
color = purpleButtonColor,
shape = RoundedCornerShape(10.dp)
)
) {
Icon(
imageVector = Icons.Default.Favorite,
contentDescription = "Like",
tint = Color(0xFFE2C4FF), // Soft light purple icon color
modifier = Modifier.size(20.dp)
)
} }
// Star Button
IconButton(
onClick = {
onStarClick()
handleLikeAndDislikeAction(
context = context,
direction = "right", // Or "like", matching your API endpoint expected value
profile = friendProfileData, // Your MeetUserProfile instance
swipeViewModel = swipeViewModel
)
},
modifier = Modifier
.size(48.dp)
.background(
color = purpleButtonColor,
shape = RoundedCornerShape(10.dp)
)
) {
Icon(
imageVector = Icons.Default.Star,
contentDescription = "Favorite",
tint = Color(0xFFE2C4FF), // Soft light purple icon color
modifier = Modifier.size(20.dp)
)
} }
IconButton(
onClick = {
onStarClick()
handleLikeAndDislikeAction(
context = context,
direction = "right", // Or "like", matching your API endpoint expected value
profile = friendProfileData, // Your MeetUserProfile instance
swipeViewModel = swipeViewModel
)
},
modifier = Modifier
.size(48.dp)
.background(
color = purpleButtonColor,
shape = RoundedCornerShape(10.dp)
)
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Favorite",
tint = Color(0xFFE2C4FF), // Soft light purple icon color
modifier = Modifier.size(20.dp)
)
}
}
}
//
// LazyVerticalGrid(
// columns = GridCells.Fixed(3),
// contentPadding = PaddingValues(12.dp),
// horizontalArrangement = Arrangement.spacedBy(8.dp),
// verticalArrangement = Arrangement.spacedBy(8.dp),
// modifier = Modifier.fillMaxSize()
// ) {
// items(
// items = uiState.items,
// key = { post -> post.id }
// ) { item ->
// PostGridItem(item = item)
// }
// }
}
}
private fun handleLikeAndDislikeAction(
context: Context,
direction:String,
profile: FriendProfileResponse,
swipeViewModel: SwipeViewModel
) {
val targetUserId = profile.id
val swipeRequest = SwipeRequest(targetUserId, direction)
swipeViewModel.sendSwipeResponse(context, swipeRequest)
}
//calculte age
private fun calculateAge(birthDateStr: String?): Int? {
if (birthDateStr.isNullOrBlank()) 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
}
} }
@Preview(showBackground = true, showSystemUi = true) @Preview(showBackground = true, showSystemUi = true)
@Composable @Composable
fun FriendProfileScreenPreview() { fun FriendProfileScreenPreview() {
DattingAPPTheme { DattingAPPTheme {
FriendProfileScreen() val mockProfileResponse = FriendProfileResponse(
id = 101,
fullName = "Elena Rostova",
gender = "Female",
birthDate = "1999-05-14",
city = "San Francisco",
country = "USA",
relationshipGoal = "Long-term relationship",
bio = "Always looking for the best flat white in the city. Amateur photographer, professional dog petter.",
profileImageUrl = "https://via.placeholder.com/150",
videoUrl = null,
compatibilityPercentage = 98,
media = null
)
FriendProfileScreen(
friendProfileData = mockProfileResponse,
context = LocalContext.current,
onBackClick = {},
onMoreClick = {},
onMessageClick = {},
onLikeClick = {},
onStarClick = {}
)
} }
} }

71
app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt

@ -1,9 +1,11 @@
package com.example.dattingapp.homes package com.example.dattingapp.homes
import android.content.Context import android.content.Context
import android.content.Intent
import android.util.Log import android.util.Log
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
@ -20,6 +22,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@ -29,6 +32,7 @@ import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.example.dattingapp.FriendProfile
import com.example.dattingapp.homes.networkandapi.MeetUserProfile import com.example.dattingapp.homes.networkandapi.MeetUserProfile
import com.example.dattingapp.homes.networkandapi.SwipeRequest import com.example.dattingapp.homes.networkandapi.SwipeRequest
import com.example.dattingapp.homes.networkandapi.SwipeViewModel import com.example.dattingapp.homes.networkandapi.SwipeViewModel
@ -199,10 +203,11 @@ private fun ProfileCardItem(
val token = remember(context) { val token = remember(context) {
TokenManager.getInstance(context).getAccessToken() TokenManager.getInstance(context).getAccessToken()
} }
val defaultPainter = rememberVectorPainter(Icons.Default.Person)
AsyncImage( AsyncImage(
model = ImageRequest.Builder(context) model = ImageRequest.Builder(context)
.data(photoUrl) .data(photoUrl.takeIf { !it.isNullOrBlank() })
.apply { .apply {
if (!token.isNullOrBlank()) { if (!token.isNullOrBlank()) {
val authHeader = if (token.startsWith("Bearer ", ignoreCase = true)) token else "Bearer $token" val authHeader = if (token.startsWith("Bearer ", ignoreCase = true)) token else "Bearer $token"
@ -211,6 +216,9 @@ private fun ProfileCardItem(
} }
.crossfade(true) .crossfade(true)
.build(), .build(),
placeholder = defaultPainter,
error = defaultPainter,
fallback = defaultPainter, // Shows Icons.Default.Person immediately when photoUrl is null/blank
contentDescription = "Profile Image", contentDescription = "Profile Image",
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
@ -299,6 +307,16 @@ private fun ProfileCardItem(
.fillMaxWidth(0.70f) .fillMaxWidth(0.70f)
) { ) {
Row( Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
val intent = Intent(context, FriendProfile::class.java).apply {
putExtra("USER_ID",profile.id )
}
context.startActivity(intent)
},
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
@ -397,3 +415,54 @@ private fun handleSwipeAction(
swipeViewModel.sendSwipeResponse(context, swipeRequest) swipeViewModel.sendSwipeResponse(context, swipeRequest)
} }
// ==========================================
// PREVIEWS & MOCK DATA
// ==========================================
private val mockProfiles = listOf(
MeetUserProfile(
id = 1,
fullName = "Sophia Chen",
birthDate = "1998-06-15",
city = "San Francisco",
country = "USA",
distanceKm = 4.2,
bio = "Coffee enthusiast, UI designer, and full-time dog lover 🐶✨ Looking for someone to explore new coffee shops with!",
compatibilityPercentage = 92,
media = null, // Triggers fallback default Person icon
gender = "Male",
videoUrl = null
),
MeetUserProfile(
id = 2,
fullName = "Alex Rivera",
birthDate = "1995-11-03",
city = "Los Angeles",
country = "USA",
distanceKm = 12.0,
bio = "Software engineer by day, musician by night 🎸 Let's jam or talk about tech!",
compatibilityPercentage = 85,
media = null,
gender = "Male",
videoUrl = null
)
)
@Preview(name = "Discovery Screen - Loaded State", showBackground = true)
@Composable
fun MeetDiscoveryScreenPreview() {
MaterialTheme {
MeetDiscoveryScreen(
profiles = mockProfiles,
isLoading = false,
errorMessage = null,
onAddClick = {},
onSearchClick = {},
onSuperLikeClick = {},
onRejectClick = {},
onLikeClick = {}
)
}
}

9
app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt

@ -4,6 +4,7 @@ import retrofit2.Response
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query
interface HomeApiService { interface HomeApiService {
@ -17,8 +18,14 @@ interface HomeApiService {
suspend fun getCurrentUserProfile(): Response<MyProfileResponse> suspend fun getCurrentUserProfile(): Response<MyProfileResponse>
@POST("api/v1/meet/swipes") @POST("api/v1/meet/swipes")
suspend fun recordSwipe( suspend fun sendSwipe(
@Body request: SwipeRequest @Body request: SwipeRequest
): Response<SwipeResponse> ): Response<SwipeResponse>
@GET("api/v1/users/{user_id}")
suspend fun getFriendProfile(
@Path("user_id") userId: Int
): Response<FriendProfileResponse>
} }

74
app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeDataClass.kt

@ -92,27 +92,6 @@ data class MediaItem(
get() = resolveMediaUrl(url) get() = resolveMediaUrl(url)
} }
//swife data
// Request payload
data class SwipeRequest(
@SerializedName("target_user_id")
val targetUserId: Int,
@SerializedName("direction")
val direction: String // "like", "dislike", "super_like"
)
data class SwipeResponse(
@SerializedName("direction")
val direction: String?,
@SerializedName("matched")
val matched: Boolean = false,
@SerializedName("match_id")
val matchId: Int? = null
)
//feeling data for create post //feeling data for create post
data class VibeFeeling( data class VibeFeeling(
val name: String, val name: String,
@ -145,3 +124,56 @@ val vibeFeelingsList = listOf(
VibeFeeling("Coding", "💻", "Activities"), VibeFeeling("Coding", "💻", "Activities"),
VibeFeeling("Gaming", "🎮", "Activities") VibeFeeling("Gaming", "🎮", "Activities")
) )
// Request Model for Swipe
data class SwipeRequest(
@SerializedName("target_user_id")
val targetUserId: Int,
@SerializedName("direction")
val direction: String
)
// Response Model
data class SwipeResponse(
@SerializedName("direction")
val direction: String?,
@SerializedName("matched")
val matched: Boolean?,
@SerializedName("match_id")
val matchId: Long?
)
// data class friend profile
data class FriendProfileMedia(
val id: Int,
@SerializedName("media_type")
val mediaType: String?,
val url: String?
)
data class FriendProfileResponse(
val id: Int,
@SerializedName("full_name")
val fullName: String?,
val gender: String?,
@SerializedName("birth_date")
val birthDate: String?,
val city: String?,
val country: String?,
@SerializedName("relationship_goal")
val relationshipGoal: String?,
val bio: String?,
@SerializedName("profile_image_url")
val profileImageUrl: String?,
@SerializedName("video_url")
val videoUrl: String?,
@SerializedName("compatibility_percentage")
val compatibilityPercentage: Int?,
val media: List<FriendProfileMedia>?
)

69
app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt

@ -159,7 +159,7 @@ class MyProfileViewModel : ViewModel() {
// Swife ViewModel--------------------------------------------------------------------------------------- //for ViewModel swife --------------------------------------------------------------------------------------
sealed interface SwipeUiState { sealed interface SwipeUiState {
object Idle : SwipeUiState object Idle : SwipeUiState
@ -189,7 +189,7 @@ class SwipeViewModel : ViewModel() {
try { try {
val response = HomeNetworkModule val response = HomeNetworkModule
.getApiService(appContext) .getApiService(appContext)
.recordSwipe(swipeRequest) .sendSwipe(swipeRequest)
val statusCode = response.code() val statusCode = response.code()
Log.d("swipe code", "Response Code: $statusCode") Log.d("swipe code", "Response Code: $statusCode")
@ -224,3 +224,68 @@ class SwipeViewModel : ViewModel() {
} }
} }
//for friend profile --------------------------------------------------------------------------------------
sealed interface FriendProfileUiState {
object Idle : FriendProfileUiState
object Loading : FriendProfileUiState
data class Success(val friendProfile: FriendProfileResponse) : FriendProfileUiState
data class Error(val message: String) : FriendProfileUiState
}
class FriendProfileViewModel : ViewModel() {
private val _friendProfileUiState = MutableStateFlow<FriendProfileUiState>(FriendProfileUiState.Idle)
val friendProfileUiState: StateFlow<FriendProfileUiState> = _friendProfileUiState.asStateFlow()
fun fetchUserProfile(context: Context, userId: Int) {
if (_friendProfileUiState.value is FriendProfileUiState.Loading) return
val appContext = context.applicationContext
viewModelScope.launch {
_friendProfileUiState.value = FriendProfileUiState.Loading
if (TokenManager.getInstance(appContext).getAccessToken().isNullOrBlank()) {
Log.e("MeetApi", "No access token in user_tokens preferences")
_friendProfileUiState.value = FriendProfileUiState.Error("No access token found. Please log in again.")
return@launch
}
try {
val response = HomeNetworkModule
.getApiService(appContext)
.getFriendProfile(userId)
val statusCode = response.code()
Log.d("userProfile code", "Response Code: $statusCode")
if (!response.isSuccessful) {
response.errorBody()?.close()
val message = when (response.code()) {
401 -> "Authentication failed. Please log in again."
403 -> "The server refused access to this profile."
404 -> "User profile was not found."
else -> "Request failed: HTTP ${response.code()}."
}
_friendProfileUiState.value = FriendProfileUiState.Error(message)
return@launch
}
val profileData = response.body()
if (profileData != null) {
_friendProfileUiState.value = FriendProfileUiState.Success(profileData)
} else {
_friendProfileUiState.value = FriendProfileUiState.Error("Profile data is empty.")
}
} catch (e: Exception) {
Log.e("MeetApi", "Unexpected request failure", e)
_friendProfileUiState.value = FriendProfileUiState.Error("Request failed: ${e.javaClass.simpleName}")
}
}
}
}

30
app/src/main/java/com/example/dattingapp/permission/PermissionHelper.kt

@ -28,7 +28,7 @@ class PermissionHelper(private val activity: ComponentActivity) {
permissionLauncher = activity.registerForActivityResult( permissionLauncher = activity.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions() ActivityResultContracts.RequestMultiplePermissions()
) { permissions -> ) { permissions ->
// Check if all permissions requested are granted // Check if at least one requested location permission (or all requested) is granted
val allGranted = permissions.entries.all { it.value } val allGranted = permissions.entries.all { it.value }
onResult?.invoke(allGranted) onResult?.invoke(allGranted)
} }
@ -57,6 +57,23 @@ class PermissionHelper(private val activity: ComponentActivity) {
} }
} }
/**
* Helper to check location permission specifically (either FINE or COARSE)
*/
fun hasLocationPermission(): Boolean {
val fineGranted = ContextCompat.checkSelfPermission(
activity,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
val coarseGranted = ContextCompat.checkSelfPermission(
activity,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
return fineGranted || coarseGranted
}
/** /**
* Request standard runtime permissions: NOTIFICATION, GALLERY, AUDIO, or LOCATION * Request standard runtime permissions: NOTIFICATION, GALLERY, AUDIO, or LOCATION
*/ */
@ -71,6 +88,17 @@ class PermissionHelper(private val activity: ComponentActivity) {
} }
} }
/**
* Direct method to request Location Permission
*/
fun requestLocationPermission(callback: (Boolean) -> Unit) {
if (hasLocationPermission()) {
callback(true)
} else {
requestPermissions(PermissionType.LOCATION, callback)
}
}
/** /**
* Request Special Permission: Draw Over Other Apps * Request Special Permission: Draw Over Other Apps
*/ */

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

@ -7,6 +7,7 @@ data class RegisterRequest(
@SerializedName("email_or_phone") val emailOrPhone: String, @SerializedName("email_or_phone") val emailOrPhone: String,
@SerializedName("password") val password: String, @SerializedName("password") val password: String,
@SerializedName("confirm_password") val confirmPassword: String, @SerializedName("confirm_password") val confirmPassword: String,
@SerializedName("relationship_goal") val relationshipGoal: String,
@SerializedName("full_name") val fullName: String, @SerializedName("full_name") val fullName: String,
@SerializedName("gender") val gender: String, @SerializedName("gender") val gender: String,
@SerializedName("birth_date") val birthDate: String, @SerializedName("birth_date") val birthDate: String,

64
app/src/main/java/com/example/dattingapp/registration/RegistrationScreen.kt

@ -122,7 +122,7 @@ fun RegistrationFlowContainer(
var selectedBirthDateMillis by rememberSaveable { mutableStateOf<Long?>(null) } var selectedBirthDateMillis by rememberSaveable { mutableStateOf<Long?>(null) }
var addressInput by rememberSaveable { mutableStateOf("") } var addressInput by rememberSaveable { mutableStateOf("") }
var selectedGender by rememberSaveable { mutableStateOf("") } var selectedGender by rememberSaveable { mutableStateOf("") }
var selectedRealationStatus by remember { mutableStateOf<String?>(null) } var selectedRealationStatus by remember { mutableStateOf("") }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
@ -408,6 +408,7 @@ fun RegistrationFlowContainer(
emailOrPhone = contact, emailOrPhone = contact,
password = passwordInput, password = passwordInput,
confirmPassword = confirmPasswordInput, confirmPassword = confirmPasswordInput,
relationshipGoal = selectedRealationStatus.lowercase(Locale.ROOT).replace(" ", "_"),
fullName = nameInput.trim(), fullName = nameInput.trim(),
gender = selectedGender.lowercase(Locale.ROOT), gender = selectedGender.lowercase(Locale.ROOT),
birthDate = formattedDate, birthDate = formattedDate,
@ -1496,6 +1497,7 @@ fun UniquePulseLoader(
data class VibeOption( data class VibeOption(
val title: String, val title: String,
val value: String, // Value sent to backend API
val description: String, val description: String,
val icon: ImageVector, val icon: ImageVector,
val iconBgColor: Color, val iconBgColor: Color,
@ -1511,6 +1513,7 @@ fun VibeCheckScreen(
listOf( listOf(
VibeOption( VibeOption(
title = "Serious Relationship", title = "Serious Relationship",
value = "serious_relationship",
description = "Looking for the one", description = "Looking for the one",
icon = Icons.Default.FavoriteBorder, icon = Icons.Default.FavoriteBorder,
iconBgColor = Color(0xFFFFE3E3), iconBgColor = Color(0xFFFFE3E3),
@ -1518,6 +1521,7 @@ fun VibeCheckScreen(
), ),
VibeOption( VibeOption(
title = "Casual Dates", title = "Casual Dates",
value = "casual_dates",
description = "Drinks & good times", description = "Drinks & good times",
icon = Icons.Default.LocalBar, icon = Icons.Default.LocalBar,
iconBgColor = Color(0xFFEDE7F6), iconBgColor = Color(0xFFEDE7F6),
@ -1525,6 +1529,7 @@ fun VibeCheckScreen(
), ),
VibeOption( VibeOption(
title = "New Friends", title = "New Friends",
value = "new_friends",
description = "Expand my circle", description = "Expand my circle",
icon = Icons.Default.People, icon = Icons.Default.People,
iconBgColor = Color(0xFFFFE0B2), iconBgColor = Color(0xFFFFE0B2),
@ -1532,6 +1537,7 @@ fun VibeCheckScreen(
), ),
VibeOption( VibeOption(
title = "Marriage", title = "Marriage",
value = "marriage",
description = "Seeing where things go", description = "Seeing where things go",
icon = Icons.Default.CompassCalibration, icon = Icons.Default.CompassCalibration,
iconBgColor = Color(0xFFE0E0E0), iconBgColor = Color(0xFFE0E0E0),
@ -1540,72 +1546,26 @@ fun VibeCheckScreen(
) )
} }
val backgroundBrush = Brush.verticalGradient( // ... rest of background and header layout ...
colors = listOf(
Color(0xFFFFF0F3),
Color(0xFFFFFFFF),
Color(0xFFFFF0F5)
)
)
Box(
modifier = Modifier
.fillMaxSize()
.background(backgroundBrush)
.padding(horizontal = 24.dp, vertical = 32.dp)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "VibeCheck",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = Color(0xFFB0003A)
)
Spacer(modifier = Modifier.height(32.dp))
Text(
text = "What's your vibe?",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color(0xFF1E293B)
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Select your main goal right now to help us find theright connections.",
fontSize = 13.sp,
color = Color(0xFF64748B),
textAlign = TextAlign.Center,
lineHeight = 18.sp
)
Spacer(modifier = Modifier.height(48.dp))
Column( Column(
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
vibeOptions.forEach { option -> vibeOptions.forEach { option ->
val isSelected = option.title == selectedRealationStatus // Check selection against option.value
val isSelected = option.value == selectedRealationStatus
VibeCard( VibeCard(
option = option, option = option,
isSelected = isSelected, isSelected = isSelected,
onClick = { onClick = {
onRelationSelected(option.title) // Send backend value instead of UI title
onRelationSelected(option.value)
} }
) )
} }
} }
}
}
} }
@Composable @Composable
fun VibeCard( fun VibeCard(
option: VibeOption, option: VibeOption,

8
app/src/main/java/com/example/dattingapp/ui/theme/Color.kt

@ -111,3 +111,11 @@ val SectionHeaderColor = Color(0xFF757575)
val CardBackgroundColor = Color.White val CardBackgroundColor = Color.White
val LinkTextColor = Color(0xFF7B1FA2) val LinkTextColor = Color(0xFF7B1FA2)
val UnreadDotColor = Color(0xFFD32F2F) val UnreadDotColor = Color(0xFFD32F2F)
val gradientBrush = Brush.horizontalGradient(
colors = listOf(
Color(0xFFB00040), // Dark Pink/Magenta
Color(0xFF6B00B0) // Deep Purple
)
)
val purpleButtonColor = Color(0xFF6B00F5)
Loading…
Cancel
Save