1
1
package com.google.firebase.quickstart.ai.feature.media.imagen
2
2
3
+ import android.net.Uri
4
+ import android.provider.OpenableColumns
5
+ import android.text.format.Formatter
6
+ import androidx.activity.compose.rememberLauncherForActivityResult
7
+ import androidx.activity.result.contract.ActivityResultContracts
3
8
import androidx.compose.foundation.Image
9
+ import androidx.compose.foundation.clickable
10
+ import androidx.compose.foundation.layout.Arrangement
4
11
import androidx.compose.foundation.layout.Box
5
12
import androidx.compose.foundation.layout.Column
13
+ import androidx.compose.foundation.layout.Row
6
14
import androidx.compose.foundation.layout.fillMaxWidth
15
+ import androidx.compose.foundation.layout.height
7
16
import androidx.compose.foundation.layout.padding
8
17
import androidx.compose.foundation.lazy.grid.GridCells
9
- import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
18
+ import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
10
19
import androidx.compose.foundation.lazy.grid.items
20
+ import androidx.compose.foundation.rememberScrollState
21
+ import androidx.compose.foundation.verticalScroll
11
22
import androidx.compose.material3.Card
12
23
import androidx.compose.material3.CardDefaults
13
24
import androidx.compose.material3.CircularProgressIndicator
25
+ import androidx.compose.material3.DropdownMenu
26
+ import androidx.compose.material3.DropdownMenuItem
14
27
import androidx.compose.material3.ElevatedCard
15
28
import androidx.compose.material3.MaterialTheme
16
29
import androidx.compose.material3.OutlinedTextField
17
30
import androidx.compose.material3.Text
18
31
import androidx.compose.material3.TextButton
19
32
import androidx.compose.runtime.Composable
20
33
import androidx.compose.runtime.getValue
34
+ import androidx.compose.runtime.mutableIntStateOf
21
35
import androidx.compose.runtime.mutableStateOf
36
+ import androidx.compose.runtime.remember
37
+ import androidx.compose.runtime.rememberCoroutineScope
22
38
import androidx.compose.runtime.saveable.rememberSaveable
23
39
import androidx.compose.runtime.setValue
24
40
import androidx.compose.ui.Alignment
25
41
import androidx.compose.ui.Modifier
26
42
import androidx.compose.ui.graphics.asImageBitmap
43
+ import androidx.compose.ui.platform.LocalContext
44
+ import androidx.compose.ui.res.painterResource
27
45
import androidx.compose.ui.unit.dp
28
46
import androidx.lifecycle.compose.collectAsStateWithLifecycle
29
47
import androidx.lifecycle.viewmodel.compose.viewModel
48
+ import com.google.firebase.quickstart.ai.R
49
+ import com.google.firebase.quickstart.ai.feature.text.Attachment
50
+ import com.google.firebase.quickstart.ai.feature.text.AttachmentsList
51
+ import kotlinx.coroutines.launch
30
52
import kotlinx.serialization.Serializable
31
53
32
54
@Serializable
@@ -40,9 +62,35 @@ fun ImagenScreen(
40
62
val errorMessage by imagenViewModel.errorMessage.collectAsStateWithLifecycle()
41
63
val isLoading by imagenViewModel.isLoading.collectAsStateWithLifecycle()
42
64
val generatedImages by imagenViewModel.generatedBitmaps.collectAsStateWithLifecycle()
65
+ val attachedImage by imagenViewModel.attachedImage.collectAsStateWithLifecycle()
66
+ val context = LocalContext .current
67
+ val contentResolver = context.contentResolver
68
+ val scope = rememberCoroutineScope()
69
+ val openDocument = rememberLauncherForActivityResult(ActivityResultContracts .OpenDocument ()) { optionalUri: Uri ? ->
70
+ optionalUri?.let { uri ->
71
+ var fileName: String? = null
72
+ // Fetch file name and size
73
+ contentResolver.query(uri, null , null , null , null )?.use { cursor ->
74
+ val nameIndex = cursor.getColumnIndex(OpenableColumns .DISPLAY_NAME )
75
+ val sizeIndex = cursor.getColumnIndex(OpenableColumns .SIZE )
76
+ cursor.moveToFirst()
77
+ val humanReadableSize = Formatter .formatShortFileSize(
78
+ context, cursor.getLong(sizeIndex)
79
+ )
80
+ fileName = " ${cursor.getString(nameIndex)} ($humanReadableSize )"
81
+ }
82
+
83
+ contentResolver.openInputStream(uri)?.use { stream ->
84
+ val bytes = stream.readBytes()
85
+ scope.launch {
86
+ imagenViewModel.attachImage(bytes)
87
+ }
88
+ }
89
+ }
90
+ }
43
91
44
92
Column (
45
- modifier = Modifier
93
+ modifier = Modifier .verticalScroll(rememberScrollState())
46
94
) {
47
95
ElevatedCard (
48
96
modifier = Modifier
@@ -59,18 +107,49 @@ fun ImagenScreen(
59
107
.padding(16 .dp)
60
108
.fillMaxWidth()
61
109
)
62
- TextButton (
63
- onClick = {
64
- if (imagenPrompt.isNotBlank()) {
65
- imagenViewModel.generateImages(imagenPrompt)
66
- }
67
- },
68
- modifier = Modifier
69
- .padding(end = 16 .dp, bottom = 16 .dp)
70
- .align(Alignment .End )
71
- ) {
72
- Text (" Generate" )
110
+ if (imagenViewModel.selectionOptions.isNotEmpty()) {
111
+ DropDownMenu (imagenViewModel.selectionOptions) { imagenViewModel.selectOption(it) }
73
112
}
113
+ val attachmentsList = buildList {
114
+ if (imagenViewModel.additionalImage != null ) {
115
+ add(
116
+ Attachment (
117
+ imagenViewModel.imageLabels.getOrElse(0 ) { " " },
118
+ imagenViewModel.additionalImage
119
+ )
120
+ )
121
+ }
122
+ if (attachedImage != null ) {
123
+ add(Attachment (imagenViewModel.imageLabels.getOrElse(1 ) { " " }, attachedImage))
124
+ }
125
+ }
126
+
127
+ if (imagenViewModel.includeAttach && attachmentsList.isNotEmpty()) {
128
+ AttachmentsList (attachmentsList)
129
+ }
130
+ Row () {
131
+ if (imagenViewModel.includeAttach) {
132
+ TextButton (
133
+ onClick = {
134
+ openDocument.launch(arrayOf(" image/*" ))
135
+ },
136
+ modifier = Modifier
137
+ .padding(end = 16 .dp, bottom = 16 .dp)
138
+ ) { Text (" Attach" ) }
139
+ }
140
+ TextButton (
141
+ onClick = {
142
+ if (imagenViewModel.allowEmptyPrompt || imagenPrompt.isNotBlank()) {
143
+ imagenViewModel.generateImages(imagenPrompt)
144
+ }
145
+ },
146
+ modifier = Modifier
147
+ .padding(end = 16 .dp, bottom = 16 .dp)
148
+ ) {
149
+ Text (" Generate" )
150
+ }
151
+ }
152
+
74
153
}
75
154
76
155
if (isLoading) {
@@ -100,9 +179,11 @@ fun ImagenScreen(
100
179
)
101
180
}
102
181
}
103
- LazyVerticalGrid (
104
- columns = GridCells .Fixed (2 ),
105
- modifier = Modifier .padding(16 .dp)
182
+ LazyHorizontalGrid (
183
+ rows = GridCells .Fixed (2 ),
184
+ modifier = Modifier
185
+ .padding(16 .dp)
186
+ .height(500 .dp)
106
187
) {
107
188
items(generatedImages) { image ->
108
189
Card (
@@ -120,3 +201,57 @@ fun ImagenScreen(
120
201
}
121
202
}
122
203
}
204
+
205
+ @Composable
206
+ fun DropDownMenu (items : List <String >, onClick : (String ) -> Unit ) {
207
+
208
+ val isDropDownExpanded = remember {
209
+ mutableStateOf(false )
210
+ }
211
+
212
+ val itemPosition = remember {
213
+ mutableIntStateOf(0 )
214
+ }
215
+
216
+
217
+ Column (
218
+ horizontalAlignment = Alignment .Start ,
219
+ verticalArrangement = Arrangement .Top ,
220
+ modifier = Modifier .padding(horizontal = 10 .dp)
221
+ ) {
222
+
223
+ Box {
224
+ Row (
225
+ horizontalArrangement = Arrangement .Start ,
226
+ verticalAlignment = Alignment .Top ,
227
+ modifier = Modifier .clickable {
228
+ isDropDownExpanded.value = true
229
+ }
230
+ ) {
231
+ Text (text = items[itemPosition.intValue])
232
+ Image (
233
+ painter = painterResource(id = R .drawable.round_arrow_drop_down_24),
234
+ contentDescription = " Dropdown Icon"
235
+ )
236
+ }
237
+ DropdownMenu (
238
+ expanded = isDropDownExpanded.value,
239
+ onDismissRequest = {
240
+ isDropDownExpanded.value = false
241
+ }) {
242
+ items.forEachIndexed { index, item ->
243
+ DropdownMenuItem (
244
+ text = {
245
+ Text (text = item)
246
+ },
247
+ onClick = {
248
+ isDropDownExpanded.value = false
249
+ itemPosition.intValue = index
250
+ onClick(item)
251
+ })
252
+ }
253
+ }
254
+ }
255
+
256
+ }
257
+ }
0 commit comments