Skip to content

Commit b0fe943

Browse files
Update Readme, fix tags, fix Video sample UI (#28)
* Update Readme, fix tags, fix Video sample UI * Add mock google-services.json * Add string resources * Update minSdk for ML Kit GenAI samples
1 parent b0ffa68 commit b0fe943

File tree

12 files changed

+152
-29
lines changed

12 files changed

+152
-29
lines changed

ai-catalog/README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@ some of Google's models.
1313
1414
Browse the samples inside the `/samples` folder:
1515

16-
- gemini-chatbot a simple chatbot using Gemini 2.0 Flash
17-
- gemini-multimodal a single screen application leveraging text+image to text generation with Gemini 1.5 Flash
16+
- **gemini-chatbot**: a simple chatbot using Gemini 2.0 Flash
17+
- **gemini-multimodal**: a single screen application leveraging text+image to text generation with Gemini 2.0 Flash
18+
- **genai-summarization**: a text summarization sample using Gemini Nano
19+
- **genai-image-description**: an image description sample using Gemini Nano
20+
- **genai-writing-assistance**: a proofreading and rewriting sample using Gemini Nano
21+
- **imagen**: an image generation sample using Imagen
22+
- **magic-selfie**: an sample using ML Kit subject segmentation and Imagen for image generation
23+
- **gemini-video-summarization**: a video summarization sample using Gemini 2.0 Flash
1824
- More to come...
1925

2026
> **Requires Firebase setup** the samples relying on Google Cloud models (Gemini Pro, Gemini Flash, etc...)

ai-catalog/app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ dependencies {
7777
implementation(libs.androidx.navigation.runtime.ktx)
7878
implementation(libs.hilt.android)
7979
implementation(libs.hilt.navigation.compose)
80+
implementation(platform(libs.firebase.bom))
81+
implementation(libs.firebase.vertexai)
8082
ksp(libs.hilt.compiler)
8183

8284
implementation(project(":samples:gemini-multimodal"))
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"comment": "NOTE: Replace this file with your own google-services.json. Learn more at: https://firebase.google.com/docs/vertex-ai/get-started#no-existing-firebase",
3+
"project_info": {
4+
"project_number": "123456",
5+
"project_id": "mock_project",
6+
"storage_bucket": "mock_project.firebasestorage.app"
7+
},
8+
"client": [
9+
{
10+
"client_info": {
11+
"mobilesdk_app_id": "1:123456:android:123456",
12+
"android_client_info": {
13+
"package_name": "com.android.ai.catalog"
14+
}
15+
},
16+
"oauth_client": [],
17+
"api_key": [
18+
{
19+
"current_key": "123456-abcde"
20+
}
21+
],
22+
"services": {
23+
"appinvite_service": {
24+
"other_platform_oauth_client": []
25+
}
26+
}
27+
}
28+
],
29+
"configuration_version": "1"
30+
}

ai-catalog/app/src/main/java/com/android/ai/catalog/ui/CatalogScreen.kt

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
package com.android.ai.catalog.ui
1919

20+
import android.content.Intent
21+
import android.net.Uri
22+
import android.util.Log
2023
import androidx.compose.foundation.background
2124
import androidx.compose.foundation.layout.Box
2225
import androidx.compose.foundation.layout.Column
@@ -28,14 +31,20 @@ import androidx.compose.foundation.layout.width
2831
import androidx.compose.foundation.lazy.LazyColumn
2932
import androidx.compose.foundation.lazy.items
3033
import androidx.compose.foundation.shape.RoundedCornerShape
34+
import androidx.compose.material3.AlertDialog
3135
import androidx.compose.material3.ElevatedCard
3236
import androidx.compose.material3.ExperimentalMaterial3Api
3337
import androidx.compose.material3.MaterialTheme
3438
import androidx.compose.material3.Scaffold
3539
import androidx.compose.material3.Text
40+
import androidx.compose.material3.TextButton
3641
import androidx.compose.material3.TopAppBar
3742
import androidx.compose.material3.TopAppBarDefaults
3843
import androidx.compose.runtime.Composable
44+
import androidx.compose.runtime.getValue
45+
import androidx.compose.runtime.mutableStateOf
46+
import androidx.compose.runtime.remember
47+
import androidx.compose.runtime.setValue
3948
import androidx.compose.ui.Modifier
4049
import androidx.compose.ui.platform.LocalContext
4150
import androidx.compose.ui.res.stringResource
@@ -48,16 +57,20 @@ import androidx.navigation.compose.rememberNavController
4857
import com.android.ai.catalog.R
4958
import com.android.ai.catalog.ui.domain.SampleCatalog
5059
import com.android.ai.catalog.ui.domain.SampleCatalogItem
60+
import com.google.firebase.FirebaseApp
5161
import kotlinx.serialization.Serializable
5262

5363
@OptIn(ExperimentalMaterial3Api::class)
5464
@Composable
5565
fun CatalogScreen(modifier: Modifier = Modifier) {
66+
val context = LocalContext.current
5667
val navController = rememberNavController()
5768
val catalog = SampleCatalog(
58-
LocalContext.current
69+
context
5970
)
6071

72+
var isDialogOpened by remember { mutableStateOf(false) }
73+
6174
NavHost(
6275
navController = navController,
6376
startDestination = HomeScreen,
@@ -81,7 +94,11 @@ fun CatalogScreen(modifier: Modifier = Modifier) {
8194
) {
8295
items(catalog.list) {
8396
CatalogListItem(catalogItem = it) {
84-
navController.navigate(it.route)
97+
if (it.needsFirebase && !isFirebaseInitialized()) {
98+
isDialogOpened = true
99+
} else {
100+
navController.navigate(it.route)
101+
}
85102
}
86103
}
87104
}
@@ -95,6 +112,18 @@ fun CatalogScreen(modifier: Modifier = Modifier) {
95112
}
96113
}
97114
}
115+
116+
117+
if (isDialogOpened) {
118+
firebaseRequiredAlert(
119+
onDismiss = { isDialogOpened = false },
120+
onOpenFirebaseDocClick = {
121+
isDialogOpened = false
122+
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://firebase.google.com/docs/vertex-ai/get-started#no-existing-firebase"))
123+
context.startActivity(intent)
124+
}
125+
)
126+
}
98127
}
99128

100129
@Composable
@@ -150,4 +179,56 @@ fun CatalogListItem(
150179
}
151180

152181
@Serializable
153-
object HomeScreen
182+
object HomeScreen
183+
184+
@Composable
185+
fun firebaseRequiredAlert(
186+
onDismiss: () -> Unit = {},
187+
onOpenFirebaseDocClick: () -> Unit = {}
188+
) {
189+
AlertDialog(
190+
onDismissRequest = {
191+
onDismiss()
192+
},
193+
title = {
194+
Text(text = stringResource(R.string.firebase_required))
195+
},
196+
text = {
197+
Text(stringResource(R.string.firebase_required_description))
198+
},
199+
dismissButton = {
200+
TextButton(
201+
onClick = {
202+
onDismiss()
203+
}
204+
) {
205+
Text(stringResource(R.string.close))
206+
}
207+
},
208+
confirmButton = {
209+
TextButton(
210+
onClick = {
211+
onOpenFirebaseDocClick()
212+
}
213+
) {
214+
Text(stringResource(R.string.firebase_doc_button))
215+
}
216+
}
217+
)
218+
}
219+
220+
221+
fun isFirebaseInitialized(): Boolean {
222+
return try {
223+
val firebaseApp = FirebaseApp.getInstance()
224+
Log.d("CatalogScreen", "firebaseApp.options.projectId: ${firebaseApp.options.projectId}")
225+
if (firebaseApp.options.projectId == "mock_project") {
226+
Log.e("CatalogScreen", "Firebase is not initialized")
227+
return false
228+
}
229+
return true
230+
} catch (e: IllegalStateException) {
231+
Log.e("CatalogScreen", "Firebase is not initialized")
232+
return false
233+
}
234+
}

ai-catalog/app/src/main/java/com/android/ai/catalog/ui/domain/SampleCatalog.kt

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ import androidx.compose.ui.graphics.Color
2323
import com.android.ai.catalog.R
2424
import com.android.ai.samples.geminichatbot.GeminiChatbotScreen
2525
import com.android.ai.samples.geminimultimodal.GeminiMultimodalScreen
26+
import com.android.ai.samples.geminivideosummary.VideoSummarizationScreen
2627
import com.android.ai.samples.genai_image_description.GenAIImageDescriptionScreen
2728
import com.android.ai.samples.genai_summarization.GenAISummarizationScreen
2829
import com.android.ai.samples.genai_writing_assistance.GenAIWritingAssistanceScreen
2930
import com.android.ai.samples.imagen.ImagenScreen
3031
import com.android.ai.samples.magicselfie.MagicSelfieScreen
31-
import com.android.ai.samples.geminivideosummary.VideoSummarizationScreen
3232

3333
class SampleCatalog(
3434
context: Context
@@ -40,56 +40,61 @@ class SampleCatalog(
4040
description = context.getString(R.string.gemini_multimodal_sample_description),
4141
route = "GeminiMultimodalScreen",
4242
sampleEntryScreen = { GeminiMultimodalScreen() },
43-
tags = listOf(SampleTags.GEMINI_1_5_FLASH, SampleTags.FIREBASE)
43+
tags = listOf(SampleTags.GEMINI_2_0_FLASH, SampleTags.FIREBASE),
44+
needsFirebase = true
4445
),
4546
SampleCatalogItem(
4647
title = context.getString(R.string.gemini_chatbot_sample_title),
4748
description = context.getString(R.string.gemini_chatbot_sample_description),
4849
route = "GeminiChitchatScreen",
4950
sampleEntryScreen = { GeminiChatbotScreen() },
50-
tags = listOf(SampleTags.GEMINI_1_5_PRO, SampleTags.FIREBASE)
51+
tags = listOf(SampleTags.GEMINI_2_0_FLASH, SampleTags.FIREBASE),
52+
needsFirebase = true
5153
),
5254
SampleCatalogItem(
5355
title = context.getString(R.string.genai_summarization_sample_title),
5456
description = context.getString(R.string.genai_summarization_sample_description),
5557
route = "GenAISummarizationScreen",
5658
sampleEntryScreen = { GenAISummarizationScreen() },
57-
tags = listOf(SampleTags.GEMINI_NANO)
59+
tags = listOf(SampleTags.GEMINI_NANO, SampleTags.ML_KIT)
5860
),
5961
SampleCatalogItem(
6062
title = context.getString(R.string.genai_image_description_sample_title),
6163
description = context.getString(R.string.genai_image_description_sample_description),
6264
route = "GenAIImageDescriptionScreen",
6365
sampleEntryScreen = { GenAIImageDescriptionScreen() },
64-
tags = listOf(SampleTags.GEMINI_NANO)
66+
tags = listOf(SampleTags.GEMINI_NANO, SampleTags.ML_KIT)
6567
),
6668
SampleCatalogItem(
6769
title = context.getString(R.string.genai_writing_assistance_sample_title),
6870
description = context.getString(R.string.genai_writing_assistance_sample_description),
6971
route = "GenAIWritingAssistanceScreen",
7072
sampleEntryScreen = { GenAIWritingAssistanceScreen() },
71-
tags = listOf(SampleTags.GEMINI_NANO)
73+
tags = listOf(SampleTags.GEMINI_NANO, SampleTags.ML_KIT)
7274
),
7375
SampleCatalogItem(
7476
title = context.getString(R.string.imagen_sample_title),
7577
description = context.getString(R.string.imagen_sample_description),
7678
route = "ImagenImageGenerationScreen",
7779
sampleEntryScreen = { ImagenScreen() },
78-
tags = listOf(SampleTags.IMAGEN, SampleTags.FIREBASE)
80+
tags = listOf(SampleTags.IMAGEN, SampleTags.FIREBASE),
81+
needsFirebase = true
7982
),
8083
SampleCatalogItem(
8184
title = context.getString(R.string.magic_selfie_sample_title),
8285
description = context.getString(R.string.magic_selfie_sample_description),
8386
route = "MagicSelfieScreen",
8487
sampleEntryScreen = { MagicSelfieScreen() },
85-
tags = listOf(SampleTags.IMAGEN, SampleTags.FIREBASE, SampleTags.ML_KIT)
88+
tags = listOf(SampleTags.IMAGEN, SampleTags.FIREBASE, SampleTags.ML_KIT),
89+
needsFirebase = true
8690
),
8791
SampleCatalogItem(
8892
title = context.getString(R.string.gemini_video_summarization_sample_title),
8993
description = context.getString(R.string.gemini_video_summarization_sample_description),
9094
route = "VideoSummarizationScreen",
9195
sampleEntryScreen = { VideoSummarizationScreen() },
92-
tags = listOf(SampleTags.GEMINI_2_0_FLASH, SampleTags.FIREBASE, SampleTags.MEDIA3)
96+
tags = listOf(SampleTags.GEMINI_2_0_FLASH, SampleTags.FIREBASE, SampleTags.MEDIA3),
97+
needsFirebase = true
9398
),
9499

95100
// To create a new sample entry, add a new SampleCatalogItem here.
@@ -102,17 +107,16 @@ data class SampleCatalogItem(
102107
val description: String,
103108
val route: String,
104109
val sampleEntryScreen: @Composable () -> Unit,
105-
val tags: List<SampleTags> = emptyList()
110+
val tags: List<SampleTags> = emptyList(),
111+
val needsFirebase: Boolean = false
106112
)
107113

108114
enum class SampleTags(
109115
val label: String,
110116
val backgroundColor: Color,
111-
val textColor: Color,
117+
val textColor: Color
112118
) {
113119
FIREBASE("Firebase", Color(0xFFFF9100), Color.White),
114-
GEMINI_1_5_PRO("Gemini 1.5 Pro", Color(0xFF4285F4), Color.White),
115-
GEMINI_1_5_FLASH("Gemini 1.5 Flash", Color(0xFF4285F4), Color.White),
116120
GEMINI_2_0_FLASH("Gemini 2.0 Flash", Color(0xFF4285F4), Color.White),
117121
GEMINI_NANO("Gemini Nano", Color(0xFF7abafe), Color.White),
118122
IMAGEN("Imagen", Color(0xFF7CB342), Color.White),

ai-catalog/app/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@
1818
<string name="magic_selfie_sample_description">Change the background of you selfies with Imagen and the ML Kit Segmentation API</string>
1919
<string name="gemini_video_summarization_sample_title">Video Summarization with Gemini and Firebase</string>
2020
<string name="gemini_video_summarization_sample_description">"Generate a summary of a video (from a cloud URL or Youtube) with Gemini API powered by Firebase"</string>
21+
<string name="firebase_required">Firebase Required</string>
22+
<string name="firebase_required_description">This feature requires Firebase to be initialized.</string>
23+
<string name="close">Close</string>
24+
<string name="firebase_doc_button">Show me how</string>
2125
</resources>

ai-catalog/samples/gemini-multimodal/src/main/java/com/android/ai/samples/geminimultimodal/GeminiMultimodalScreen.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.Column
3232
import androidx.compose.foundation.layout.Row
3333
import androidx.compose.foundation.layout.Spacer
3434
import androidx.compose.foundation.layout.fillMaxSize
35+
import androidx.compose.foundation.layout.fillMaxWidth
3536
import androidx.compose.foundation.layout.height
3637
import androidx.compose.foundation.layout.padding
3738
import androidx.compose.foundation.layout.safeContentPadding
@@ -133,7 +134,7 @@ fun GeminiMultimodalScreen(
133134
bitmap = it.asImageBitmap(),
134135
contentDescription = "Picture",
135136
contentScale = ContentScale.Fit,
136-
modifier = Modifier.fillMaxSize()
137+
modifier = Modifier.fillMaxWidth()
137138
)
138139
}
139140
}

ai-catalog/samples/gemini-video-summarization/src/main/java/com/android/ai/samples/geminivideosummary/VideoSummarizationScreen.kt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ fun VideoSummarizationScreen(viewModel: VideoSummarizationViewModel = hiltViewMo
7979
var newVideoUrl by remember { mutableStateOf("") }
8080
val outputText by viewModel.outputText.collectAsState()
8181
var textForSpeech by remember { mutableStateOf("") }
82-
//manage the state of the Text to speech here
8382
var textToSpeech: TextToSpeech? by remember { mutableStateOf(null) }
8483
var isInitialized by remember { mutableStateOf(false) }
8584
var isSpeaking by remember { mutableStateOf(false) }
@@ -103,7 +102,6 @@ fun VideoSummarizationScreen(viewModel: VideoSummarizationViewModel = hiltViewMo
103102
})
104103
}
105104

106-
//Initialize the text to speech.
107105
DisposableEffect(key1 = true) {
108106
textToSpeech = initializeTextToSpeech(context) { initialized ->
109107
isInitialized = initialized
@@ -112,9 +110,7 @@ fun VideoSummarizationScreen(viewModel: VideoSummarizationViewModel = hiltViewMo
112110
textToSpeech?.shutdown()
113111
}
114112
}
115-
// Default to English US
116113
var selectedAccent by remember { mutableStateOf(Locale.FRANCE) }
117-
// Options for accents
118114
val accentOptions = listOf(
119115
Locale.UK,
120116
Locale.FRANCE,
@@ -148,7 +144,6 @@ fun VideoSummarizationScreen(viewModel: VideoSummarizationViewModel = hiltViewMo
148144
videoOptions = videoOptions,
149145
onVideoUriSelected = { uri ->
150146
selectedVideoUri = uri
151-
//clear the text when the video is changed.
152147
viewModel.clearOutputText()
153148
},
154149
onNewVideoUrlChanged = { url ->
@@ -232,7 +227,6 @@ fun onSelectedVideoChange(
232227
exoPlayer.setMediaItem(MediaItem.fromUri(selectedVideoUri))
233228
exoPlayer.prepare()
234229
}
235-
//when the video is changed stop speaking
236230
textToSpeech?.stop()
237231
onSpeakingStateChange(false, true)
238232
}
@@ -246,7 +240,6 @@ fun onSummarizeButtonClick(
246240
) {
247241
if (selectedVideoUri != null) {
248242
viewModel.getVideoSummary(selectedVideoUri)
249-
//when a button is pressed stop speaking
250243
textToSpeech?.stop()
251244
onSpeakingStateChange(false, true)
252245
}

ai-catalog/samples/gemini-video-summarization/src/main/java/com/android/ai/samples/geminivideosummary/ui/TextToSpeechControls.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ fun TextToSpeechControls(
5959
},
6060
modifier = Modifier
6161
.clickable { onAccentDropdownExpanded(!isAccentDropdownExpanded) }
62-
.padding(end = 8.dp))
62+
.padding(end = 8.dp)
63+
.weight(1f))
6364
DropdownMenu(
6465
expanded = isAccentDropdownExpanded,
6566
onDismissRequest = { onAccentDropdownExpanded(false) },

0 commit comments

Comments
 (0)