Updated Login
This commit is contained in:
parent
8d2808734a
commit
d6b099f921
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MarkdownSettings">
|
||||
<option name="previewPanelProviderInfo">
|
||||
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -2,18 +2,17 @@ plugins {
|
|||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.livingai_lg"
|
||||
compileSdk {
|
||||
version = release(36)
|
||||
}
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.example.livingai_lg"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
|
|
@ -30,14 +29,15 @@ android {
|
|||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +50,20 @@ dependencies {
|
|||
implementation(libs.androidx.compose.ui.graphics)
|
||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation("androidx.navigation:navigation-compose:2.7.7")
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
|
||||
// Ktor
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.cio)
|
||||
implementation(libs.ktor.client.content.negotiation)
|
||||
implementation(libs.ktor.serialization.kotlinx.json)
|
||||
|
||||
// Kotlinx Serialization
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
|
||||
// AndroidX Security
|
||||
implementation(libs.androidx.security.crypto)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -2,6 +2,8 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
|
|
@ -10,11 +12,12 @@
|
|||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.LivingAi_Lg">
|
||||
android:theme="@style/Theme.LivingAi_Lg"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.LivingAi_Lg">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
|
||||
package com.example.livingai_lg
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.example.livingai_lg.ui.login.LoginScreen
|
||||
import com.example.livingai_lg.ui.login.SignUpScreen
|
||||
import com.example.livingai_lg.ui.login.OtpScreen
|
||||
import com.example.livingai_lg.ui.login.CreateProfileScreen
|
||||
import androidx.navigation.navArgument
|
||||
import com.example.livingai_lg.ui.login.*
|
||||
import com.example.livingai_lg.ui.theme.LivingAi_LgTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
|
@ -28,11 +26,32 @@ class MainActivity : ComponentActivity() {
|
|||
composable("signup") {
|
||||
SignUpScreen(navController = navController)
|
||||
}
|
||||
composable("otp") {
|
||||
OtpScreen(navController = navController)
|
||||
composable("signin") {
|
||||
SignInScreen(navController = navController)
|
||||
}
|
||||
composable("create_profile") {
|
||||
CreateProfileScreen(navController = navController)
|
||||
composable(
|
||||
"otp/{phoneNumber}/{name}",
|
||||
arguments = listOf(
|
||||
navArgument("phoneNumber") { type = NavType.StringType },
|
||||
navArgument("name") { type = NavType.StringType })
|
||||
) { backStackEntry ->
|
||||
OtpScreen(
|
||||
navController = navController,
|
||||
phoneNumber = backStackEntry.arguments?.getString("phoneNumber") ?: "",
|
||||
name = backStackEntry.arguments?.getString("name") ?: ""
|
||||
)
|
||||
}
|
||||
composable(
|
||||
"create_profile/{name}",
|
||||
arguments = listOf(navArgument("name") { type = NavType.StringType })
|
||||
) { backStackEntry ->
|
||||
CreateProfileScreen(
|
||||
navController = navController,
|
||||
name = backStackEntry.arguments?.getString("name") ?: ""
|
||||
)
|
||||
}
|
||||
composable("success") {
|
||||
SuccessScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,107 @@
|
|||
package com.example.livingai_lg.api
|
||||
|
||||
import android.os.Build
|
||||
import com.example.livingai_lg.BuildConfig
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
class AuthApiClient(private val baseUrl: String) {
|
||||
private val client = HttpClient {
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
ignoreUnknownKeys = true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun requestOtp(phoneNumber: String): Result<RequestOtpResponse> {
|
||||
return try {
|
||||
val response = client.post("$baseUrl/auth/request-otp") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(RequestOtpRequest(phoneNumber))
|
||||
}
|
||||
Result.success(response.body())
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun verifyOtp(
|
||||
phoneNumber: String,
|
||||
code: String,
|
||||
deviceId: String
|
||||
): Result<VerifyOtpResponse> {
|
||||
return try {
|
||||
val request = VerifyOtpRequest(phoneNumber, code, deviceId, getDeviceInfo())
|
||||
val response = client.post("$baseUrl/auth/verify-otp") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
Result.success(response.body())
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun refreshToken(refreshToken: String): Result<RefreshResponse> {
|
||||
return try {
|
||||
val request = RefreshRequest(refreshToken)
|
||||
val response = client.post("$baseUrl/auth/refresh") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
Result.success(response.body())
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateProfile(
|
||||
name: String,
|
||||
userType: String,
|
||||
accessToken: String
|
||||
): Result<UpdateProfileResponse> {
|
||||
return try {
|
||||
val request = UpdateProfileRequest(name, userType)
|
||||
val response = client.put("$baseUrl/users/me") {
|
||||
contentType(ContentType.Application.Json)
|
||||
header("Authorization", "Bearer $accessToken")
|
||||
setBody(request)
|
||||
}
|
||||
Result.success(response.body())
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun logout(refreshToken: String): Result<Unit> {
|
||||
return try {
|
||||
val request = RefreshRequest(refreshToken)
|
||||
client.post("$baseUrl/auth/logout") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
Result.success(Unit)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDeviceInfo(): DeviceInfo {
|
||||
return DeviceInfo(
|
||||
platform = "android",
|
||||
model = Build.MODEL,
|
||||
os_version = Build.VERSION.RELEASE,
|
||||
app_version = BuildConfig.VERSION_NAME,
|
||||
language_code = Locale.getDefault().toString(),
|
||||
timezone = TimeZone.getDefault().id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.example.livingai_lg.api
|
||||
|
||||
import android.content.Context
|
||||
import android.provider.Settings
|
||||
|
||||
class AuthManager(
|
||||
private val context: Context,
|
||||
private val apiClient: AuthApiClient,
|
||||
private val tokenManager: TokenManager
|
||||
) {
|
||||
suspend fun requestOtp(phoneNumber: String): Result<RequestOtpResponse> {
|
||||
return apiClient.requestOtp(phoneNumber)
|
||||
}
|
||||
|
||||
suspend fun login(phoneNumber: String, code: String): Result<VerifyOtpResponse> {
|
||||
val deviceId = getDeviceId()
|
||||
return apiClient.verifyOtp(phoneNumber, code, deviceId)
|
||||
.onSuccess { response ->
|
||||
tokenManager.saveTokens(response.access_token, response.refresh_token)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateProfile(name: String, userType: String): Result<UpdateProfileResponse> {
|
||||
val accessToken = tokenManager.getAccessToken() ?: return Result.failure(Exception("No access token found"))
|
||||
return apiClient.updateProfile(name, userType, accessToken)
|
||||
}
|
||||
|
||||
private fun getDeviceId(): String {
|
||||
return Settings.Secure.getString(
|
||||
context.contentResolver,
|
||||
Settings.Secure.ANDROID_ID
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.example.livingai_lg.api
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKey
|
||||
|
||||
class TokenManager(context: Context) {
|
||||
private val masterKey = MasterKey.Builder(context)
|
||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||
.build()
|
||||
|
||||
private val prefs: SharedPreferences = EncryptedSharedPreferences.create(
|
||||
context,
|
||||
"auth_tokens",
|
||||
masterKey,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
|
||||
fun saveTokens(accessToken: String, refreshToken: String) {
|
||||
prefs.edit().apply {
|
||||
putString("access_token", accessToken)
|
||||
putString("refresh_token", refreshToken)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun getAccessToken(): String? = prefs.getString("access_token", null)
|
||||
fun getRefreshToken(): String? = prefs.getString("refresh_token", null)
|
||||
|
||||
fun clearTokens() {
|
||||
prefs.edit().clear().apply()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.example.livingai_lg.api
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class RequestOtpRequest(val phone_number: String)
|
||||
|
||||
@Serializable
|
||||
data class RequestOtpResponse(val ok: Boolean)
|
||||
|
||||
@Serializable
|
||||
data class DeviceInfo(
|
||||
val platform: String,
|
||||
val model: String? = null,
|
||||
val os_version: String? = null,
|
||||
val app_version: String? = null,
|
||||
val language_code: String? = null,
|
||||
val timezone: String? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class VerifyOtpRequest(
|
||||
val phone_number: String,
|
||||
val code: String,
|
||||
val device_id: String,
|
||||
val device_info: DeviceInfo? = null
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class User(
|
||||
val id: String,
|
||||
val phone_number: String,
|
||||
val name: String?,
|
||||
val role: String,
|
||||
val user_type: String?
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class VerifyOtpResponse(
|
||||
val user: User,
|
||||
val access_token: String,
|
||||
val refresh_token: String,
|
||||
val needs_profile: Boolean
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RefreshRequest(val refresh_token: String)
|
||||
|
||||
@Serializable
|
||||
data class RefreshResponse(val access_token: String, val refresh_token: String)
|
||||
|
||||
@Serializable
|
||||
data class UpdateProfileRequest(val name: String, val user_type: String)
|
||||
|
||||
@Serializable
|
||||
data class UpdateProfileResponse(
|
||||
val id: String,
|
||||
val phone_number: String,
|
||||
val name: String?,
|
||||
val role: String,
|
||||
val user_type: String?
|
||||
)
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.example.livingai_lg.ui.login
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -7,11 +8,14 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -20,10 +24,30 @@ import androidx.compose.ui.unit.sp
|
|||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.example.livingai_lg.R
|
||||
import com.example.livingai_lg.api.AuthApiClient
|
||||
import com.example.livingai_lg.api.AuthManager
|
||||
import com.example.livingai_lg.api.TokenManager
|
||||
import com.example.livingai_lg.ui.theme.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun CreateProfileScreen(navController: NavController) {
|
||||
fun CreateProfileScreen(navController: NavController, name: String) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val authManager = remember { AuthManager(context, AuthApiClient("http://10.0.2.2:3000"), TokenManager(context)) }
|
||||
|
||||
fun updateProfile(userType: String) {
|
||||
scope.launch {
|
||||
authManager.updateProfile(name, userType)
|
||||
.onSuccess {
|
||||
navController.navigate("success") { popUpTo("login") { inclusive = true } }
|
||||
}
|
||||
.onFailure {
|
||||
Toast.makeText(context, "Failed to update profile: ${it.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -46,26 +70,26 @@ fun CreateProfileScreen(navController: NavController) {
|
|||
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
|
||||
ProfileTypeItem(text = "I'm a Seller", icon = R.drawable.ic_seller)
|
||||
ProfileTypeItem(text = "I'm a Seller", icon = R.drawable.ic_seller) { updateProfile("seller") }
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
ProfileTypeItem(text = "I'm a Buyer", icon = R.drawable.ic_buyer)
|
||||
ProfileTypeItem(text = "I'm a Buyer", icon = R.drawable.ic_buyer) { updateProfile("buyer") }
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
ProfileTypeItem(text = "I'm a Service Provider", icon = R.drawable.ic_service_provider)
|
||||
ProfileTypeItem(text = "I'm a Service Provider", icon = R.drawable.ic_service_provider) { updateProfile("service_provider") }
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
ProfileTypeItem(text = "I'm a Mandi Host", icon = R.drawable.ic_mandi_host)
|
||||
ProfileTypeItem(text = "I'm a Mandi Host", icon = R.drawable.ic_mandi_host) { /* TODO: Add user_type for Mandi Host */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ProfileTypeItem(text: String, icon: Int) {
|
||||
fun ProfileTypeItem(text: String, icon: Int, onClick: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(72.dp)
|
||||
.shadow(elevation = 1.dp, shape = RoundedCornerShape(16.dp))
|
||||
.background(Color.White, RoundedCornerShape(16.dp))
|
||||
.clickable { /* TODO */ }
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
|
@ -81,6 +105,6 @@ fun ProfileTypeItem(text: String, icon: Int) {
|
|||
@Composable
|
||||
fun CreateProfileScreenPreview() {
|
||||
LivingAi_LgTheme {
|
||||
CreateProfileScreen(rememberNavController())
|
||||
CreateProfileScreen(rememberNavController(), "John Doe")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
|
||||
package com.example.livingai_lg.ui.login
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
|
|
@ -10,9 +11,8 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -24,6 +24,8 @@ import com.example.livingai_lg.ui.theme.*
|
|||
|
||||
@Composable
|
||||
fun LoginScreen(navController: NavController) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -33,12 +35,7 @@ fun LoginScreen(navController: NavController) {
|
|||
)
|
||||
)
|
||||
) {
|
||||
// Decorative elements
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
// ... (decorative elements)
|
||||
}
|
||||
// Decorative elements...
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
|
@ -99,6 +96,8 @@ fun LoginScreen(navController: NavController) {
|
|||
textAlign = TextAlign.Center
|
||||
)
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
|
||||
// New User Button
|
||||
Button(
|
||||
onClick = { navController.navigate("signup") },
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
|
|
@ -109,9 +108,12 @@ fun LoginScreen(navController: NavController) {
|
|||
) {
|
||||
Text(text = "New user? Sign up", color = DarkerBrown, fontSize = 16.sp, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// Existing User Button
|
||||
Button(
|
||||
onClick = { /* TODO: Handle Sign in */ },
|
||||
onClick = { navController.navigate("signin") },
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = TerraCotta),
|
||||
modifier = Modifier
|
||||
|
|
@ -120,13 +122,18 @@ fun LoginScreen(navController: NavController) {
|
|||
) {
|
||||
Text(text = "Already a user? Sign in", color = DarkerBrown, fontSize = 16.sp, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
// Guest Button
|
||||
Text(
|
||||
text = "Continue as Guest",
|
||||
color = MidBrown,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.clickable { Toast.makeText(context, "Guest mode is not yet available", Toast.LENGTH_SHORT).show() }
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1.5f))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,18 @@
|
|||
|
||||
package com.example.livingai_lg.ui.login
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
|
|
@ -27,11 +21,21 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.example.livingai_lg.api.AuthApiClient
|
||||
import com.example.livingai_lg.api.AuthManager
|
||||
import com.example.livingai_lg.api.TokenManager
|
||||
import com.example.livingai_lg.ui.theme.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun OtpScreen(navController: NavController) {
|
||||
fun OtpScreen(navController: NavController, phoneNumber: String, name: String) {
|
||||
val otp = remember { mutableStateOf("") }
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val authManager = remember { AuthManager(context, AuthApiClient("http://10.0.2.2:3000"), TokenManager(context)) }
|
||||
|
||||
// Flag to determine if this is a sign-in flow for an existing user.
|
||||
val isSignInFlow = name == "existing_user"
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -54,36 +58,49 @@ fun OtpScreen(navController: NavController) {
|
|||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
for (i in 0..3) {
|
||||
TextField(
|
||||
value = if (otp.value.length > i) otp.value[i].toString() else "",
|
||||
onValueChange = { if (it.length <= 1) { /* TODO */ } },
|
||||
modifier = Modifier
|
||||
.width(60.dp)
|
||||
.height(60.dp)
|
||||
.shadow(elevation = 1.dp, shape = RoundedCornerShape(16.dp)),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedContainerColor = Color.White.copy(alpha = 0.9f),
|
||||
unfocusedContainerColor = Color.White.copy(alpha = 0.9f),
|
||||
disabledContainerColor = Color.White.copy(alpha = 0.9f),
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center, fontSize = 24.sp),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
||||
)
|
||||
}
|
||||
}
|
||||
TextField(
|
||||
value = otp.value,
|
||||
onValueChange = { if (it.length <= 6) otp.value = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(60.dp)
|
||||
.shadow(elevation = 1.dp, shape = RoundedCornerShape(16.dp)),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedContainerColor = Color.White.copy(alpha = 0.9f),
|
||||
unfocusedContainerColor = Color.White.copy(alpha = 0.9f),
|
||||
disabledContainerColor = Color.White.copy(alpha = 0.9f),
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center, fontSize = 24.sp),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
|
||||
Button(
|
||||
onClick = { navController.navigate("create_profile") },
|
||||
onClick = {
|
||||
scope.launch {
|
||||
authManager.login(phoneNumber, otp.value)
|
||||
.onSuccess { response ->
|
||||
if (isSignInFlow) {
|
||||
// For existing users, always go to the success screen.
|
||||
navController.navigate("success") { popUpTo("login") { inclusive = true } }
|
||||
} else {
|
||||
// For new users, check if a profile needs to be created.
|
||||
if (response.needs_profile) {
|
||||
navController.navigate("create_profile/$name")
|
||||
} else {
|
||||
navController.navigate("success") { popUpTo("login") { inclusive = true } }
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
Toast.makeText(context, "Invalid or expired OTP", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFE9A00)),
|
||||
modifier = Modifier
|
||||
|
|
@ -101,6 +118,6 @@ fun OtpScreen(navController: NavController) {
|
|||
@Composable
|
||||
fun OtpScreenPreview() {
|
||||
LivingAi_LgTheme {
|
||||
OtpScreen(rememberNavController())
|
||||
OtpScreen(rememberNavController(), "+919876543210", "John Doe")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
package com.example.livingai_lg.ui.login
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Phone
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.example.livingai_lg.api.AuthApiClient
|
||||
import com.example.livingai_lg.api.AuthManager
|
||||
import com.example.livingai_lg.api.TokenManager
|
||||
import com.example.livingai_lg.ui.theme.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun SignInScreen(navController: NavController) {
|
||||
val phoneNumber = remember { mutableStateOf("") }
|
||||
val isPhoneNumberValid = remember(phoneNumber.value) { phoneNumber.value.length == 10 && phoneNumber.value.all { it.isDigit() } }
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val authManager = remember { AuthManager(context, AuthApiClient("http://10.0.2.2:3000"), TokenManager(context)) }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
brush = Brush.linearGradient(
|
||||
colors = listOf(LightCream, LighterCream, LightestGreen)
|
||||
)
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 36.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(140.dp))
|
||||
|
||||
Row {
|
||||
Text("Farm", fontSize = 32.sp, fontWeight = FontWeight.Medium, color = Color(0xFFE17100))
|
||||
Text("Market", fontSize = 32.sp, fontWeight = FontWeight.Medium, color = Color.Black)
|
||||
}
|
||||
Text("Welcome back!", fontSize = 16.sp, color = Color(0xFF4A5565))
|
||||
|
||||
Spacer(modifier = Modifier.height(128.dp))
|
||||
|
||||
Text(
|
||||
text = "Enter Phone Number",
|
||||
fontSize = 16.sp,
|
||||
color = Color(0xFF364153),
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.align(Alignment.Start).padding(start = 21.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(65.dp)
|
||||
.height(52.dp)
|
||||
.shadow(elevation = 1.dp, shape = RoundedCornerShape(16.dp))
|
||||
.background(color = Color.White.copy(alpha = 0.9f), shape = RoundedCornerShape(16.dp))
|
||||
.border(width = 1.dp, color = Color.Black.copy(alpha = 0.07f), shape = RoundedCornerShape(16.dp)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text("+91", fontSize = 16.sp, color = Color(0xFF0A0A0A))
|
||||
}
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
TextField(
|
||||
value = phoneNumber.value,
|
||||
onValueChange = { phoneNumber.value = it },
|
||||
placeholder = { Text("Enter your Phone Number", color = Color(0xFF99A1AF)) },
|
||||
leadingIcon = { Icon(Icons.Default.Phone, contentDescription = null, tint = Color(0xFF99A1AF)) },
|
||||
colors = TextFieldDefaults.colors(
|
||||
focusedContainerColor = Color.White.copy(alpha = 0.9f),
|
||||
unfocusedContainerColor = Color.White.copy(alpha = 0.9f),
|
||||
disabledContainerColor = Color.White.copy(alpha = 0.9f),
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
isError = phoneNumber.value.isNotEmpty() && !isPhoneNumberValid,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
modifier = Modifier.weight(1f).height(52.dp).shadow(elevation = 1.dp, shape = RoundedCornerShape(16.dp)),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
|
||||
singleLine = true
|
||||
)
|
||||
}
|
||||
if (phoneNumber.value.isNotEmpty() && !isPhoneNumberValid) {
|
||||
Text(
|
||||
text = "Please enter a valid 10-digit phone number",
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(start = 16.dp, top = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
val fullPhoneNumber = "+91${phoneNumber.value}"
|
||||
scope.launch {
|
||||
authManager.requestOtp(fullPhoneNumber)
|
||||
.onSuccess {
|
||||
// For existing user, name is not needed, so we pass a placeholder
|
||||
navController.navigate("otp/$fullPhoneNumber/existing_user")
|
||||
}
|
||||
.onFailure {
|
||||
Toast.makeText(context, "Failed to send OTP: ${it.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = isPhoneNumberValid,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color(0xFFFE9A00),
|
||||
disabledContainerColor = Color(0xFFF8DDA7),
|
||||
contentColor = Color.White,
|
||||
disabledContentColor = Color.White.copy(alpha = 0.7f)
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp).shadow(elevation = 4.dp, shape = RoundedCornerShape(16.dp))
|
||||
) {
|
||||
Text("Sign In", fontSize = 16.sp, fontWeight = FontWeight.Medium)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun SignInScreenPreview() {
|
||||
LivingAi_LgTheme {
|
||||
SignInScreen(rememberNavController())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +1,23 @@
|
|||
|
||||
package com.example.livingai_lg.ui.login
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material.icons.filled.Phone
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
|
|
@ -32,12 +26,21 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.example.livingai_lg.api.AuthApiClient
|
||||
import com.example.livingai_lg.api.AuthManager
|
||||
import com.example.livingai_lg.api.TokenManager
|
||||
import com.example.livingai_lg.ui.theme.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun SignUpScreen(navController: NavController) {
|
||||
val name = remember { mutableStateOf("") }
|
||||
val phoneNumber = remember { mutableStateOf("") }
|
||||
val isPhoneNumberValid = remember(phoneNumber.value) { phoneNumber.value.length == 10 && phoneNumber.value.all { it.isDigit() } }
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
// Use 10.0.2.2 to connect to host machine's localhost from emulator
|
||||
val authManager = remember { AuthManager(context, AuthApiClient("http://10.0.2.2:3000"), TokenManager(context)) }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
|
@ -48,12 +51,6 @@ fun SignUpScreen(navController: NavController) {
|
|||
)
|
||||
)
|
||||
) {
|
||||
// Decorative elements from LoginScreen
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
// ...
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -129,19 +126,45 @@ fun SignUpScreen(navController: NavController) {
|
|||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
isError = phoneNumber.value.isNotEmpty() && !isPhoneNumberValid,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
modifier = Modifier.weight(1f).height(52.dp).shadow(elevation = 1.dp, shape = RoundedCornerShape(16.dp)),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
|
||||
singleLine = true
|
||||
)
|
||||
}
|
||||
if (phoneNumber.value.isNotEmpty() && !isPhoneNumberValid) {
|
||||
Text(
|
||||
text = "Please enter a valid 10-digit phone number",
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(start = 16.dp, top = 4.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
Button(
|
||||
onClick = { navController.navigate("otp") },
|
||||
onClick = {
|
||||
val fullPhoneNumber = "+91${phoneNumber.value}"
|
||||
scope.launch {
|
||||
authManager.requestOtp(fullPhoneNumber)
|
||||
.onSuccess {
|
||||
navController.navigate("otp/$fullPhoneNumber/${name.value}")
|
||||
}
|
||||
.onFailure {
|
||||
Toast.makeText(context, "Failed to send OTP: ${it.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = isPhoneNumberValid,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFE9A00)),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color(0xFFFE9A00),
|
||||
disabledContainerColor = Color(0xFFF8DDA7),
|
||||
contentColor = Color.White,
|
||||
disabledContentColor = Color.White.copy(alpha = 0.7f)
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp).shadow(elevation = 4.dp, shape = RoundedCornerShape(16.dp))
|
||||
) {
|
||||
Text("Sign In", color = Color.White, fontSize = 16.sp, fontWeight = FontWeight.Medium)
|
||||
|
|
@ -150,7 +173,7 @@ fun SignUpScreen(navController: NavController) {
|
|||
Spacer(modifier = Modifier.height(32.dp))
|
||||
|
||||
Row(modifier = Modifier.align(Alignment.CenterHorizontally)) {
|
||||
Text("Don\'t have an account? ", color = Color(0xFF4A5565), fontSize = 16.sp)
|
||||
Text("Don't have an account? ", color = Color(0xFF4A5565), fontSize = 16.sp)
|
||||
Text(
|
||||
text = "Sign up",
|
||||
color = Color(0xFFE17100),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
package com.example.livingai_lg.ui.login
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.example.livingai_lg.ui.theme.LightCream
|
||||
import com.example.livingai_lg.ui.theme.LighterCream
|
||||
import com.example.livingai_lg.ui.theme.LightestGreen
|
||||
import com.example.livingai_lg.ui.theme.LivingAi_LgTheme
|
||||
|
||||
@Composable
|
||||
fun SuccessScreen() {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
brush = Brush.linearGradient(
|
||||
colors = listOf(LightCream, LighterCream, LightestGreen)
|
||||
)
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
text = "Success!",
|
||||
fontSize = 48.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(
|
||||
text = "Your profile has been created.",
|
||||
fontSize = 24.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun SuccessScreenPreview() {
|
||||
LivingAi_LgTheme {
|
||||
SuccessScreen()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,17 @@
|
|||
[versions]
|
||||
agp = "8.13.1"
|
||||
kotlin = "2.0.21"
|
||||
coreKtx = "1.10.1"
|
||||
agp = "8.2.2"
|
||||
kotlin = "2.0.0"
|
||||
coreKtx = "1.13.1"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.1.5"
|
||||
espressoCore = "3.5.1"
|
||||
lifecycleRuntimeKtx = "2.6.1"
|
||||
activityCompose = "1.8.0"
|
||||
composeBom = "2024.09.00"
|
||||
junitVersion = "1.2.1"
|
||||
espressoCore = "3.6.1"
|
||||
lifecycleRuntimeKtx = "2.8.3"
|
||||
activityCompose = "1.9.0"
|
||||
composeBom = "2024.06.00"
|
||||
navigationCompose = "2.7.7"
|
||||
ktor = "2.3.12"
|
||||
kotlinxSerialization = "1.6.3"
|
||||
securityCrypto = "1.1.0-alpha06"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
|
|
@ -24,9 +28,22 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u
|
|||
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
||||
|
||||
# Ktor
|
||||
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
|
||||
ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" }
|
||||
ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" }
|
||||
ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
||||
|
||||
# Kotlinx Serialization
|
||||
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
||||
|
||||
# AndroidX Security
|
||||
androidx-security-crypto = { group = "androidx.security", name = "security-crypto", version.ref = "securityCrypto" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
|
|
|
|||
Loading…
Reference in New Issue