Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ dependencies {
implementation(libs.firebase.storage)
implementation(libs.firebase.firestore)
implementation(libs.firebase.perf)
implementation(libs.play.services.location)
implementation(libs.compose.material.icons.core)

//Library to handle Markdown in Compose
implementation(libs.richtext.commonmark)
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<application
android:allowBackup="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,12 @@ class MainActivity : ComponentActivity() {
)
}
composable<GroceryListRoute> {
GroceryListScreen()
GroceryListScreen(
showError = {
val message = this@MainActivity.getString(R.string.error_message)
scope.launch { snackbarHostState.showSnackbar(message) }
}
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,71 @@ import com.google.firebase.ai.type.PublicPreviewAPI
import com.google.firebase.ai.type.content
import com.google.firebase.example.friendlymeals.data.schema.MealSchema
import com.google.firebase.example.friendlymeals.data.schema.RecipeSchema
import com.google.firebase.example.friendlymeals.data.schema.StoreLocalizerResult
import com.google.firebase.perf.performance
import com.google.firebase.perf.trace
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import kotlinx.serialization.json.Json
import javax.inject.Inject
import com.google.firebase.ai.type.LatLng
import com.google.firebase.ai.type.Tool
import com.google.firebase.ai.type.ToolConfig
import com.google.firebase.ai.type.retrievalConfig
import com.google.firebase.example.friendlymeals.data.schema.StoreSchema

@OptIn(PublicPreviewAPI::class)
class AIRemoteDataSource @Inject constructor(
aiModel: FirebaseAI,
private val aiModel: FirebaseAI,
private val remoteConfig: FirebaseRemoteConfig
) {
private val json = Json { ignoreUnknownKeys = true }

private val hybridGenerativeModel = aiModel.generativeModel(
modelName = remoteConfig.getString(HYBRID_CLOUD_MODEL_KEY),
onDeviceConfig = OnDeviceConfig(mode = InferenceMode.PREFER_IN_CLOUD)
)

private val templateGenerativeModel = aiModel.templateGenerativeModel()

suspend fun localizeIngredients(
ingredients: List<String>,
latitude: Double,
longitude: Double,
currentTime: String,
dayOfWeek: String
): List<StoreSchema> {
val groundingModel = aiModel.generativeModel(
modelName = remoteConfig.getString(GROUNDING_MODEL_KEY),
tools = listOf(Tool.googleMaps()),
toolConfig = ToolConfig(
retrievalConfig = retrievalConfig {
latLng = LatLng(latitude = latitude, longitude = longitude)
languageCode = LANGUAGE
}
)
)

val groundingPrompt = remoteConfig.getString(GROUNDING_PROMPT_KEY)
.replace("{{ingredients}}", ingredients.joinToString(", "))
.replace("{{dayOfWeek}}", dayOfWeek)
.replace("{{currentTime}}", currentTime)

return try {
val response = groundingModel.generateContent(groundingPrompt)
val rawText = response.text ?: return emptyList()

val cleanJson = rawText
.replace("```json", "")
.replace("```", "")
.trim()

json.decodeFromString<StoreLocalizerResult>(cleanJson).stores
} catch (e: Exception) {
Log.e(TAG, "Error localizing ingredients", e)
emptyList()
}
}

suspend fun generateIngredients(image: Bitmap): String {
// Adding a Performance Monitoring trace is completely optional. Traces can help you
// measure how long it takes to generate ingredients on device and in cloud.
Expand Down Expand Up @@ -142,6 +188,8 @@ class AIRemoteDataSource @Inject constructor(
private const val SCAN_MEAL_KEY = "scan_meal"
private const val HYBRID_CLOUD_MODEL_KEY = "hybrid_cloud_model"
private const val HYBRID_INGREDIENTS_PROMPT_KEY = "hybrid_ingredients_prompt"
private const val GROUNDING_MODEL_KEY = "grounding_model"
private const val GROUNDING_PROMPT_KEY = "grounding_prompt"

//Template input fields
private const val IMAGE_DATA_FIELD = "imageData"
Expand All @@ -153,6 +201,9 @@ class AIRemoteDataSource @Inject constructor(
//Template input values
private const val MIME_TYPE_VALUE = "image/jpeg"

//Grounding with Maps config
private const val LANGUAGE = "en_US"

//Class TAG
private const val TAG = "AIRemoteDataSource"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.graphics.Bitmap
import com.google.firebase.example.friendlymeals.data.datasource.AIRemoteDataSource
import com.google.firebase.example.friendlymeals.data.schema.MealSchema
import com.google.firebase.example.friendlymeals.data.schema.RecipeSchema
import com.google.firebase.example.friendlymeals.data.schema.StoreSchema
import javax.inject.Inject

class AIRepository @Inject constructor(
Expand All @@ -13,6 +14,22 @@ class AIRepository @Inject constructor(
return aiRemoteDataSource.generateIngredients(image)
}

suspend fun localizeIngredients(
ingredients: List<String>,
latitude: Double,
longitude: Double,
currentTime: String,
dayOfWeek: String
): List<StoreSchema> {
return aiRemoteDataSource.localizeIngredients(
ingredients,
latitude,
longitude,
currentTime,
dayOfWeek
)
}

suspend fun generateRecipe(ingredients: String, notes: String): RecipeSchema? {
return aiRemoteDataSource.generateRecipe(ingredients, notes)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.google.firebase.example.friendlymeals.data.schema

import kotlinx.serialization.Serializable

@Serializable
data class StoreSchema(
val name: String = "",
val address: String = "",
val distance: String = "",
val openNow: Boolean = false,
val closingSoon: Boolean = false,
val hasParking: Boolean = false,
val parkingDetails: String = "",
val mapUrl: String = ""
)

@Serializable
data class StoreLocalizerResult(
val stores: List<StoreSchema> = emptyList()
)
Loading