diff --git a/app/src/main/java/com/example/livingai_lg/api/AuthApiClient.kt b/app/src/main/java/com/example/livingai_lg/api/AuthApiClient.kt index ed4c610..549194d 100644 --- a/app/src/main/java/com/example/livingai_lg/api/AuthApiClient.kt +++ b/app/src/main/java/com/example/livingai_lg/api/AuthApiClient.kt @@ -30,8 +30,6 @@ class AuthApiClient(private val context: Context) { prettyPrint = true isLenient = true ignoreUnknownKeys = true - coerceInputValues = true // Coerce missing fields to default values - encodeDefaults = false }) } @@ -78,23 +76,9 @@ class AuthApiClient(private val context: Context) { } suspend fun verifyOtp(phoneNumber: String, code: String, deviceId: String): Result = runCatching { - // Trim and validate code (ensure no whitespace, exactly 6 digits) - val trimmedCode = code.trim() - - // Debug: Log the request details - android.util.Log.d("AuthApiClient", "Verify OTP Request:") - android.util.Log.d("AuthApiClient", " phone_number: $phoneNumber") - android.util.Log.d("AuthApiClient", " code (original): '$code' (length: ${code.length})") - android.util.Log.d("AuthApiClient", " code (trimmed): '$trimmedCode' (length: ${trimmedCode.length})") - android.util.Log.d("AuthApiClient", " device_id: $deviceId") - - // Create request object with trimmed code - val request = VerifyOtpRequest(phoneNumber, trimmedCode, deviceId, getDeviceInfo()) - val response: VerifyOtpResponse = client.post("auth/verify-otp") { contentType(ContentType.Application.Json) - // Send code as string - backend validation requires string type and bcrypt.compare needs string - setBody(request) + setBody(VerifyOtpRequest(phoneNumber, code.toInt(), deviceId, getDeviceInfo())) }.body() tokenManager.saveTokens(response.accessToken, response.refreshToken) @@ -107,18 +91,12 @@ class AuthApiClient(private val context: Context) { setBody(request.copy(deviceId = getDeviceId(), deviceInfo = getDeviceInfo())) } - // Handle both success and error responses + // Instead of throwing an exception on non-2xx, we return a result type + // that can be handled by the caller. if (response.status.isSuccess()) { response.body() } else { - // Try to parse error response as SignupResponse (for 409 conflicts with user_exists flag) - try { - response.body() - } catch (e: Exception) { - // If parsing fails, throw an exception with the status and message - val errorBody = response.bodyAsText() - throw Exception("Signup failed: ${response.status} - $errorBody") - } + response.body() } } diff --git a/app/src/main/java/com/example/livingai_lg/api/models.kt b/app/src/main/java/com/example/livingai_lg/api/models.kt index bb3f331..abe17a9 100644 --- a/app/src/main/java/com/example/livingai_lg/api/models.kt +++ b/app/src/main/java/com/example/livingai_lg/api/models.kt @@ -23,7 +23,7 @@ data class DeviceInfo( @Serializable data class VerifyOtpRequest( @SerialName("phone_number") val phoneNumber: String, - val code: String, // Changed from Int to String - backend expects string for bcrypt comparison + val code: Int, @SerialName("device_id") val deviceId: String, @SerialName("device_info") val deviceInfo: DeviceInfo? = null ) @@ -87,9 +87,9 @@ data class UpdateProfileRequest( data class User( val id: String, @SerialName("phone_number") val phoneNumber: String, - val name: String? = null, - val role: String? = null, // Made nullable - backend can return null for new users - @SerialName("user_type") val userType: String? = null, // Made nullable with default - backend may not return this + val name: String?, + val role: String, + @SerialName("user_type") val userType: String?, @SerialName("created_at") val createdAt: String? = null, @SerialName("country_code") val countryCode: String? = null ) diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/BuyAnimalCard.kt b/app/src/main/java/com/example/livingai_lg/ui/components/BuyAnimalCard.kt index e3c7a0d..35f4fd5 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/components/BuyAnimalCard.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/components/BuyAnimalCard.kt @@ -9,6 +9,8 @@ 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.Info +import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.LocationOn import androidx.compose.material.icons.outlined.Visibility import androidx.compose.material3.Icon @@ -153,12 +155,23 @@ fun BuyAnimalCard( horizontalArrangement = Arrangement.SpaceBetween ) { Column { - Text( - product.name ?: "", - fontSize = AppTypography.Body, - fontWeight = FontWeight.Medium - ) - Text( + Row { + Text( + product.breed ?: "", + fontSize = AppTypography.Body, + fontWeight = FontWeight.Medium + ) + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = null, + Modifier.padding(horizontal = 4.dp).size(16.dp).align(Alignment.CenterVertically), + + ) + + //InfoIconWithOverlay(infoText = product.breedInfo ?: "") + } + + Text( formatPrice(product.price), fontSize = AppTypography.Body, fontWeight = FontWeight.Medium diff --git a/app/src/main/java/com/example/livingai_lg/ui/components/InfoTooltip.kt b/app/src/main/java/com/example/livingai_lg/ui/components/InfoTooltip.kt new file mode 100644 index 0000000..4f2a795 --- /dev/null +++ b/app/src/main/java/com/example/livingai_lg/ui/components/InfoTooltip.kt @@ -0,0 +1,99 @@ +package com.example.livingai_lg.ui.components + +import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Info +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.TooltipBox +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.filled.Info +import androidx.compose.material3.Text +import androidx.compose.material3.TooltipDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +import androidx.compose.ui.Modifier +import androidx.compose.ui.zIndex + +@Composable +fun InfoTooltipOverlay( + text: String, + visible: Boolean, + onDismiss: () -> Unit +) { + if (!visible) return + + Box( + modifier = Modifier + .fillMaxSize() + .zIndex(100f) // ensure it overlays everything + ) { + // Dimmed background + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.35f)) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + onDismiss() + } + ) + + // Tooltip card + Box( + modifier = Modifier + .align(Alignment.Center) + .background(Color.Black, RoundedCornerShape(12.dp)) + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + Text( + text = text, + color = Color.White, + fontSize = 13.sp, + lineHeight = 18.sp + ) + } + } +} + +@Composable +fun InfoIconWithOverlay( + infoText: String +) { + var showInfo by remember { mutableStateOf(false) } + + Box { + Icon( + imageVector = Icons.Default.Info, + contentDescription = "Info", + tint = Color.Gray, + modifier = Modifier + .size(18.dp) + .clickable { showInfo = true } + ) + + InfoTooltipOverlay( + text = infoText, + visible = showInfo, + onDismiss = { showInfo = false } + ) + } +} diff --git a/app/src/main/java/com/example/livingai_lg/ui/models/Animal.kt b/app/src/main/java/com/example/livingai_lg/ui/models/Animal.kt index b6d6df8..657dc4a 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/models/Animal.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/models/Animal.kt @@ -5,6 +5,7 @@ data class Animal( val name: String? = null, val age: Int? = null, val breed: String? = null, + val breedInfo: String? = null, val price: Long? = null, val isFairPrice: Boolean? = null, val imageUrl: List? = null, @@ -28,6 +29,7 @@ val sampleAnimals = listOf( name = "Rita", age = 45, breed = "Gir", + breedInfo = "The 2nd best in India", location = "Punjab", distance = 12000, imageUrl = listOf("https://external-content.duckduckgo.com/iu/?u=http%3A%2F%2F4.bp.blogspot.com%2F_tecSnxaePMo%2FTLLVknW8dOI%2FAAAAAAAAACo%2F_kd1ZNBXU1o%2Fs1600%2FGIR%2CGujrat.jpg&f=1&nofb=1&ipt=da6ba1d040c396b64d3f08cc99998f66200dcd6c001e4a56def143ab3d1a87ea","https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcpimg.tistatic.com%2F4478702%2Fb%2F4%2Fgir-cow.jpg&f=1&nofb=1&ipt=19bf391461480585c786d01433d863a383c60048ac2ce063ce91f173e215205d"), @@ -49,6 +51,7 @@ val sampleAnimals = listOf( price = 95000, age = 35, breed = "Sahiwal", + breedInfo = "The 2nd best in India", location = "Punjab", isFairPrice = true, imageUrl = listOf("https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fcdnbbsr.s3waas.gov.in%2Fs3a5a61717dddc3501cfdf7a4e22d7dbaa%2Fuploads%2F2020%2F09%2F2020091812-1024x680.jpg&f=1&nofb=1&ipt=bb426406b3747e54151e4812472e203f33922fa3b4e11c4feef9aa59a5733146"), @@ -67,6 +70,7 @@ val sampleAnimals = listOf( name = "Bessie", age = 48, breed = "Holstein Friesian", + breedInfo = "Not Indian", location = "Punjab", distance = 12000, imageUrl = listOf("https://api.builder.io/api/v1/image/assets/TEMP/885e24e34ede6a39f708df13dabc4c1683c3e976?width=786"), diff --git a/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt b/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt index fe1426c..94cb893 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/navigation/AppNavigation.kt @@ -100,7 +100,7 @@ object AppScreen { object Graph { const val AUTH = "auth" - const val MAIN = "main" + const val MAIN = "auth" fun auth(route: String)= "$AUTH/$route" diff --git a/app/src/main/java/com/example/livingai_lg/ui/screens/auth/SignUpScreen.kt b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/SignUpScreen.kt index 2214eb2..b274c73 100644 --- a/app/src/main/java/com/example/livingai_lg/ui/screens/auth/SignUpScreen.kt +++ b/app/src/main/java/com/example/livingai_lg/ui/screens/auth/SignUpScreen.kt @@ -167,8 +167,8 @@ fun SignUpScreen( // Sign Up button Button( onClick = { - val fullPhoneNumber = "+91${formData.phoneNumber}" scope.launch { + val fullPhoneNumber = "+91${formData.phoneNumber}" // Request OTP first before allowing signup authManager.requestOtp(fullPhoneNumber) .onSuccess { @@ -177,7 +177,7 @@ fun SignUpScreen( onSignUpClick(fullPhoneNumber, formData.name, formData.state, formData.district, formData.village) } .onFailure { - Toast.makeText(context, "Failed to send OTP: ${it.message}", Toast.LENGTH_LONG).show() + Toast.makeText(context, "Signup failed: ${it.message}", Toast.LENGTH_LONG).show() } } },