Resolve merge conflicts: keep local changes for AuthApiClient, models, and SignUpScreen
This commit is contained in:
commit
95c6c598a0
|
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue