From 5b6ce5f751792b791629915d17daf72951aaf2e1 Mon Sep 17 00:00:00 2001 From: osman160128 Date: Tue, 29 Sep 2026 11:35:07 +0600 Subject: [PATCH] update meet --- .../example/dattingapp/homes/MeetScreen.kt | 68 ++++++++++++----- .../com/example/dattingapp/homes/MyProfile.kt | 58 +++++++++------ .../homes/networkandapi/HomeApiService.kt | 7 ++ .../homes/networkandapi/HomeDataClass.kt | 23 +++++- .../homes/networkandapi/HomeViewModelClass.kt | 73 ++++++++++++++++++- .../dattingapp/registration/AuthApiService.kt | 4 +- 6 files changed, 190 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt b/app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt index 325ad45..88f090b 100644 --- a/app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt +++ b/app/src/main/java/com/example/dattingapp/homes/MeetScreen.kt @@ -1,6 +1,8 @@ package com.example.dattingapp.homes +import android.content.Context import android.util.Log +import androidx.compose.animation.core.Animatable import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape @@ -21,11 +23,15 @@ 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.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import coil.request.ImageRequest 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.homes.networkandapi.TokenManager import com.example.dattingapp.ui.theme.BrandPink import com.example.dattingapp.ui.theme.BrandPurple @@ -43,9 +49,10 @@ fun MeetDiscoveryScreen( onSearchClick: () -> Unit = {}, onSuperLikeClick: (MeetUserProfile) -> Unit = {}, onRejectClick: (MeetUserProfile) -> Unit = {}, - onLikeClick: (MeetUserProfile) -> Unit = {} + onLikeClick: (MeetUserProfile) -> Unit = {}, + swipeViewModel: SwipeViewModel = viewModel() ) { - // Index tracking current active user profile (starts at 0) + val context = LocalContext.current var currentIndex by remember { mutableIntStateOf(0) } Box( @@ -121,7 +128,6 @@ fun MeetDiscoveryScreen( } profiles.isNotEmpty() && currentIndex < profiles.size -> { - // Current profile starting at index 0 val currentProfile = profiles[currentIndex] ProfileCardItem( @@ -131,10 +137,12 @@ fun MeetDiscoveryScreen( currentIndex++ }, onReject = { + handleSwipeAction(context,direction="pass", currentProfile, swipeViewModel) onRejectClick(currentProfile) currentIndex++ }, onLike = { + handleSwipeAction(context,direction="like", currentProfile, swipeViewModel) onLikeClick(currentProfile) currentIndex++ } @@ -161,20 +169,22 @@ private fun ProfileCardItem( onReject: () -> Unit, onLike: () -> Unit ) { - // Extract primary photo URL from media list + val context = LocalContext.current + + val scope = rememberCoroutineScope() + // Reset drag offset whenever the profile changes + val offsetX = remember(profile) { Animatable(0f) } + 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" } @@ -186,10 +196,8 @@ private fun ProfileCardItem( .fillMaxSize() .clip(RoundedCornerShape(20.dp)) ) { - // BACKEND PROFILE IMAGE - val context = LocalContext.current val token = remember(context) { - TokenManager.getInstance(context).getAccessToken() + TokenManager.getInstance(context).getAccessToken() } AsyncImage( @@ -197,7 +205,6 @@ private fun ProfileCardItem( .data(photoUrl) .apply { if (!token.isNullOrBlank()) { - // Formatting matches "Bearer " or raw token based on backend preference val authHeader = if (token.startsWith("Bearer ", ignoreCase = true)) token else "Bearer $token" addHeader("Authorization", authHeader) } @@ -295,7 +302,6 @@ private fun ProfileCardItem( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - // Name & Age val displayName = profile.fullName ?: "User" val nameAndAge = if (age != null) "$displayName, $age" else displayName @@ -306,7 +312,6 @@ private fun ProfileCardItem( color = Color.White ) - // Compatibility / Match Percentage Badge profile.compatibilityPercentage?.let { match -> Surface( shape = RoundedCornerShape(10.dp), @@ -336,7 +341,6 @@ private fun ProfileCardItem( Spacer(modifier = Modifier.height(4.dp)) - // Location & Distance if (locationText.isNotBlank()) { Row( verticalAlignment = Alignment.CenterVertically, @@ -356,7 +360,6 @@ private fun ProfileCardItem( } } - // Bio profile.bio?.takeIf { it.isNotBlank() }?.let { bio -> Spacer(modifier = Modifier.height(8.dp)) Text( @@ -370,16 +373,43 @@ private fun ProfileCardItem( } } -// Utility to calculate age from date string (e.g. "1999-05-15") +//Helper to calculate age from birth date string (e.g., "1999-05-15") private fun calculateAge(birthDateStr: String?): Int? { - if (birthDateStr.isNull_or_blank()) return null + if (birthDateStr.isNullOrBlank()) return null return try { val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") - val birthDate = LocalDate.parse(birthDateStr?.take(10), formatter) + val birthDate = LocalDate.parse(birthDateStr.take(10), formatter) Period.between(birthDate, LocalDate.now()).years } catch (e: Exception) { null } } -private fun String?.isNull_or_blank(): Boolean = this == null || this.trim().isEmpty() \ No newline at end of file +// Helper method for reject swipe network call +private fun handleSwipeAction( + context: Context, + direction:String, + profile: MeetUserProfile, + swipeViewModel: SwipeViewModel +) { + val targetUserId = profile.id + val swipeRequest = SwipeRequest(targetUserId, direction) + swipeViewModel.sendSwipeResponse(context, swipeRequest) +} + +@Preview( + name = "Discovery Screen - Active Card", + showBackground = true, + widthDp = 390, + heightDp = 844 +) +@Composable +fun MeetDiscoveryScreenPreview() { + MaterialTheme { + MeetDiscoveryScreen( + profiles = emptyList(), + isLoading = false, + errorMessage = null + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt b/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt index d7f6ede..d28820d 100644 --- a/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt +++ b/app/src/main/java/com/example/dattingapp/homes/MyProfile.kt @@ -47,7 +47,6 @@ import com.example.dattingapp.ui.theme.TextMuted @Composable fun MyProfileScreen( - profile: MyProfileResponse?, // Change from MyProfileData? to MyProfileResponse? isLoading: Boolean, errorMessage: String?, @@ -57,39 +56,25 @@ fun MyProfileScreen( 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 - ), - + painter = painterResource(id = R.drawable.locationpermissionbg), contentDescription = "Cover Banner", - modifier = Modifier.fillMaxWidth().height(200.dp), contentScale = ContentScale.Crop ) @@ -179,7 +164,7 @@ fun MyProfileScreen( ) { Text( - text = "Alex, 28", + text = profile?.profile?.fullName?:"", fontSize = 22.sp, fontWeight = FontWeight.Bold, color = BrandMagenta @@ -222,7 +207,7 @@ fun MyProfileScreen( Text( - text = "Adventure seeker, dog lover, and amateur chef. Let's find a vibe!", + text = profile?.profile?.bio?:"", fontSize = 13.sp, color = TextMuted, lineHeight = 18.sp @@ -230,9 +215,7 @@ fun MyProfileScreen( } - Spacer( - modifier = Modifier.height(12.dp) - ) + Spacer(modifier = Modifier.height(12.dp)) // ==================================================== // PREMIUM BANNER // ==================================================== @@ -443,3 +426,36 @@ private fun StatCard( // ============================================================ // PREVIEW // ============================================================ +// 1. Success State Preview (With Mock User Profile Data) +@Preview(showBackground = true, showSystemUi = true) +@Composable +fun MyProfileScreenSuccessPreview() { + DattingAPPTheme { + // Create dummy profile response matching your backend structure + val mockProfileResponse = MyProfileResponse( + id = 1, + emailOrPhone = "alex.dev@example.com", + status = "active", + profile = com.example.dattingapp.homes.networkandapi.UserProfileDetails( + fullName = "Alex", + gender = "Male", + city = "New York", + country = "USA", + bio = "Adventure seeker, dog lover, and amateur chef. Let's find a vibe!", + profileImageUrl = "https://picsum.photos/300/300", // Sample remote URL + videoUrl = null + ), + createdAt = "2026-01-01" + ) + + MyProfileScreen( + profile = mockProfileResponse, + isLoading = false, + errorMessage = null, + onRetry = {}, + onEditClick = {}, + onUpgradeClick = {}, + onSettingsClick = {} + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt index e86a721..fa16577 100644 --- a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt +++ b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeApiService.kt @@ -1,7 +1,9 @@ package com.example.dattingapp.homes.networkandapi import retrofit2.Response +import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.POST import retrofit2.http.Query interface HomeApiService { @@ -14,4 +16,9 @@ interface HomeApiService { @GET("api/v1/users/me") suspend fun getCurrentUserProfile(): Response + @POST("api/v1/meet/swipes") + suspend fun recordSwipe( + @Body request: SwipeRequest + ): Response + } \ No newline at end of file diff --git a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeDataClass.kt b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeDataClass.kt index 6163283..9fb640e 100644 --- a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeDataClass.kt +++ b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeDataClass.kt @@ -90,4 +90,25 @@ data class MediaItem( ) { val fullMediaUrl: String? get() = resolveMediaUrl(url) -} \ No newline at end of file +} + + + +//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 +) \ No newline at end of file diff --git a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt index f2bf549..0278e39 100644 --- a/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt +++ b/app/src/main/java/com/example/dattingapp/homes/networkandapi/HomeViewModelClass.kt @@ -93,6 +93,8 @@ class MeetViewModel : ViewModel() { +// Myprofle ViewModel--------------------------------------------------------------------------------------- + sealed interface MyProfileUiState { object Idle : MyProfileUiState object Loading : MyProfileUiState @@ -152,4 +154,73 @@ class MyProfileViewModel : ViewModel() { } } } -} \ No newline at end of file +} + + + + +// Swife ViewModel--------------------------------------------------------------------------------------- + +sealed interface SwipeUiState { + object Idle : SwipeUiState + object Loading : SwipeUiState + data class Success(val swipedata: SwipeResponse) : SwipeUiState // Ensure this is MyProfileData, not MyProfileResponse + data class Error(val message: String) : SwipeUiState +} +class SwipeViewModel : ViewModel() { + + private val _swipeUiState = MutableStateFlow(SwipeUiState.Idle) + val swipeUiState: StateFlow = _swipeUiState.asStateFlow() + + fun sendSwipeResponse(context: Context,swipeRequest: SwipeRequest) { + if (_swipeUiState.value is SwipeUiState.Loading) return + + val appContext = context.applicationContext + + viewModelScope.launch { + _swipeUiState.value = SwipeUiState.Loading + + if (TokenManager.getInstance(appContext).getAccessToken().isNullOrBlank()) { + Log.e("MeetApi", "No access token in user_tokens preferences") + _swipeUiState.value = SwipeUiState.Error("No access token found. Please log in again.") + return@launch + } + + try { + val response = HomeNetworkModule + .getApiService(appContext) + .recordSwipe(swipeRequest) + + val statusCode = response.code() + Log.d("swipe 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 these profiles." + 404 -> "Meet endpoint was not found." + else -> "Request failed: HTTP ${response.code()}." + } + _swipeUiState.value = SwipeUiState.Error(message) + return@launch + } + + // FIX: Use response.body() directly as profileData + val swipeData = response.body() + + + if ( swipeData!= null) { + _swipeUiState.value = SwipeUiState.Success(swipeData) + } else { + _swipeUiState.value = SwipeUiState.Error("Profile data is empty.") + } + + } catch (e: Exception) { + Log.e("MeetApi", "Unexpected request failure", e) + _swipeUiState.value = SwipeUiState.Error("Request failed: ${e.javaClass.simpleName}") + } + } + } +} + diff --git a/app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt b/app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt index b88b627..174525c 100644 --- a/app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt +++ b/app/src/main/java/com/example/dattingapp/registration/AuthApiService.kt @@ -9,7 +9,9 @@ import retrofit2.http.Part interface AuthApiService { @POST("/api/v1/auth/register") - suspend fun registerUser(@Body request: RegisterRequest): Response + suspend fun registerUser( + @Body request: RegisterRequest + ): Response @Multipart @POST("api/v1/users/me/image")