diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
new file mode 100644
index 0000000..f789f7d
--- /dev/null
+++ b/.idea/appInsightsSettings.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index 7fea705..144c4bd 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -2,12 +2,12 @@
-
+
-
+
-
+
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index fdf8d99..d4b7acc 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8978d23..22fd288 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,8 +1,6 @@
-
-
-
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 94a25f7..35eb1dd 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.kotlin/errors/errors-1729205467831.log b/.kotlin/errors/errors-1729205467831.log
new file mode 100644
index 0000000..7cd151b
--- /dev/null
+++ b/.kotlin/errors/errors-1729205467831.log
@@ -0,0 +1,4 @@
+kotlin version: 2.0.20
+error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
+ 1. Kotlin compile daemon is ready
+
diff --git a/.kotlin/errors/errors-1730660052865.log b/.kotlin/errors/errors-1730660052865.log
new file mode 100644
index 0000000..7cd151b
--- /dev/null
+++ b/.kotlin/errors/errors-1730660052865.log
@@ -0,0 +1,4 @@
+kotlin version: 2.0.20
+error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
+ 1. Kotlin compile daemon is ready
+
diff --git a/.kotlin/errors/errors-1730754361219.log b/.kotlin/errors/errors-1730754361219.log
new file mode 100644
index 0000000..7cd151b
--- /dev/null
+++ b/.kotlin/errors/errors-1730754361219.log
@@ -0,0 +1,4 @@
+kotlin version: 2.0.20
+error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
+ 1. Kotlin compile daemon is ready
+
diff --git a/.kotlin/errors/errors-1731096633063.log b/.kotlin/errors/errors-1731096633063.log
new file mode 100644
index 0000000..7cd151b
--- /dev/null
+++ b/.kotlin/errors/errors-1731096633063.log
@@ -0,0 +1,4 @@
+kotlin version: 2.0.20
+error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
+ 1. Kotlin compile daemon is ready
+
diff --git a/README.md b/README.md
index 5cf7283..0a52232 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,4 @@
### Visit the android developers website
-[Compila apps adaptables con las nuevas APIs de Compose, ahora estables](https://developer.android.com/?hl=es-419#:~:text=Discover%20the%20latest%20app%20development%20tools,)
+# [Compila apps adaptables con las nuevas APIs de Compose, ahora estables](https://developer.android.com/?hl=es-419#:~:text=Discover%20the%20latest%20app%20development%20tools,)
+no olvides configurar el gradle para una exitosa ejecución,
+el apk será generado y compartido en su debido momento
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 18e07e9..821592b 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,7 +1,10 @@
plugins {
id("com.android.application") version "8.6.1" // AGP version
- id("org.jetbrains.kotlin.android") version "1.9.0" // Kotlin plugin for Android
- id("org.jetbrains.kotlin.plugin.serialization") version "1.9.0" // Kotlin serialization plugin
+ id("org.jetbrains.kotlin.android") version "2.0.20" // Kotlin plugin for Android
+ id("org.jetbrains.kotlin.plugin.serialization") version "2.0.20" // Kotlin serialization plugin
+ id("org.jetbrains.kotlin.plugin.compose") version "2.0.20"
+ id("kotlin-kapt") // Asegúrate de que kapt esté habilitado
+ id("com.google.gms.google-services") // Plugin de Google Services
}
android {
@@ -30,19 +33,24 @@ android {
)
}
}
+
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
+
kotlinOptions {
jvmTarget = "1.8"
}
+
buildFeatures {
compose = true
}
+
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
+
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
@@ -51,8 +59,6 @@ android {
}
dependencies {
-
-
// Supabase dependencies
implementation(platform("io.github.jan-tennert.supabase:bom:3.0.0"))
implementation("io.github.jan-tennert.supabase:postgrest-kt")
@@ -65,20 +71,48 @@ dependencies {
// Compose dependencies
implementation(platform("androidx.compose:compose-bom:2024.09.03"))
- implementation("androidx.compose.ui:ui")
- implementation("androidx.compose.ui:ui-graphics")
- implementation("androidx.compose.ui:ui-tooling-preview")
- implementation("androidx.compose.material3:material3:1.3.0")
+ implementation("androidx.compose.material3:material3")
+ implementation("androidx.navigation:navigation-compose:2.8.2")
+ implementation("androidx.biometric:biometric:1.1.0")
+ implementation("androidx.fragment:fragment-ktx:1.8.4")
+ implementation("com.google.android.libraries.places:places:4.0.0")
+ implementation("androidx.appcompat:appcompat:1.7.0")
+ implementation("com.google.maps.android:maps-compose:2.2.0")
+ implementation("com.google.accompanist:accompanist-flowlayout:0.28.0")
+ implementation("androidx.compose.runtime:runtime-livedata:1.7.4")
+
+ // Google Play Services
+ implementation("com.google.android.gms:play-services-maps")
+ implementation("com.google.android.gms:play-services-measurement-api")
+ implementation("com.google.android.gms:play-services-measurement-sdk")
+
+ // Room and Kapt
+ implementation("androidx.room:room-runtime:2.5.1")
+ kapt("androidx.room:room-compiler:2.5.1")
+
+ // Glide
+ implementation("com.github.bumptech.glide:glide:4.12.0")
+ kapt("com.github.bumptech.glide:compiler:4.12.0")
+
+ // Coil
+ implementation("io.coil-kt:coil-compose:2.0.0")
// Testing dependencies
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
- androidTestImplementation(platform("androidx.compose:compose-bom:2024.09.03"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0")
+ testImplementation("io.mockk:mockk:1.13.3")
// Debug dependencies
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
-}
\ No newline at end of file
+ // Firebase dependencies (using BOM for version management)
+ implementation(platform("com.google.firebase:firebase-bom:32.1.0"))
+ implementation("com.google.firebase:firebase-analytics-ktx")
+ implementation("com.google.firebase:firebase-firestore-ktx")
+ implementation("com.google.firebase:firebase-storage-ktx")
+ implementation("com.google.firebase:firebase-messaging-ktx") // Notificaciones push
+}
diff --git a/app/google-services.json b/app/google-services.json
new file mode 100644
index 0000000..b0a18df
--- /dev/null
+++ b/app/google-services.json
@@ -0,0 +1,29 @@
+{
+ "project_info": {
+ "project_number": "631169455352",
+ "project_id": "ecobitesandroid",
+ "storage_bucket": "ecobitesandroid.firebasestorage.app"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:631169455352:android:d850824d325ecaea7bd70d",
+ "android_client_info": {
+ "package_name": "com.uniandes.ecobites"
+ }
+ },
+ "oauth_client": [],
+ "api_key": [
+ {
+ "current_key": "AIzaSyAKofhygTp6LyG_fZ2wLT0lpUlP-40ZH8A"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": []
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fb516a9..ab71e60 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,13 @@
+
+
+
+
+
+
+
+
+
selectedTab = tab })
- }
- ) { innerPadding ->
- Box(modifier = Modifier.padding(innerPadding)) {
- when (selectedTab) {
- 0 -> HomeScreen()
- 1 -> CartScreen()
- 3 -> ProfileScreen()
- }
- }
+ NavigationHost(navController = navController, biometricAuth = biometricAuth) // Pass the NavController here
}
}
-
diff --git a/app/src/main/java/com/uniandes/ecobites/MenuCategory.kt b/app/src/main/java/com/uniandes/ecobites/MenuCategory.kt
new file mode 100644
index 0000000..71645fa
--- /dev/null
+++ b/app/src/main/java/com/uniandes/ecobites/MenuCategory.kt
@@ -0,0 +1,8 @@
+package com.uniandes.ecobites
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class MenuCategory(
+ val menu: List
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/components/BiometricAuth.kt b/app/src/main/java/com/uniandes/ecobites/ui/components/BiometricAuth.kt
new file mode 100644
index 0000000..ad1f128
--- /dev/null
+++ b/app/src/main/java/com/uniandes/ecobites/ui/components/BiometricAuth.kt
@@ -0,0 +1,67 @@
+package com.uniandes.ecobites.ui.components
+
+import android.Manifest
+import android.app.Activity
+import android.content.Context
+import android.content.pm.PackageManager
+import android.hardware.fingerprint.FingerprintManager
+import android.os.CancellationSignal
+import androidx.core.app.ActivityCompat
+import androidx.navigation.NavController
+
+class BiometricAuth(private val context: Context) {
+
+ private var cancellationSignal: CancellationSignal? = null
+
+ // agregamos el navController para la navegación
+ private var navController: NavController? = null
+
+
+ private val authenticationCallback: FingerprintManager.AuthenticationCallback =
+ object : FingerprintManager.AuthenticationCallback() {
+ @Deprecated("Deprecated in Java")
+ override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
+ super.onAuthenticationSucceeded(result)
+ // Aquí manejo el éxito de la autenticación
+ navController?.navigate("home") {
+ popUpTo("login") { inclusive = true}
+ }
+ }
+
+ @Deprecated("Deprecated in Java")
+ override fun onAuthenticationFailed() {
+ super.onAuthenticationFailed()
+ // Aquí manejo la autenticación fallida
+ }
+ }
+
+ // Función para verificar si la autenticación por huella está soportada en el dispositivo
+ fun isFingerprintSupported(): Boolean {
+ val fingerprintManager = context.getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager
+
+ return if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
+ false
+ } else {
+ fingerprintManager.isHardwareDetected && fingerprintManager.hasEnrolledFingerprints()
+ }
+ }
+
+ // Función para iniciar la autenticación por huella y navegar a la pantalla de home si la navegación es exitosa
+ fun authenticate(navController: NavController) {
+ this.navController = navController // guardamos el navController para poder navegar después
+ val fingerprintManager = context.getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager
+
+ if (ActivityCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(
+ context as Activity,
+ arrayOf(Manifest.permission.USE_FINGERPRINT,Manifest.permission.USE_BIOMETRIC),
+ 101
+ )// Permisos de huella no están concedidos, manejo de caso
+ return
+ }
+
+ cancellationSignal = CancellationSignal()
+
+ fingerprintManager.authenticate(null, cancellationSignal, 0, authenticationCallback, null)
+ }
+}
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/components/CachedImage.kt b/app/src/main/java/com/uniandes/ecobites/ui/components/CachedImage.kt
new file mode 100644
index 0000000..e1fcdef
--- /dev/null
+++ b/app/src/main/java/com/uniandes/ecobites/ui/components/CachedImage.kt
@@ -0,0 +1,36 @@
+package com.uniandes.ecobites.ui.components
+
+
+import androidx.compose.foundation.Image
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import coil.compose.rememberAsyncImagePainter
+import coil.request.ImageRequest
+import coil.size.Scale
+import coil.transform.RoundedCornersTransformation
+import androidx.compose.ui.platform.LocalContext
+
+@Composable
+fun CachedImage(
+ imageUrl: String,
+ contentDescription: String?,
+ modifier: Modifier = Modifier,
+ cornerRadius: Float = 8f
+) {
+ val painter = rememberAsyncImagePainter(
+ model = ImageRequest.Builder(LocalContext.current)
+ .data(imageUrl)
+ .crossfade(true)
+ .transformations(RoundedCornersTransformation(cornerRadius))
+ .scale(Scale.FILL)
+ .build()
+ )
+
+ Image(
+ painter = painter,
+ contentDescription = contentDescription,
+ modifier = modifier,
+ contentScale = ContentScale.Crop
+ )
+}
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/components/NavBar.kt b/app/src/main/java/com/uniandes/ecobites/ui/components/NavBar.kt
index c72d5e9..d4ee394 100644
--- a/app/src/main/java/com/uniandes/ecobites/ui/components/NavBar.kt
+++ b/app/src/main/java/com/uniandes/ecobites/ui/components/NavBar.kt
@@ -6,34 +6,46 @@ import androidx.compose.material.icons.rounded.Home
import androidx.compose.material.icons.rounded.ShoppingCart
import androidx.compose.material.icons.rounded.List
import androidx.compose.material.icons.rounded.Person
+import androidx.compose.material.icons.rounded.LocationOn // Nuevo ícono de ubicación
import androidx.compose.runtime.Composable
+import androidx.navigation.NavController
+import androidx.navigation.compose.currentBackStackEntryAsState
@Composable
-fun NavBar(selectedTab: Int, onTabSelected: (Int) -> Unit) {
+fun NavBar(navController: NavController) {
+ val currentRoute = navController.currentBackStackEntryAsState()?.value?.destination?.route
+
NavigationBar {
NavigationBarItem(
icon = { Icon(Icons.Rounded.Home, contentDescription = "Home") },
label = { Text("Home") },
- selected = selectedTab == 0,
- onClick = { onTabSelected(0) }
+ selected = currentRoute == "home",
+ onClick = { navController.navigate("home") }
)
NavigationBarItem(
icon = { Icon(Icons.Rounded.ShoppingCart, contentDescription = "Cart") },
label = { Text("Cart") },
- selected = selectedTab == 1,
- onClick = { onTabSelected(1) }
+ selected = currentRoute == "cart",
+ onClick = { navController.navigate("cart") }
)
NavigationBarItem(
icon = { Icon(Icons.Rounded.List, contentDescription = "Orders") },
label = { Text("Orders") },
- selected = selectedTab == 2,
- onClick = { onTabSelected(2) }
+ selected = currentRoute == "orders",
+ onClick = { navController.navigate("orders") }
)
NavigationBarItem(
icon = { Icon(Icons.Rounded.Person, contentDescription = "Profile") },
label = { Text("Profile") },
- selected = selectedTab == 3,
- onClick = { onTabSelected(3) }
+ selected = currentRoute == "profile",
+ onClick = { navController.navigate("profile") }
+ )
+ // Nuevo ítem de Location
+ NavigationBarItem(
+ icon = { Icon(Icons.Rounded.LocationOn, contentDescription = "Location") }, // Ícono de ubicación
+ label = { Text("Location") },
+ selected = currentRoute == "location",
+ onClick = { navController.navigate("location") }
)
}
}
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/data/MenuDao.kt b/app/src/main/java/com/uniandes/ecobites/ui/data/MenuDao.kt
new file mode 100644
index 0000000..38b78be
--- /dev/null
+++ b/app/src/main/java/com/uniandes/ecobites/ui/data/MenuDao.kt
@@ -0,0 +1,19 @@
+package com.uniandes.ecobites.ui.data
+
+import androidx.lifecycle.LiveData
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.Query
+
+@Dao
+interface MenuDao {
+ @Query("SELECT * FROM MenuItem")
+ fun getAllMenuItems(): LiveData>
+
+ @Insert
+ fun saveMenuItem(menuItem: MenuItem)
+
+ @Delete
+ fun deleteMenuItem(menuItem: MenuItem)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/data/MenuDatabase.kt b/app/src/main/java/com/uniandes/ecobites/ui/data/MenuDatabase.kt
new file mode 100644
index 0000000..eca9ef7
--- /dev/null
+++ b/app/src/main/java/com/uniandes/ecobites/ui/data/MenuDatabase.kt
@@ -0,0 +1,9 @@
+package com.uniandes.ecobites.ui.data
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+
+@Database(entities = [MenuItem::class], version = 1)
+abstract class MenuDatabase : RoomDatabase() {
+ abstract fun menuDao(): MenuDao
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/data/MenuItem.kt b/app/src/main/java/com/uniandes/ecobites/ui/data/MenuItem.kt
new file mode 100644
index 0000000..24782af
--- /dev/null
+++ b/app/src/main/java/com/uniandes/ecobites/ui/data/MenuItem.kt
@@ -0,0 +1,11 @@
+package com.uniandes.ecobites.ui.data
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+data class MenuItem(
+ @PrimaryKey val id: String,
+ val name: String,
+ val price: Double
+)
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/data/SupabaseClient.kt b/app/src/main/java/com/uniandes/ecobites/ui/data/SupabaseClient.kt
new file mode 100644
index 0000000..699b389
--- /dev/null
+++ b/app/src/main/java/com/uniandes/ecobites/ui/data/SupabaseClient.kt
@@ -0,0 +1,213 @@
+package com.uniandes.ecobites.ui.data
+
+import android.util.Log
+import io.github.jan.supabase.auth.Auth
+import io.github.jan.supabase.auth.auth
+import io.github.jan.supabase.auth.providers.builtin.Email
+import io.github.jan.supabase.createSupabaseClient
+import io.github.jan.supabase.postgrest.Postgrest
+import io.github.jan.supabase.postgrest.from
+import io.github.jan.supabase.postgrest.query.Columns
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.buildJsonObject
+import kotlinx.serialization.json.put
+import io.github.jan.supabase.SupabaseClient
+import io.github.jan.supabase.SupabaseClientBuilder
+import io.github.jan.supabase.createSupabaseClient
+
+// Create Supabase Client
+val supabase = createSupabaseClient(
+ supabaseUrl = "https://nlhcaanwwchxdzdiyizf.supabase.co",
+ supabaseKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5saGNhYW53d2NoeGR6ZGl5aXpmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Mjc5MDc0OTQsImV4cCI6MjA0MzQ4MzQ5NH0.LrcRGkVH1qjPE09xDngX7wrtrUmfIYbTGrgbPKarTeM"
+) {
+ install(Postgrest)
+ install(Auth)
+}
+@Serializable
+data class Restaurant(
+ val id: Int,
+ val name: String,
+ val description: String?,
+ val address: String?,
+ val phone: String?
+)
+// Store model
+@Serializable
+data class Store(
+ val id: Int,
+ val name: String,
+ val description: String?,
+ val address: String?,
+ val phone: String?
+)
+
+// Fetch store details based on storeName
+suspend fun fetchStore(storeName: String): Store {
+ return withContext(Dispatchers.IO) {
+ supabase.from("stores").select() {
+ filter {
+ eq("name", storeName)
+ }
+ }.decodeSingle()
+ }
+}
+
+@Serializable
+data class Product(
+ val id: Int,
+ val store_id: Int,
+ val name: String,
+ val price: Int
+)
+
+// Fetch store products based on storeId
+suspend fun fetchStoreProducts(storeId: Int): List {
+ return withContext(Dispatchers.IO) {
+ supabase.from("products").select() {
+ filter {
+ eq("store_id", storeId)
+ }
+ }.decodeList()
+ }
+}
+
+// Concurrently fetch store and its products
+suspend fun fetchStoreAndProducts(storeName: String): Pair> = coroutineScope {
+ // Launch both fetches concurrently
+ val storeDeferred = async { fetchStore(storeName) }
+ val productsDeferred = async {
+ val store = storeDeferred.await() // Wait for store ID to fetch products
+ fetchStoreProducts(store.id)
+ }
+
+ // Await and return both results as a Pair
+ val store = storeDeferred.await()
+ val products = productsDeferred.await()
+
+ Pair(store, products)
+}
+
+// Function to handle user sign-in
+suspend fun signInWithEmail(email: String, password: String): Result {
+ return withContext(Dispatchers.IO) {
+ try {
+ supabase.auth.signInWith(Email) {
+ this.email = email
+ this.password = password
+ }
+ Result.success(Unit)
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+ }
+}
+
+suspend fun signUpWithEmailAndName(email: String, password: String, name: String): Result {
+ return withContext(Dispatchers.IO) {
+ try {
+ supabase.auth.signUpWith(Email) {
+ this.email = email
+ this.password = password
+ data = buildJsonObject {
+ put("display_name", name)
+ }
+ }
+ Result.success(Unit)
+ } catch (e: Exception) {
+ Result.failure(e)
+ }
+ }
+}
+
+@Serializable
+data class CartItem(
+ val id: Int? = null,
+ val user_id: String,
+ val product_id: Int,
+ val quantity: Int
+)
+
+suspend fun clearCart(userId: String) {
+ supabase.from("cart_items").delete {
+ filter { eq("user_id", userId) }
+ }
+}
+
+suspend fun addToCart(product: Product, quantity: Int, userId: String) {
+ val cartItem = CartItem(
+ user_id = userId,
+ product_id = product.id,
+ quantity = quantity
+ )
+ supabase.from("cart_items").insert(cartItem)
+}
+
+suspend fun updateCartItem(productId: Int, newQuantity: Int, userId: String) {
+ if (newQuantity > 0) {
+ supabase.from("cart_items").update(
+ mapOf("quantity" to newQuantity)
+ ) {
+ filter {
+ eq("product_id", productId)
+ eq("user_id", userId)
+ }
+ }
+ } else {
+ supabase.from("cart_items").delete {
+ filter {
+ eq("product_id", productId)
+ eq("user_id", userId)
+ }
+ }
+ }
+}
+
+suspend fun removeFromCart(productId: Int, userId: String) {
+ supabase.from("cart_items").delete {
+ filter {
+ eq("product_id", productId)
+ eq("user_id", userId)
+ }
+ }
+}
+
+
+// Creación del cliente Supabase sin autenticación para pruebas
+fun createSupabaseClientForTesting(): SupabaseClient {
+ return createSupabaseClient(
+ supabaseUrl = "https://your-supabase-url.supabase.co",
+ supabaseKey = "your-anon-key"
+ ) {
+ // Aquí podrías agregar otras configuraciones de prueba si son soportadas por la biblioteca
+ // Esto es un cliente básico, sin manejo de sesión.
+ }
+}
+@Serializable
+data class CartProduct(
+ val id: Int,
+ val quantity: Int,
+ val products: ProductInCart
+)
+
+@Serializable
+data class ProductInCart(
+ val id: Int,
+ val name: String,
+ val price: Int
+)
+
+suspend fun fetchCartItemsWithDetails(userId: String): List {
+ val rawResponse = supabase.from("cart_items")
+ .select(Columns.raw("id, quantity, products(id, name, price)")) {
+ filter { eq("user_id", userId) }
+ }
+
+ Log.d("SupabaseResponse", "Raw JSON Response: ${rawResponse.data}")
+
+ return rawResponse.decodeList()
+}
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/navigation/NavigationHost.kt b/app/src/main/java/com/uniandes/ecobites/ui/navigation/NavigationHost.kt
index 0d057e8..6c72cae 100644
--- a/app/src/main/java/com/uniandes/ecobites/ui/navigation/NavigationHost.kt
+++ b/app/src/main/java/com/uniandes/ecobites/ui/navigation/NavigationHost.kt
@@ -1,18 +1,233 @@
package com.uniandes.ecobites.ui.navigation
+import RestaurantMapScreen
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
-import com.uniandes.ecobites.ui.screens.ProfileScreen
-import com.uniandes.ecobites.ui.screens.CartScreen
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.room.Room
+import com.uniandes.ecobites.ui.components.BiometricAuth
+import com.uniandes.ecobites.ui.components.NavBar
+import com.uniandes.ecobites.ui.data.MenuDatabase
+import com.uniandes.ecobites.ui.data.supabase
+import com.uniandes.ecobites.ui.screens.*
import com.uniandes.ecobites.ui.screens.home.HomeScreen
-import com.uniandes.ecobites.ui.screens.OrdersScreen
+import com.uniandes.ecobites.ui.screens.store.StoreDetailsScreen
+import com.uniandes.ecobites.ui.screens.ImageCacheScreen // Importa la pantalla de caching
+import io.github.jan.supabase.auth.auth
+import android.widget.Toast
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import com.uniandes.ecobites.R
+import com.uniandes.ecobites.ui.screens.restaurants.HornitosScreen
@Composable
-fun NavigationHost(selectedTab: Int) {
- when (selectedTab) {
- 0 -> HomeScreen()
- 1 -> CartScreen()
- 2 -> OrdersScreen()
- 3 -> ProfileScreen()
- else -> HomeScreen()
+fun NavigationHost(navController: NavHostController, biometricAuth: BiometricAuth) {
+ NavHost(navController = navController, startDestination = "login") {
+
+ // Pantalla de login
+ composable("login") {
+ LoginScreen(
+ onLoginSuccess = {
+ navController.navigate("home") {
+ popUpTo("login") { inclusive = true }
+ }
+ },
+ navController = navController,
+ biometricAuth = biometricAuth
+ )
+ }
+
+ // Pantalla de registro
+ composable("signup") {
+ SignUpScreen(
+ onSignUpSuccess = {
+ navController.navigate("home") {
+ popUpTo("signup") { inclusive = true }
+ }
+ },
+ navController = navController
+ )
+ }
+ fun isNetworkAvailable(context: Context): Boolean {
+ val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val activeNetwork = connectivityManager.activeNetwork ?: return false
+ val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
+ return networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ }
+ // Pantalla principal
+ composable("home") {
+ val context = LocalContext.current
+
+ if (isNetworkAvailable(context)) {
+ Scaffold(
+ bottomBar = {
+ NavBar(navController = navController)
+ }
+ ) { innerPadding ->
+ Box(modifier = Modifier.padding(innerPadding)) {
+ HomeScreen(navController)
+ }
+ }
+ } else {
+
+ Scaffold(
+ bottomBar = {
+ NavBar(navController = navController)
+ }
+ ) { innerPadding ->
+ Box(modifier = Modifier.padding(innerPadding)) {
+ HomeScreen(navController)
+ }
+ }
+ }
+ }
+
+
+
+
+ // Pantalla de carrito
+ composable("cart") {
+ val context = LocalContext.current
+ val user = supabase.auth.currentUserOrNull()
+ val userId = user?.id
+
+ if (isNetworkAvailable(context)) {
+ Scaffold(
+ bottomBar = {
+ NavBar(navController = navController)
+ }
+ ) { innerPadding ->
+ Box(modifier = Modifier.padding(innerPadding)) {
+ if (userId != null) {
+ CartScreen(userId = userId)
+ }
+ }
+ }
+ } else {
+ Scaffold(
+ bottomBar = {
+ NavBar(navController = navController)
+ }
+ ) { innerPadding ->
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding),
+ contentAlignment = Alignment.Center
+ ) {
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Image(
+ painter = painterResource(id = R.drawable.carticon),
+ contentDescription = "Descripción de la imagen",
+ modifier = Modifier.size(500.dp)
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Toast.makeText(context, "Sin conexión, intente más tarde", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ }
+ }
+
+ composable("orders") {
+ Scaffold(
+ bottomBar = {
+ NavBar(navController = navController)
+ }
+ ) { innerPadding ->
+ Box(modifier = Modifier.padding(innerPadding)) {
+ OrdersScreen(navController)
+ }
+ }
+ }
+
+ // Pantalla de perfil con botón de caching
+ composable("profile") {
+ Scaffold(
+ bottomBar = {
+ NavBar(navController = navController)
+ }
+ ) { innerPadding ->
+ Box(modifier = Modifier.padding(innerPadding)) {
+ ProfileScreen(navController = navController)
+ }
+ }
+ }
+ composable("storage") {
+ val menuDatabase = Room.databaseBuilder(
+ LocalContext.current,
+ MenuDatabase::class.java,
+ "menu.db"
+ ).build()
+ Scaffold(
+ bottomBar = {
+ NavBar(navController = navController)
+ }
+ ) { innerPadding ->
+ Box(modifier = Modifier.padding(innerPadding)) {
+ StorageScreen(menuDatabase = menuDatabase)
+ }
+ }
+ }
+ composable("store/{storeName}") { backStackEntry ->
+ val storeName = backStackEntry.arguments?.getString("storeName")
+ Scaffold(
+ bottomBar = {
+ NavBar(navController = navController)
+ }
+ ) { innerPadding ->
+ Box(modifier = Modifier.padding(innerPadding)) {
+ val user = supabase.auth.currentUserOrNull()
+ val userId = user?.id
+ StoreDetailsScreen(storeName ?: "", userId = userId!!)
+ }
+ }
+ }
+
+ composable("location") {
+ Scaffold(
+ bottomBar = {
+ NavBar(navController = navController)
+ }
+ ) { innerPadding ->
+ Box(modifier = Modifier.padding(innerPadding)) {
+ RestaurantMapScreen()
+ }
+ }
+ }
+
+ composable("hornitos") {
+ val context = LocalContext.current
+ HornitosScreen(context = context)
+ }
+
+ // Pantalla de caching
+ composable("caching") {
+ Scaffold(
+ bottomBar = {
+ NavBar(navController = navController)
+ }
+ ) { innerPadding ->
+ Box(modifier = Modifier.padding(innerPadding)) {
+ ImageCacheScreen()
+ }
+ }
+ }
}
}
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/screens/CartScreen.kt b/app/src/main/java/com/uniandes/ecobites/ui/screens/CartScreen.kt
index decff1f..3310132 100644
--- a/app/src/main/java/com/uniandes/ecobites/ui/screens/CartScreen.kt
+++ b/app/src/main/java/com/uniandes/ecobites/ui/screens/CartScreen.kt
@@ -1,147 +1,201 @@
package com.uniandes.ecobites.ui.screens
import androidx.compose.foundation.Image
-import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.AccountCircle
-import androidx.compose.material.icons.filled.Home
-import androidx.compose.material.icons.filled.ShoppingCart
+import androidx.compose.material.icons.rounded.KeyboardArrowLeft
+import androidx.compose.material.icons.rounded.KeyboardArrowRight
import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
+import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import com.uniandes.ecobites.R // Reemplazar con tu propio recurso de íconos
-
+import com.uniandes.ecobites.R
+import com.uniandes.ecobites.ui.data.CartProduct
+import com.uniandes.ecobites.ui.data.clearCart
+import com.uniandes.ecobites.ui.data.fetchCartItemsWithDetails
+import com.uniandes.ecobites.ui.data.removeFromCart
+import com.uniandes.ecobites.ui.data.updateCartItem
+import kotlinx.coroutines.launch
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun CartScreen() {
- // Aquí puedes replicar la vista que tienes en la imagen del carrito
- Column(
- modifier = Modifier
- .fillMaxSize()
- .padding(16.dp)
- ) {
- // Ejemplo de un título para la pantalla
- Text(text = "Your Cart", style = MaterialTheme.typography.titleLarge)
+fun CartScreen(userId: String) {
+ val scope = rememberCoroutineScope()
+ var cartItems by remember { mutableStateOf>(emptyList()) }
+ var isLoading by remember { mutableStateOf(true) }
- Spacer(modifier = Modifier.height(16.dp))
+ // Fetch cart items when the screen loads
+ LaunchedEffect(userId) {
+ scope.launch {
+ isLoading = true
+ cartItems = fetchCartItemsWithDetails(userId)
+ isLoading = false
+ }
+ }
- // Ejemplo de un listado de productos
- // Aquí deberías poner tu implementación que se refleje como la que has mostrado
- Text(text = "Pineapple Pizza - $20,400 COP")
- Text(text = "Donuts - $22,800 COP")
- Text(text = "Cheeseburger - $14,600 COP")
+ // Clear cart functionality
+ fun handleClearCart() {
+ scope.launch {
+ clearCart(userId)
+ cartItems = emptyList() // Clear the local state as well
+ }
+ }
- Spacer(modifier = Modifier.height(16.dp))
+ // Function to update quantity
+ fun updateQuantity(cartItemId: Int, newQuantity: Int, productId: Int) {
+ scope.launch {
+ updateCartItem(productId, newQuantity, userId) // Update in the database
- // Ejemplo de botón para checkout
- Button(
- onClick = { /* Acción del botón de checkout */ },
- modifier = Modifier.fillMaxWidth()
- ) {
- Text(text = "Checkout")
+ if (newQuantity > 0) {
+ cartItems = cartItems.map {
+ if (it.id == cartItemId) it.copy(quantity = newQuantity) else it
+ }
+ } else {
+ // Remove the item if quantity goes to zero
+ removeFromCart(cartItemId,userId)
+ // Update the local state to remove the item
+ cartItems = cartItems.filterNot { it.id == cartItemId }
+ }
+ }
+ }
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text("Cart") },
+ navigationIcon = {
+ IconButton(onClick = { /* Handle back action */ }) {
+ Icon(imageVector = Icons.Rounded.KeyboardArrowLeft, contentDescription = "Back")
+ }
+ }
+ )
+ },
+ bottomBar = {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ OutlinedButton(
+ onClick = { handleClearCart() },
+ colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.Red)
+ ) {
+ Text(text = "Clear", color = Color.Red)
+ }
+ Column(
+ horizontalAlignment = Alignment.End
+ ) {
+ Text(
+ text = "Subtotal",
+ style = MaterialTheme.typography.bodySmall,
+ color = Color.Gray
+ )
+ Text(
+ text = calculateSubtotal(cartItems) + " COP",
+ style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold)
+ )
+ }
+ Button(
+ onClick = { /* Handle checkout */ },
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color(0xFF907B00) // Adjust color as needed
+ )
+ ) {
+ Text(text = "Checkout")
+ }
+ }
+ }
+ ) { innerPadding ->
+ if (isLoading) {
+ CircularProgressIndicator(modifier = Modifier.padding(16.dp))
+ } else if (cartItems.isEmpty()) {
+ Text(
+ text = "Your cart is empty",
+ style = MaterialTheme.typography.bodyLarge,
+ modifier = Modifier.padding(16.dp)
+ )
+ } else {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding),
+ contentPadding = PaddingValues(16.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ items(cartItems) { cartItem ->
+ CartItemCard(
+ product = cartItem,
+ onIncreaseQuantity = { updateQuantity(cartItem.id, cartItem.quantity + 1,cartItem.products.id) },
+ onDecreaseQuantity = { updateQuantity(cartItem.id, cartItem.quantity - 1,cartItem.products.id) }
+ )
+ }
+ }
}
}
}
@Composable
-fun CartItemCard(item: CartItem) {
- Card(
- modifier = Modifier.fillMaxWidth(),
- shape = RoundedCornerShape(8.dp)
+fun CartItemCard(
+ product: CartProduct,
+ onIncreaseQuantity: () -> Unit,
+ onDecreaseQuantity: () -> Unit
+) {
+ ElevatedCard(
+ modifier = Modifier.fillMaxWidth()
) {
Row(
modifier = Modifier
- .padding(16.dp)
- .fillMaxWidth(),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.SpaceBetween
+ .fillMaxWidth()
+ .padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically
) {
- Row(
- verticalAlignment = Alignment.CenterVertically
+ Image(
+ painter = painterResource(R.drawable.def), // Replace with product image if available
+ contentDescription = "Product Image",
+ modifier = Modifier.size(100.dp).clip(MaterialTheme.shapes.medium)
+ )
+ Spacer(modifier = Modifier.width(16.dp))
+ Column(
+ modifier = Modifier.weight(1f)
) {
- // Aquí puedes reemplazar la imagen con una imagen de recurso
- Image(
- painter = painterResource(id = R.drawable.placeholder), // Reemplazar con imagen real
- contentDescription = item.name,
- modifier = Modifier
- .size(64.dp)
- .background(Color.LightGray, CircleShape),
- contentScale = ContentScale.Crop
+ Text(
+ text = product.products.name,
+ style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold)
+ )
+ Text(
+ text = product.products.price.toString() + " COP",
+ style = MaterialTheme.typography.bodySmall.copy(color = Color.Gray),
+ modifier = Modifier.padding(vertical = 4.dp)
)
- Spacer(modifier = Modifier.width(16.dp))
- Column {
- Text(text = item.name, fontSize = 16.sp, fontWeight = FontWeight.Bold)
- Text(text = item.price, fontSize = 14.sp, color = Color.Gray)
- }
- }
- // Counter for quantity
+ }
Row(
verticalAlignment = Alignment.CenterVertically
) {
- IconButton(onClick = { /* TODO: Decrease quantity */ }) {
- Text(text = "-", fontSize = 20.sp, fontWeight = FontWeight.Bold)
+ IconButton(onClick = onDecreaseQuantity) {
+ Icon(Icons.Rounded.KeyboardArrowLeft, contentDescription = "Decrease quantity")
}
- Text(
- text = item.quantity.toString(),
- modifier = Modifier.padding(horizontal = 8.dp),
- fontSize = 16.sp,
- textAlign = TextAlign.Center
- )
- IconButton(onClick = { /* TODO: Increase quantity */ }) {
- Text(text = "+", fontSize = 20.sp, fontWeight = FontWeight.Bold)
+ Text(text = product.quantity.toString(), modifier = Modifier.padding(horizontal = 8.dp))
+ IconButton(onClick = onIncreaseQuantity) {
+ Icon(Icons.Rounded.KeyboardArrowRight, contentDescription = "Increase quantity")
}
}
}
}
}
-@Composable
-fun BottomNavigationBar() {
- NavigationBar {
- NavigationBarItem(
- icon = { Icon(imageVector = Icons.Default.Home, contentDescription = "Home") },
- label = { Text("Home") },
- selected = true, // Cambiar esto según la lógica de la navegación
- onClick = { /* TODO: Navegar a Home */ }
- )
- NavigationBarItem(
- icon = { Icon(imageVector = Icons.Default.ShoppingCart, contentDescription = "Cart") },
- label = { Text("Cart") },
- selected = false, // Cambiar esto según la lógica de la navegación
- onClick = { /* TODO: Navegar al carrito */ }
- )
- NavigationBarItem(
- icon = { Icon(imageVector = Icons.Default.AccountCircle, contentDescription = "Profile") },
- label = { Text("Profile") },
- selected = false, // Cambiar esto según la lógica de la navegación
- onClick = { /* TODO: Navegar al perfil */ }
- )
- }
-}
-
-// Modelo para representar un item del carrito
-data class CartItem(
- val name: String,
- val price: String,
- val quantity: Int
-)
-
-// Datos de prueba
-val cartItems = listOf(
- CartItem("Pineapple Pizza", "20,400 COP", 1),
- CartItem("Donuts", "22,800 COP", 6),
- CartItem("Cheeseburger", "14,600 COP", 1)
-)
+// This can stay here or be moved to a separate file for utility functions
+fun calculateSubtotal(cartItems: List): String {
+ val subtotal = cartItems.sumOf { ( it.products.price) * it.quantity }
+ return subtotal.toString() // Format as currency without decimals
+}
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/screens/ImageCacheScreen.kt b/app/src/main/java/com/uniandes/ecobites/ui/screens/ImageCacheScreen.kt
new file mode 100644
index 0000000..f012a2b
--- /dev/null
+++ b/app/src/main/java/com/uniandes/ecobites/ui/screens/ImageCacheScreen.kt
@@ -0,0 +1,49 @@
+package com.uniandes.ecobites.ui.screens
+
+
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.uniandes.ecobites.ui.components.CachedImage
+
+// Datos de ejemplo: URLs de imágenes
+val imageUrls = listOf(
+ "https://images.unsplash.com/photo-1553621042-f6e147245754",
+ "https://images.unsplash.com/photo-1600891964599-f61ba0e24092",
+ "https://images.unsplash.com/photo-1498654896293-37aacf113fd9",
+ "https://images.unsplash.com/photo-1565299624946-b28f40a0ae38",
+ "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4"
+)
+
+@Composable
+fun ImageCacheScreen() {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+ Text(
+ text = "Imágenes cargadas en caché",
+ style = MaterialTheme.typography.headlineMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+
+ LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ items(imageUrls) { imageUrl ->
+ CachedImage(
+ imageUrl = imageUrl,
+ contentDescription = "Imagen en caché",
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(200.dp)
+ )
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/screens/LoginScreen.kt b/app/src/main/java/com/uniandes/ecobites/ui/screens/LoginScreen.kt
index 72673af..29c1b2c 100644
--- a/app/src/main/java/com/uniandes/ecobites/ui/screens/LoginScreen.kt
+++ b/app/src/main/java/com/uniandes/ecobites/ui/screens/LoginScreen.kt
@@ -1,110 +1,141 @@
package com.uniandes.ecobites.ui.screens
-import android.os.Bundle
import android.widget.Toast
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.compose.ui.tooling.preview.Preview
-import io.github.jan.supabase.createSupabaseClient
+import androidx.navigation.NavController
+import com.uniandes.ecobites.R
+import com.uniandes.ecobites.ui.data.signInWithEmail
+import com.uniandes.ecobites.ui.components.BiometricAuth
import kotlinx.coroutines.launch
-import io.github.jan.supabase.auth.Auth
-import io.github.jan.supabase.auth.auth
-import io.github.jan.supabase.auth.providers.builtin.Email
-
-// Initialize Supabase
-val supabase = createSupabaseClient(
- supabaseUrl = "https://nlhcaanwwchxdzdiyizf.supabase.co", // Replace with your actual Supabase URL
- supabaseKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5saGNhYW53d2NoeGR6ZGl5aXpmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Mjc5MDc0OTQsImV4cCI6MjA0MzQ4MzQ5NH0.LrcRGkVH1qjPE09xDngX7wrtrUmfIYbTGrgbPKarTeM" // Replace with your actual Supabase anon key
-){
- install(Auth)
-}
-val auth = supabase.auth
@Composable
-fun LoginScreen(onLoginSuccess: () -> Unit) {
+fun LoginScreen(onLoginSuccess: () -> Unit, navController: NavController, biometricAuth: BiometricAuth) {
val context = LocalContext.current
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
- val coroutineScope = rememberCoroutineScope() // For launching coroutines
+ val coroutineScope = rememberCoroutineScope()
+
+ // Verificar si la autenticación biométrica está soportada
+ val isFingerprintSupported = biometricAuth.isFingerprintSupported()
- Column(
+ LaunchedEffect(Unit){
+
+ }
+ Box(
modifier = Modifier
.fillMaxSize()
- .padding(16.dp),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
+ .background(color = MaterialTheme.colorScheme.surfaceContainer)
) {
- // Title
- Text(text = "Log in", fontSize = 24.sp)
- Spacer(modifier = Modifier.height(24.dp))
+ // Log in text at the top
+ Text(
+ text = "Log in",
+ fontSize = 24.sp,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier
+ .align(Alignment.TopCenter)
+ .padding(top = 28.dp)
+ )
- // App name
- Text(text = "eco", fontSize = 48.sp, color = androidx.compose.ui.graphics.Color(0xFF4A6A2B))
- Text(text = "bites", fontSize = 48.sp, color = androidx.compose.ui.graphics.Color(0xFF4A6A2B))
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.icon),
+ contentDescription = "Eco Bites Logo",
+ contentScale = ContentScale.Fit,
+ modifier = Modifier
+ .width(350.dp)
+ .height(350.dp)
+ )
- Spacer(modifier = Modifier.height(24.dp))
+ // Email Input Field
+ OutlinedTextField(
+ value = email,
+ onValueChange = {
+ if(it.length<=30) { // Limitar a 30 caracteres
+ email = it
+ }
+ },
+ label = { Text("Email") },
+ modifier = Modifier.fillMaxWidth()
+ )
- // Email Input Field
- OutlinedTextField(
- value = email,
- onValueChange = { email = it },
- label = { Text("Email") },
- modifier = Modifier.fillMaxWidth()
- )
+ Spacer(modifier = Modifier.height(8.dp))
- // Password Input Field
- OutlinedTextField(
- value = password,
- onValueChange = { password = it },
- label = { Text("Password") },
- modifier = Modifier.fillMaxWidth(),
- visualTransformation = PasswordVisualTransformation()
- )
+ // Password Input Field
+ OutlinedTextField(
+ value = password,
+ onValueChange = { password = it },
+ label = { Text("Password") },
+ modifier = Modifier.fillMaxWidth(),
+ visualTransformation = PasswordVisualTransformation()
+ )
- Spacer(modifier = Modifier.height(16.dp))
+ Spacer(modifier = Modifier.height(16.dp))
- // Sign-In Button
- Button(
- onClick = {
- coroutineScope.launch {
- try {
- auth.signInWith(Email) {
- this.email = email
- this.password = password
+ // Sign-In Button
+ Button(
+ onClick = {
+ coroutineScope.launch {
+ val result = signInWithEmail(email, password)
+ result.onSuccess {
+ Toast.makeText(context, "Signed in successfully!", Toast.LENGTH_LONG).show()
+ onLoginSuccess()
+ }.onFailure { e ->
+ Toast.makeText(context, "Sign in failed: ${e.message}", Toast.LENGTH_LONG).show()
}
- Toast.makeText(context, "Signed in successfully!", Toast.LENGTH_LONG).show()
- onLoginSuccess() // Call the callback to update the login state
- } catch (e: Exception) {
- Toast.makeText(context, "Sign in failed: ${e.message}", Toast.LENGTH_LONG).show()
}
+ },
+ modifier = Modifier
+ .width(200.dp)
+ .height(48.dp)
+ ) {
+ Text("Sign in")
+ }
+
+ Spacer(modifier = Modifier.height(10.dp))
+
+ // Autenticación por huella si está soportada
+ if (isFingerprintSupported) {
+ Button(
+ onClick = {
+ biometricAuth.authenticate(navController) // Inicia el proceso de autenticación biométrica
+ },
+ modifier = Modifier
+ .width(200.dp)
+ .height(48.dp)
+ ) {
+ Text("Iniciar sesión con huella")
}
- },
- modifier = Modifier.fillMaxWidth()
- ) {
- Text("Sign in")
- }
+ }
- Spacer(modifier = Modifier.height(10.dp))
+ Spacer(modifier = Modifier.height(10.dp))
- // Sign-Up Button
- Button(
- onClick = {
- // Handle sign-up logic (optional)
- Toast.makeText(context, "Sign Up clicked", Toast.LENGTH_SHORT).show()
- },
- modifier = Modifier.fillMaxWidth()
- ) {
- Text("Sign Up")
+ // Sign-Up Button
+ TextButton(
+ onClick = {
+ navController.navigate("signup") // Navigate to Sign-Up Screen
+ }
+ ) {
+ Text("Don't have an account? Sign Up")
+ }
}
}
}
-
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/screens/OrdersScreen.kt b/app/src/main/java/com/uniandes/ecobites/ui/screens/OrdersScreen.kt
index 2cb372d..c50b035 100644
--- a/app/src/main/java/com/uniandes/ecobites/ui/screens/OrdersScreen.kt
+++ b/app/src/main/java/com/uniandes/ecobites/ui/screens/OrdersScreen.kt
@@ -1,9 +1,155 @@
package com.uniandes.ecobites.ui.screens
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.KeyboardArrowLeft
+import androidx.compose.material.icons.rounded.KeyboardArrowRight
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ElevatedCard
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import com.uniandes.ecobites.R
+import androidx.navigation.NavController
+
+
+// Sample data structure for orders
+data class Order(
+ val storeName: String,
+ val deliveryDate: String,
+ val storeLogo: Int // Resource ID of the store logo
+)
+
+
+val pastOrders = listOf(
+ Order("Pan Pa Ya!", "10/09", R.drawable.pan_pa_ya),
+ Order("McDonalds", "10/09", R.drawable.mc_donalds),
+ Order("Exito", "10/09", R.drawable.exito),
+ Order("Hornitos", "10/09", R.drawable.hornitos))
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun OrdersScreen(navController: NavController) {
+ // Mockup data
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text("Orders") },
+ navigationIcon = {
+ IconButton(onClick = { /* Handle back action */ }) {
+ Icon(imageVector = Icons.Rounded.KeyboardArrowLeft, contentDescription = "Back")
+ }
+ }
+ )
+ },
+ bottomBar = {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = "Keep saving money!",
+ style = MaterialTheme.typography.bodySmall,
+ color = Color.Gray
+ )
+ Button(
+ onClick = { navController.navigate("storage") },
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color(0xFF907B00) // Adjust the color as needed
+ )
+ ) {
+ Text(text = "local storage")
+ }
+ }
+ }
+ ) { innerPadding ->
+ LazyColumn(
+ contentPadding = PaddingValues(16.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding)
+ ) {
+ items(pastOrders) { order ->
+ OrderItemCard(order)
+ }
+ }
+ }
+}
@Composable
-fun OrdersScreen() {
- Text(text = "Order Screen")
+fun OrderItemCard(order: Order) {
+ ElevatedCard(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ // Display Store Logo
+ Image(
+ contentScale = ContentScale.Crop,
+ painter = painterResource(order.storeLogo),
+ contentDescription = "Store Logo",
+ modifier = Modifier.size(100.dp).fillMaxSize().clip(MaterialTheme.shapes.medium)
+ )
+ Spacer(modifier = Modifier.width(16.dp))
+
+ Column(
+ modifier = Modifier.weight(1f)
+ ) {
+ Text(
+ text = order.storeName,
+ style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold)
+ )
+ Text(
+ text = "Delivered ${order.deliveryDate}",
+ style = MaterialTheme.typography.bodySmall.copy(color = Color.Gray),
+ modifier = Modifier.padding(vertical = 4.dp)
+ )
+ }
+
+ IconButton(onClick = { /* Handle view details action */ }) {
+ Icon(imageVector = Icons.Rounded.KeyboardArrowRight, contentDescription = "View Details")
+ }
+ }
+ }
+
}
+
+
+
+
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/screens/ProfileScreen.kt b/app/src/main/java/com/uniandes/ecobites/ui/screens/ProfileScreen.kt
index 38ae7e5..9c8f5f3 100644
--- a/app/src/main/java/com/uniandes/ecobites/ui/screens/ProfileScreen.kt
+++ b/app/src/main/java/com/uniandes/ecobites/ui/screens/ProfileScreen.kt
@@ -3,50 +3,51 @@ package com.uniandes.ecobites.ui.screens
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.DateRange
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import androidx.compose.ui.tooling.preview.Preview
+import androidx.navigation.NavController
import com.uniandes.ecobites.R
@Composable
-fun ProfileScreen() {
+fun ProfileScreen(navController: NavController) {
var name by remember { mutableStateOf("Pepito Andrés") }
var surname by remember { mutableStateOf("Rincon Arismendy") }
var citizenId by remember { mutableStateOf("1005634120") }
var email by remember { mutableStateOf("p.rincon@uniandes.edu.co") }
- var phone by remember { mutableStateOf("p.rincon@uniandes.edu.co") }
- var birthdate by remember { mutableStateOf("08/17/2023") }
+ var phone by remember { mutableStateOf("1234567890") }
Column(
modifier = Modifier
.fillMaxSize()
- .padding(16.dp),
+ .padding(16.dp)
+ .verticalScroll(rememberScrollState()), // Habilitar scroll vertical
horizontalAlignment = Alignment.CenterHorizontally
) {
- // Greeting Text
- Text(text = "¡Hi, Pepito!", fontSize = 28.sp, color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.titleLarge)
+ Text(
+ text = "¡Hi, Pepito!",
+ fontSize = 28.sp,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.titleLarge
+ )
Text(
text = "You can edit your personal information here.",
fontSize = 16.sp,
- color = Color.Gray,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(bottom = 16.dp)
)
- // Profile Image
+ // Imagen de perfil
Image(
- painter = painterResource(id = R.drawable.profile_image), // Replace with actual image
+ painter = painterResource(id = R.drawable.profile_image),
contentDescription = "Profile Image",
modifier = Modifier
.size(120.dp)
@@ -56,30 +57,21 @@ fun ProfileScreen() {
Spacer(modifier = Modifier.height(32.dp))
- // Text Fields for Profile Information
ProfileTextField(label = "Name", value = name, onValueChange = { name = it })
ProfileTextField(label = "Surname", value = surname, onValueChange = { surname = it })
ProfileTextField(label = "Citizen ID", value = citizenId, onValueChange = { citizenId = it })
ProfileTextField(label = "Email", value = email, onValueChange = { email = it })
ProfileTextField(label = "Phone", value = phone, onValueChange = { phone = it })
- // Birthdate field with icon
- TextField(
- value = birthdate,
- onValueChange = { birthdate = it },
- label = { Text("Birthdate") },
- trailingIcon = {
- Icon(
- imageVector = Icons.Outlined.DateRange,
- contentDescription = "Calendar Icon"
- )
- },
- singleLine = true,
- modifier = Modifier
- .fillMaxWidth()
- .padding(vertical = 8.dp),
- placeholder = { Text("MM/DD/YYYY") }
- )
+ Spacer(modifier = Modifier.height(16.dp))
+
+ // Botón Caching
+ Button(
+ onClick = { navController.navigate("caching") },
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text("Caching")
+ }
}
}
@@ -92,13 +84,6 @@ fun ProfileTextField(label: String, value: String, onValueChange: (String) -> Un
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp), // Adds space between text fields
- singleLine = true,
-
+ singleLine = true
)
}
-
-@Preview(showBackground = true)
-@Composable
-fun PreviewProfileScreen() {
- ProfileScreen()
-}
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/screens/SignUpScreen.kt b/app/src/main/java/com/uniandes/ecobites/ui/screens/SignUpScreen.kt
new file mode 100644
index 0000000..37982b4
--- /dev/null
+++ b/app/src/main/java/com/uniandes/ecobites/ui/screens/SignUpScreen.kt
@@ -0,0 +1,127 @@
+package com.uniandes.ecobites.ui.screens
+
+import android.widget.Toast
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.navigation.NavController
+import com.uniandes.ecobites.R
+import com.uniandes.ecobites.ui.data.signUpWithEmailAndName // Import the sign-up function
+import kotlinx.coroutines.launch
+
+@Composable
+fun SignUpScreen(onSignUpSuccess: () -> Unit,navController: NavController) {
+ val context = LocalContext.current
+ var name by remember { mutableStateOf("") } // Name input state
+ var email by remember { mutableStateOf("") }
+ var password by remember { mutableStateOf("") }
+ val coroutineScope = rememberCoroutineScope()
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(color = MaterialTheme.colorScheme.surfaceContainer)
+ ) {
+ // Sign-Up text at the top
+ Text(
+ text = "Sign Up",
+ fontSize = 24.sp,
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier
+ .align(Alignment.TopCenter)
+ .padding(top = 28.dp)
+ )
+
+ // Content in the center
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.icon),
+ contentDescription = "Eco Bites Logo",
+ contentScale = ContentScale.Fit,
+ modifier = Modifier
+ .width(350.dp)
+ .height(350.dp)
+ )
+
+ // Name Input Field
+ OutlinedTextField(
+ value = name,
+ onValueChange = { name = it },
+ label = { Text("Name") },
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ // Email Input Field
+ OutlinedTextField(
+ value = email,
+ onValueChange = { email = it },
+ label = { Text("Email") },
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ // Password Input Field
+ OutlinedTextField(
+ value = password,
+ onValueChange = { password = it },
+ label = { Text("Password") },
+ modifier = Modifier.fillMaxWidth(),
+ visualTransformation = PasswordVisualTransformation()
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ // Sign-Up Button
+ Button(
+ onClick = {
+ coroutineScope.launch {
+ val result = signUpWithEmailAndName(email, password, name) // Call the sign-up function
+ result.onSuccess {
+ Toast.makeText(context, "Signed up successfully!", Toast.LENGTH_LONG).show()
+ onSignUpSuccess() // Trigger the success callback
+ }.onFailure { e ->
+ Toast.makeText(context, "Sign up failed: ${e.message}", Toast.LENGTH_LONG).show()
+ }
+ }
+ },
+ modifier = Modifier
+ .width(200.dp)
+ .height(48.dp)
+ ) {
+ Text("Sign Up")
+ }
+
+ Spacer(modifier = Modifier.height(10.dp))
+
+ // Navigate to Login Button (optional)
+ TextButton(
+ onClick = {
+ navController.navigate("login")
+ }
+ ) {
+ Text("Already have an account? Log In")
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/uniandes/ecobites/ui/screens/StorageScreen.kt b/app/src/main/java/com/uniandes/ecobites/ui/screens/StorageScreen.kt
new file mode 100644
index 0000000..75ca0de
--- /dev/null
+++ b/app/src/main/java/com/uniandes/ecobites/ui/screens/StorageScreen.kt
@@ -0,0 +1,160 @@
+package com.uniandes.ecobites.ui.screens
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.uniandes.ecobites.ui.data.MenuDao
+import com.uniandes.ecobites.ui.data.MenuDatabase
+import com.uniandes.ecobites.ui.data.MenuItem
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.util.*
+import com.uniandes.ecobites.R
+import kotlinx.coroutines.withContext
+
+// ViewModel que utiliza LiveData en lugar de Flow
+class MenuViewModel(database: MenuDatabase) : ViewModel() {
+ private val menuDao: MenuDao = database.menuDao()
+
+ // LiveData para observar los elementos del menú
+ private val _menuItems = MediatorLiveData>()
+ val menuItems: LiveData> = _menuItems
+
+ init {
+ loadMenuItems()
+ }
+
+ private fun loadMenuItems() {
+ // Configuración de MediatorLiveData para observar cambios en el hilo principal
+ viewModelScope.launch {
+ withContext(Dispatchers.Main) {
+ _menuItems.addSource(menuDao.getAllMenuItems()) { items ->
+ _menuItems.postValue(items) // Asegura la actualización en el hilo principal
+ }
+ }
+ }
+ }
+
+ fun addMenuItem(dishName: String, price: Double) {
+ val newMenuItem = MenuItem(id = UUID.randomUUID().toString(), name = dishName, price = price)
+ viewModelScope.launch(Dispatchers.IO) {
+ menuDao.saveMenuItem(newMenuItem)
+ loadMenuItems() // Recargar los datos después de añadir un elemento
+ }
+ }
+
+ fun deleteMenuItem(menuItem: MenuItem) {
+ viewModelScope.launch(Dispatchers.IO) {
+ menuDao.deleteMenuItem(menuItem)
+ loadMenuItems() // Recargar los datos después de eliminar un elemento
+ }
+ }
+}
+
+// Pantalla de Storage que utiliza el MenuViewModel con LiveData
+@Composable
+fun StorageScreen(menuDatabase: MenuDatabase, menuViewModel: MenuViewModel = viewModel(factory = MenuViewModelFactory(menuDatabase))) {
+ var dishName by remember { mutableStateOf("") }
+ var priceInput by remember { mutableStateOf("") }
+ val menuItems by menuViewModel.menuItems.observeAsState(emptyList())
+
+ Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
+ Row(modifier = Modifier.padding(16.dp)) {
+ TextField(
+ modifier = Modifier.weight(0.6f),
+ value = dishName,
+ onValueChange = { dishName = it },
+ label = { Text("Dish name") }
+ )
+ Spacer(modifier = Modifier.width(16.dp))
+ TextField(
+ modifier = Modifier.weight(0.4f),
+ value = priceInput,
+ onValueChange = { priceInput = it },
+ label = { Text("Price") }
+ )
+ }
+
+ Button(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
+ onClick = {
+ val price = priceInput.toDoubleOrNull()
+ if (price != null) {
+ menuViewModel.addMenuItem(dishName, price)
+ dishName = ""
+ priceInput = ""
+ }
+ }
+ ) {
+ Text("Add dish")
+ }
+
+ ItemsList(menuItems, menuViewModel)
+ }
+}
+
+// Composable para mostrar la lista de elementos del menú
+@Composable
+fun ItemsList(menuItems: List