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
|
prettyPrint = true
|
||||||
isLenient = true
|
isLenient = true
|
||||||
ignoreUnknownKeys = 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 {
|
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") {
|
val response: VerifyOtpResponse = client.post("auth/verify-otp") {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
// Send code as string - backend validation requires string type and bcrypt.compare needs string
|
setBody(VerifyOtpRequest(phoneNumber, code.toInt(), deviceId, getDeviceInfo()))
|
||||||
setBody(request)
|
|
||||||
}.body()
|
}.body()
|
||||||
|
|
||||||
tokenManager.saveTokens(response.accessToken, response.refreshToken)
|
tokenManager.saveTokens(response.accessToken, response.refreshToken)
|
||||||
|
|
@ -107,18 +91,12 @@ class AuthApiClient(private val context: Context) {
|
||||||
setBody(request.copy(deviceId = getDeviceId(), deviceInfo = getDeviceInfo()))
|
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()) {
|
if (response.status.isSuccess()) {
|
||||||
response.body<SignupResponse>()
|
response.body<SignupResponse>()
|
||||||
} else {
|
} else {
|
||||||
// Try to parse error response as SignupResponse (for 409 conflicts with user_exists flag)
|
response.body<SignupResponse>()
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ data class DeviceInfo(
|
||||||
@Serializable
|
@Serializable
|
||||||
data class VerifyOtpRequest(
|
data class VerifyOtpRequest(
|
||||||
@SerialName("phone_number") val phoneNumber: String,
|
@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_id") val deviceId: String,
|
||||||
@SerialName("device_info") val deviceInfo: DeviceInfo? = null
|
@SerialName("device_info") val deviceInfo: DeviceInfo? = null
|
||||||
)
|
)
|
||||||
|
|
@ -87,9 +87,9 @@ data class UpdateProfileRequest(
|
||||||
data class User(
|
data class User(
|
||||||
val id: String,
|
val id: String,
|
||||||
@SerialName("phone_number") val phoneNumber: String,
|
@SerialName("phone_number") val phoneNumber: String,
|
||||||
val name: String? = null,
|
val name: String?,
|
||||||
val role: String? = null, // Made nullable - backend can return null for new users
|
val role: String,
|
||||||
@SerialName("user_type") val userType: String? = null, // Made nullable with default - backend may not return this
|
@SerialName("user_type") val userType: String?,
|
||||||
@SerialName("created_at") val createdAt: String? = null,
|
@SerialName("created_at") val createdAt: String? = null,
|
||||||
@SerialName("country_code") val countryCode: 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.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
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.LocationOn
|
||||||
import androidx.compose.material.icons.outlined.Visibility
|
import androidx.compose.material.icons.outlined.Visibility
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
|
@ -153,12 +155,23 @@ fun BuyAnimalCard(
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Row {
|
||||||
product.name ?: "",
|
Text(
|
||||||
fontSize = AppTypography.Body,
|
product.breed ?: "",
|
||||||
fontWeight = FontWeight.Medium
|
fontSize = AppTypography.Body,
|
||||||
)
|
fontWeight = FontWeight.Medium
|
||||||
Text(
|
)
|
||||||
|
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),
|
formatPrice(product.price),
|
||||||
fontSize = AppTypography.Body,
|
fontSize = AppTypography.Body,
|
||||||
fontWeight = FontWeight.Medium
|
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 name: String? = null,
|
||||||
val age: Int? = null,
|
val age: Int? = null,
|
||||||
val breed: String? = null,
|
val breed: String? = null,
|
||||||
|
val breedInfo: String? = null,
|
||||||
val price: Long? = null,
|
val price: Long? = null,
|
||||||
val isFairPrice: Boolean? = null,
|
val isFairPrice: Boolean? = null,
|
||||||
val imageUrl: List<String>? = null,
|
val imageUrl: List<String>? = null,
|
||||||
|
|
@ -28,6 +29,7 @@ val sampleAnimals = listOf(
|
||||||
name = "Rita",
|
name = "Rita",
|
||||||
age = 45,
|
age = 45,
|
||||||
breed = "Gir",
|
breed = "Gir",
|
||||||
|
breedInfo = "The 2nd best in India",
|
||||||
location = "Punjab",
|
location = "Punjab",
|
||||||
distance = 12000,
|
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"),
|
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,
|
price = 95000,
|
||||||
age = 35,
|
age = 35,
|
||||||
breed = "Sahiwal",
|
breed = "Sahiwal",
|
||||||
|
breedInfo = "The 2nd best in India",
|
||||||
location = "Punjab",
|
location = "Punjab",
|
||||||
isFairPrice = true,
|
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"),
|
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",
|
name = "Bessie",
|
||||||
age = 48,
|
age = 48,
|
||||||
breed = "Holstein Friesian",
|
breed = "Holstein Friesian",
|
||||||
|
breedInfo = "Not Indian",
|
||||||
location = "Punjab",
|
location = "Punjab",
|
||||||
distance = 12000,
|
distance = 12000,
|
||||||
imageUrl = listOf("https://api.builder.io/api/v1/image/assets/TEMP/885e24e34ede6a39f708df13dabc4c1683c3e976?width=786"),
|
imageUrl = listOf("https://api.builder.io/api/v1/image/assets/TEMP/885e24e34ede6a39f708df13dabc4c1683c3e976?width=786"),
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ object AppScreen {
|
||||||
|
|
||||||
object Graph {
|
object Graph {
|
||||||
const val AUTH = "auth"
|
const val AUTH = "auth"
|
||||||
const val MAIN = "main"
|
const val MAIN = "auth"
|
||||||
|
|
||||||
fun auth(route: String)=
|
fun auth(route: String)=
|
||||||
"$AUTH/$route"
|
"$AUTH/$route"
|
||||||
|
|
|
||||||
|
|
@ -167,8 +167,8 @@ fun SignUpScreen(
|
||||||
// Sign Up button
|
// Sign Up button
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
val fullPhoneNumber = "+91${formData.phoneNumber}"
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
val fullPhoneNumber = "+91${formData.phoneNumber}"
|
||||||
// Request OTP first before allowing signup
|
// Request OTP first before allowing signup
|
||||||
authManager.requestOtp(fullPhoneNumber)
|
authManager.requestOtp(fullPhoneNumber)
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
|
|
@ -177,7 +177,7 @@ fun SignUpScreen(
|
||||||
onSignUpClick(fullPhoneNumber, formData.name, formData.state, formData.district, formData.village)
|
onSignUpClick(fullPhoneNumber, formData.name, formData.state, formData.district, formData.village)
|
||||||
}
|
}
|
||||||
.onFailure {
|
.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