diff --git a/firebase-ai/app/build.gradle.kts b/firebase-ai/app/build.gradle.kts
index f350bc2ad4..6f88d35e50 100644
--- a/firebase-ai/app/build.gradle.kts
+++ b/firebase-ai/app/build.gradle.kts
@@ -67,6 +67,13 @@ dependencies {
// Webkit
implementation(libs.androidx.webkit)
+ // CameraX
+ implementation(libs.androidx.camera.core)
+ implementation(libs.androidx.camera.camera2)
+ implementation(libs.androidx.camera.lifecycle)
+ implementation(libs.androidx.camera.view)
+ implementation(libs.androidx.camera.extensions)
+
// Material for XML-based theme
implementation(libs.material)
diff --git a/firebase-ai/app/src/main/AndroidManifest.xml b/firebase-ai/app/src/main/AndroidManifest.xml
index 699c61714c..93521d7c3d 100644
--- a/firebase-ai/app/src/main/AndroidManifest.xml
+++ b/firebase-ai/app/src/main/AndroidManifest.xml
@@ -4,8 +4,12 @@
-
+
+
+
+
+
diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt
index ad3a5cd435..53a06dd8cc 100644
--- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt
+++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/FirebaseAISamples.kt
@@ -275,6 +275,8 @@ val FIREBASE_AI_SAMPLES = listOf(
description = "Use bidirectional streaming to get information about" +
" weather conditions for a specific US city on a specific date",
navRoute = "stream",
+ backend = GenerativeBackend.vertexAI(),
+ modelName = "gemini-2.0-flash-live-preview-04-09",
categories = listOf(Category.LIVE_API, Category.AUDIO, Category.FUNCTION_CALLING),
tools = listOf(
Tool.functionDeclarations(
@@ -298,6 +300,36 @@ val FIREBASE_AI_SAMPLES = listOf(
text("What was the weather in Boston, MA on October 17, 2024?")
}
),
+ Sample(
+ title = "Video input",
+ description = "Use bidirectional streaming to chat with Gemini using your" +
+ " phone's camera",
+ navRoute = "streamVideo",
+ backend = GenerativeBackend.vertexAI(),
+ modelName = "gemini-2.0-flash-live-preview-04-09",
+ categories = listOf(Category.LIVE_API, Category.VIDEO, Category.FUNCTION_CALLING),
+ tools = listOf(
+ Tool.functionDeclarations(
+ listOf(
+ FunctionDeclaration(
+ "fetchWeather",
+ "Get the weather conditions for a specific US city on a specific date.",
+ mapOf(
+ "city" to Schema.string("The US city of the location."),
+ "state" to Schema.string("The US state of the location."),
+ "date" to Schema.string(
+ "The date for which to get the weather." +
+ " Date must be in the format: YYYY-MM-DD."
+ ),
+ ),
+ )
+ )
+ )
+ ),
+ initialPrompt = content {
+ text("What was the weather in Boston, MA on October 17, 2024?")
+ }
+ ),
Sample(
title = "Weather Chat",
description = "Use function calling to get the weather conditions" +
diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt
index 54eaff6543..39e6e34e7a 100644
--- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt
+++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/MainActivity.kt
@@ -1,6 +1,7 @@
package com.google.firebase.quickstart.ai
import android.Manifest
+import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
@@ -31,6 +32,8 @@ import androidx.navigation.compose.rememberNavController
import com.google.firebase.ai.type.toImagenInlineImage
import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeRoute
import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeScreen
+import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoRoute
+import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeVideoScreen
import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenRoute
import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenScreen
import com.google.firebase.quickstart.ai.feature.text.ChatRoute
@@ -42,10 +45,7 @@ class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- if(ContextCompat.checkSelfPermission(this,
- Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO), 1)
- }
+
enableEdgeToEdge()
catImage = BitmapFactory.decodeResource(applicationContext.resources, R.drawable.cat)
setContent {
@@ -90,6 +90,9 @@ class MainActivity : ComponentActivity() {
"stream" -> {
navController.navigate(StreamRealtimeRoute(it.id))
}
+ "streamVideo" -> {
+ navController.navigate(StreamRealtimeVideoRoute(it.id))
+ }
}
}
)
@@ -102,10 +105,18 @@ class MainActivity : ComponentActivity() {
composable {
ImagenScreen()
}
- // Stream Realtime Samples
+ // The permission is checked by the @RequiresPermission annotation on the
+ // StreamRealtimeScreen composable.
+ @SuppressLint("MissingPermission")
composable {
StreamRealtimeScreen()
}
+ // The permission is checked by the @RequiresPermission annotation on the
+ // StreamRealtimeVideoScreen composable.
+ @SuppressLint("MissingPermission")
+ composable {
+ StreamRealtimeVideoScreen()
+ }
}
}
}
diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt
index a7f183704f..3f36b81c3f 100644
--- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt
+++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/BidiViewModel.kt
@@ -1,56 +1,33 @@
-package com.google.firebase.quickstart.ai.feature.media.imagen
+package com.google.firebase.quickstart.ai.feature.live
-import android.Manifest
+import android.annotation.SuppressLint
import android.graphics.Bitmap
-import androidx.annotation.RequiresPermission
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.toRoute
import com.google.firebase.Firebase
import com.google.firebase.ai.FirebaseAI
-import com.google.firebase.ai.ImagenModel
-import com.google.firebase.ai.LiveGenerativeModel
-import com.google.firebase.ai.ai
import com.google.firebase.ai.type.FunctionCallPart
import com.google.firebase.ai.type.FunctionResponsePart
-import com.google.firebase.ai.type.GenerativeBackend
-import com.google.firebase.ai.type.ImagenAspectRatio
-import com.google.firebase.ai.type.ImagenImageFormat
-import com.google.firebase.ai.type.ImagenPersonFilterLevel
-import com.google.firebase.ai.type.ImagenSafetyFilterLevel
-import com.google.firebase.ai.type.ImagenSafetySettings
-import com.google.firebase.ai.type.InlineDataPart
-import com.google.firebase.ai.type.LiveServerContent
-import com.google.firebase.ai.type.LiveServerMessage
+import com.google.firebase.ai.type.InlineData
import com.google.firebase.ai.type.LiveSession
import com.google.firebase.ai.type.PublicPreviewAPI
import com.google.firebase.ai.type.ResponseModality
import com.google.firebase.ai.type.SpeechConfig
-import com.google.firebase.ai.type.TextPart
-import com.google.firebase.ai.type.Tool
import com.google.firebase.ai.type.Voice
-import com.google.firebase.ai.type.asTextOrNull
-import com.google.firebase.ai.type.imagenGenerationConfig
import com.google.firebase.ai.type.liveGenerationConfig
import com.google.firebase.app
import com.google.firebase.quickstart.ai.FIREBASE_AI_SAMPLES
-import com.google.firebase.quickstart.ai.feature.live.StreamRealtimeRoute
-import com.google.firebase.quickstart.ai.feature.text.functioncalling.WeatherRepository
import com.google.firebase.quickstart.ai.feature.text.functioncalling.WeatherRepository.Companion.fetchWeather
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
+import java.io.ByteArrayOutputStream
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
@OptIn(PublicPreviewAPI::class)
-class BidiViewModel(
- savedStateHandle: SavedStateHandle
-) : ViewModel() {
+class BidiViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
private val sampleId = savedStateHandle.toRoute().sampleId
private val sample = FIREBASE_AI_SAMPLES.first { it.id == sampleId }
@@ -63,41 +40,54 @@ class BidiViewModel(
// Change this to ContentModality.TEXT if you want text output.
responseModality = ResponseModality.AUDIO
}
+
@OptIn(PublicPreviewAPI::class)
- val liveModel = FirebaseAI.getInstance(Firebase.app, sample.backend).liveModel(
- "gemini-live-2.5-flash",
- generationConfig = liveGenerationConfig,
- tools = sample.tools
- )
- runBlocking {
- liveSession = liveModel.connect()
- }
+ val liveModel =
+ FirebaseAI.getInstance(Firebase.app, sample.backend)
+ .liveModel(
+ modelName = sample.modelName ?: "gemini-live-2.5-flash",
+ generationConfig = liveGenerationConfig,
+ tools = sample.tools,
+ )
+ runBlocking { liveSession = liveModel.connect() }
}
- fun handler(fetchWeatherCall: FunctionCallPart) : FunctionResponsePart {
- val response:JsonObject
+ fun handler(fetchWeatherCall: FunctionCallPart): FunctionResponsePart {
+ val response: JsonObject
fetchWeatherCall.let {
val city = it.args["city"]?.jsonPrimitive?.content
val state = it.args["state"]?.jsonPrimitive?.content
val date = it.args["date"]?.jsonPrimitive?.content
runBlocking {
- response = if(!city.isNullOrEmpty() and !state.isNullOrEmpty() and date.isNullOrEmpty()) {
- fetchWeather(city!!, state!!, date!!)
- } else {
- JsonObject(emptyMap())
- }
+ response =
+ if (!city.isNullOrEmpty() and !state.isNullOrEmpty() and date.isNullOrEmpty()) {
+ fetchWeather(city!!, state!!, date!!)
+ } else {
+ JsonObject(emptyMap())
+ }
}
}
- return FunctionResponsePart("fetchWeather", response, fetchWeatherCall.id)
+ return FunctionResponsePart("fetchWeather", response, fetchWeatherCall.id)
}
- @RequiresPermission(Manifest.permission.RECORD_AUDIO)
+
+ // The permission check is handled by the view that calls this function.
+ @SuppressLint("MissingPermission")
suspend fun startConversation() {
- liveSession.startAudioConversation(::handler)
+ liveSession.startAudioConversation(::handler)
}
fun endConversation() {
liveSession.stopAudioConversation()
}
+ fun sendVideoFrame(frame: Bitmap) {
+ viewModelScope.launch {
+ // Directly compress the Bitmap to a ByteArray
+ val byteArrayOutputStream = ByteArrayOutputStream()
+ frame.compress(Bitmap.CompressFormat.JPEG, 80, byteArrayOutputStream)
+ val jpegBytes = byteArrayOutputStream.toByteArray()
+ liveSession.sendVideoRealtime(InlineData(jpegBytes, "image/jpeg"))
+ }
+ }
}
diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/CameraView.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/CameraView.kt
new file mode 100644
index 0000000000..8fcf0258ba
--- /dev/null
+++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/CameraView.kt
@@ -0,0 +1,98 @@
+package com.google.firebase.quickstart.ai.feature.live
+
+import android.annotation.SuppressLint
+import android.graphics.Bitmap
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageProxy
+import androidx.camera.core.Preview
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.view.PreviewView
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.LifecycleOwner
+import kotlin.time.Duration.Companion.seconds
+
+@Composable
+fun CameraView(
+ modifier: Modifier = Modifier,
+ cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA,
+ onFrameCaptured: (Bitmap) -> Unit,
+) {
+ val context = LocalContext.current
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
+
+ AndroidView(
+ factory = { ctx ->
+ val previewView = PreviewView(ctx)
+ val executor = ContextCompat.getMainExecutor(ctx)
+ cameraProviderFuture.addListener(
+ {
+ val cameraProvider = cameraProviderFuture.get()
+ bindPreview(
+ lifecycleOwner,
+ previewView,
+ cameraProvider,
+ cameraSelector,
+ onFrameCaptured,
+ )
+ },
+ executor,
+ )
+ previewView
+ },
+ modifier = modifier,
+ )
+}
+
+private fun bindPreview(
+ lifecycleOwner: LifecycleOwner,
+ previewView: PreviewView,
+ cameraProvider: ProcessCameraProvider,
+ cameraSelector: CameraSelector,
+ onFrameCaptured: (Bitmap) -> Unit,
+) {
+ val preview =
+ Preview.Builder().build().also { it.setSurfaceProvider(previewView.surfaceProvider) }
+
+ val imageAnalysis =
+ ImageAnalysis.Builder()
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
+ .build()
+ .also {
+ it.setAnalyzer(
+ ContextCompat.getMainExecutor(previewView.context),
+ SnapshotFrameAnalyzer(onFrameCaptured),
+ )
+ }
+
+ cameraProvider.unbindAll()
+ cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageAnalysis)
+}
+
+// Calls the [onFrameCaptured] callback with the captured frame every second.
+private class SnapshotFrameAnalyzer(private val onFrameCaptured: (Bitmap) -> Unit) :
+ ImageAnalysis.Analyzer {
+ private var lastFrameTimestamp = 0L
+ private val interval = 1.seconds // 1 second
+
+ @SuppressLint("UnsafeOptInUsageError")
+ override fun analyze(image: ImageProxy) {
+ val currentTimestamp = System.currentTimeMillis()
+ if (lastFrameTimestamp == 0L) {
+ lastFrameTimestamp = currentTimestamp
+ }
+
+ if (currentTimestamp - lastFrameTimestamp >= interval.inWholeMilliseconds) {
+ onFrameCaptured(image.toBitmap())
+ lastFrameTimestamp = currentTimestamp
+ }
+ image.close()
+ }
+}
diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt
index f088cda23b..194b04023f 100644
--- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt
+++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeScreen.kt
@@ -32,8 +32,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.viewmodel.compose.viewModel
-import com.google.firebase.quickstart.ai.feature.media.imagen.BidiViewModel
-import com.google.firebase.quickstart.ai.feature.media.imagen.ImagenViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeVideoScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeVideoScreen.kt
new file mode 100644
index 0000000000..a30c93980c
--- /dev/null
+++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/live/StreamRealtimeVideoScreen.kt
@@ -0,0 +1,82 @@
+package com.google.firebase.quickstart.ai.feature.live
+
+import android.Manifest
+import android.content.pm.PackageManager
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.annotation.RequiresPermission
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.viewmodel.compose.viewModel
+import kotlinx.coroutines.launch
+import kotlinx.serialization.Serializable
+
+@Serializable class StreamRealtimeVideoRoute(val sampleId: String)
+
+@RequiresPermission(allOf = [Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA])
+@Composable
+fun StreamRealtimeVideoScreen(bidiView: BidiViewModel = viewModel()) {
+ val backgroundColor = MaterialTheme.colorScheme.background
+
+ val scope = rememberCoroutineScope()
+
+ val context = LocalContext.current
+ var hasPermissions by remember {
+ mutableStateOf(
+ ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) ==
+ PackageManager.PERMISSION_GRANTED &&
+ ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) ==
+ PackageManager.PERMISSION_GRANTED
+ )
+ }
+
+ val launcher =
+ rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
+ permissions ->
+ hasPermissions = permissions.values.all { it }
+ }
+
+ LaunchedEffect(Unit) {
+ if (!hasPermissions) {
+ launcher.launch(arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO))
+ }
+ }
+
+ DisposableEffect(hasPermissions) {
+ if (hasPermissions) {
+ scope.launch { bidiView.startConversation() }
+ }
+ onDispose { bidiView.endConversation() }
+ }
+
+ Surface(modifier = Modifier.fillMaxSize(), color = backgroundColor) {
+ Column(modifier = Modifier.fillMaxSize()) {
+ if (hasPermissions) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ CameraView(
+ modifier = Modifier.fillMaxHeight(0.5f),
+ onFrameCaptured = { bitmap -> bidiView.sendVideoFrame(bitmap) },
+ )
+ }
+ } else {
+ Text("Camera and audio permissions are required to use this feature.")
+ }
+ }
+ }
+}
diff --git a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt
index 276024c27f..f89bcba17a 100644
--- a/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt
+++ b/firebase-ai/app/src/main/java/com/google/firebase/quickstart/ai/feature/text/ChatScreen.kt
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.Bitmap
import android.net.Uri
+import androidx.core.net.toUri
import android.provider.OpenableColumns
import android.text.format.Formatter
import android.webkit.WebResourceRequest
@@ -374,7 +375,7 @@ fun SourceLinkView(
ClickableText(text = annotatedString, onClick = { offset ->
annotatedString.getStringAnnotations(tag = "URL", start = offset, end = offset)
.firstOrNull()?.let { annotation ->
- context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(annotation.item)))
+ context.startActivity(Intent(Intent.ACTION_VIEW, annotation.item.toUri()))
}
})
}
diff --git a/firebase-ai/app/src/main/res/values/colors.xml b/firebase-ai/app/src/main/res/values/colors.xml
index f8c6127d32..55344e5192 100644
--- a/firebase-ai/app/src/main/res/values/colors.xml
+++ b/firebase-ai/app/src/main/res/values/colors.xml
@@ -1,10 +1,3 @@
- #FFBB86FC
- #FF6200EE
- #FF3700B3
- #FF03DAC5
- #FF018786
- #FF000000
- #FFFFFFFF
\ No newline at end of file
diff --git a/firebase-ai/gradle/libs.versions.toml b/firebase-ai/gradle/libs.versions.toml
index 49abf9fe73..431f132872 100644
--- a/firebase-ai/gradle/libs.versions.toml
+++ b/firebase-ai/gradle/libs.versions.toml
@@ -1,19 +1,20 @@
[versions]
activityCompose = "1.11.0"
-agp = "8.9.2"
-composeBom = "2024.09.00"
+agp = "8.13.0"
+composeBom = "2025.10.00"
composeNavigation = "2.9.5"
coreKtx = "1.17.0"
espressoCore = "3.7.0"
-firebaseBom = "34.4.0"
+firebaseBom = "34.5.0"
junit = "4.13.2"
junitVersion = "1.3.0"
-kotlin = "2.0.21"
+kotlin = "2.2.20"
kotlinxSerializationCore = "1.9.0"
lifecycle = "2.9.4"
-lifecycleRuntimeKtx = "2.8.7"
+lifecycleRuntimeKtx = "2.9.4"
material = "1.13.0"
webkit = "1.14.0"
+camerax = "1.5.1"
[libraries]
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
@@ -41,6 +42,11 @@ firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "fir
junit = { group = "junit", name = "junit", version.ref = "junit" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationCore" }
material = { module = "com.google.android.material:material", version.ref = "material" }
+androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" }
+androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "camerax" }
+androidx-camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "camerax" }
+androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" }
+androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "camerax" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
diff --git a/firebase-ai/settings.gradle.kts b/firebase-ai/settings.gradle.kts
index f1cb15d710..26668b9361 100644
--- a/firebase-ai/settings.gradle.kts
+++ b/firebase-ai/settings.gradle.kts
@@ -14,6 +14,7 @@ pluginManagement {
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
+ mavenLocal()
google()
mavenCentral()
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c60c47da91..79096c7fa2 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,12 +1,17 @@
[versions]
+activityCompose = "1.11.0"
agp = "8.13.0"
+camerax = "1.5.1"
coilCompose = "2.7.0"
-firebaseBom = "34.4.0"
+firebaseBom = "34.5.0"
kotlin = "2.2.21"
coreKtx = "1.17.0"
+espressoCore = "3.7.0"
+firebaseBom = "34.4.0"
+googleServices = "4.4.4"
junit = "4.13.2"
junitVersion = "1.3.0"
-espressoCore = "3.7.0"
+kotlin = "2.2.20"
kotlinxSerializationCore = "1.9.0"
lifecycle = "2.9.4"
activityCompose = "1.11.0"
@@ -17,33 +22,38 @@ material = "1.13.0"
webkit = "1.14.0"
[libraries]
+androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
+androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" }
+androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "camerax" }
+androidx-camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "camerax" }
+androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" }
+androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "camerax" }
+androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-lifecycle-runtime-compose-android = { module = "androidx.lifecycle:lifecycle-runtime-compose-android", version.ref = "lifecycle" }
+androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle" }
androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" }
+androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite" }
-coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
-firebase-ai = { module = "com.google.firebase:firebase-ai" }
-firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
-junit = { group = "junit", name = "junit", version.ref = "junit" }
-androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
-androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
-androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
-androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
-androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
+androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
+androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
-androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
-androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
-androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
+coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "composeNavigation"}
+firebase-ai = { module = "com.google.firebase:firebase-ai" }
+firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerializationCore" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationCore" }
-androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
material = { module = "com.google.android.material:material", version.ref = "material" }
[plugins]