Resolve merge conflicts: keep local changes for AuthApiClient, models, and SignUpScreen

This commit is contained in:
Chandresh Kerkar 2025-12-19 23:38:30 +05:30
commit 95c6c598a0
7 changed files with 133 additions and 39 deletions

View File

@ -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<VerifyOtpResponse> = 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<SignupResponse>()
} else {
// Try to parse error response as SignupResponse (for 409 conflicts with user_exists flag)
try {
response.body<SignupResponse>()
} 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<SignupResponse>()
}
}

View File

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

View File

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

View File

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

View File

@ -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<String>? = 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"),

View File

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

View File

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