15
15
*/
16
16
package com.android.ai.samples.imagen
17
17
18
- import android.content.Context
19
18
import android.content.Intent
20
- import android.net.Uri
21
19
import androidx.compose.foundation.Image
20
+ import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
22
21
import androidx.compose.foundation.layout.Column
23
- import androidx.compose.foundation.layout.Row
24
22
import androidx.compose.foundation.layout.Spacer
23
+ import androidx.compose.foundation.layout.WindowInsets
24
+ import androidx.compose.foundation.layout.aspectRatio
25
25
import androidx.compose.foundation.layout.fillMaxSize
26
26
import androidx.compose.foundation.layout.fillMaxWidth
27
27
import androidx.compose.foundation.layout.height
28
+ import androidx.compose.foundation.layout.ime
28
29
import androidx.compose.foundation.layout.padding
29
30
import androidx.compose.foundation.layout.size
31
+ import androidx.compose.foundation.layout.windowInsetsBottomHeight
30
32
import androidx.compose.foundation.layout.wrapContentSize
31
33
import androidx.compose.foundation.rememberScrollState
34
+ import androidx.compose.foundation.text.KeyboardActions
35
+ import androidx.compose.foundation.text.KeyboardOptions
32
36
import androidx.compose.foundation.verticalScroll
33
37
import androidx.compose.material.icons.Icons
34
38
import androidx.compose.material.icons.filled.Code
35
39
import androidx.compose.material.icons.filled.SmartToy
36
40
import androidx.compose.material3.Button
41
+ import androidx.compose.material3.ButtonDefaults
37
42
import androidx.compose.material3.Card
38
43
import androidx.compose.material3.ExperimentalMaterial3Api
39
44
import androidx.compose.material3.Icon
@@ -44,32 +49,39 @@ import androidx.compose.material3.TextField
44
49
import androidx.compose.material3.TopAppBar
45
50
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
46
51
import androidx.compose.runtime.Composable
47
- import androidx.compose.runtime.collectAsState
48
52
import androidx.compose.runtime.getValue
49
- import androidx.compose.runtime.livedata.observeAsState
50
53
import androidx.compose.runtime.mutableStateOf
51
- import androidx.compose.runtime.remember
54
+ import androidx.compose.runtime.saveable.rememberSaveable
52
55
import androidx.compose.runtime.setValue
53
56
import androidx.compose.ui.Alignment
54
57
import androidx.compose.ui.Modifier
55
58
import androidx.compose.ui.graphics.asImageBitmap
56
59
import androidx.compose.ui.layout.ContentScale
57
60
import androidx.compose.ui.platform.LocalContext
58
61
import androidx.compose.ui.res.stringResource
62
+ import androidx.compose.ui.text.input.ImeAction
59
63
import androidx.compose.ui.text.style.TextAlign
64
+ import androidx.compose.ui.tooling.preview.Preview
60
65
import androidx.compose.ui.unit.dp
61
- import androidx.compose.ui.unit.sp
66
+ import androidx.core.net.toUri
62
67
import androidx.hilt.navigation.compose.hiltViewModel
68
+ import androidx.lifecycle.compose.collectAsStateWithLifecycle
63
69
64
70
@OptIn(ExperimentalMaterial3Api ::class )
65
71
@Composable
66
72
fun ImagenScreen (viewModel : ImagenViewModel = hiltViewModel()) {
67
- val context = LocalContext .current
68
- val isGenerating by viewModel.isGenerating.observeAsState(false )
69
- val generatedBitmap by viewModel.imageGenerated.collectAsState()
73
+ val uiState: ImagenUIState by viewModel.uiState.collectAsStateWithLifecycle()
70
74
71
- val placeholder = stringResource(R .string.placeholder_prompt)
72
- var editTextValue by remember { mutableStateOf(placeholder) }
75
+ ImagenScreen (
76
+ uiState = uiState,
77
+ onGenerateClick = viewModel::generateImage,
78
+ )
79
+ }
80
+
81
+ @Composable
82
+ @OptIn(ExperimentalMaterial3Api ::class )
83
+ private fun ImagenScreen (uiState : ImagenUIState , onGenerateClick : (String ) -> Unit ) {
84
+ val isGenerating = uiState is ImagenUIState .Loading
73
85
74
86
Scaffold (
75
87
modifier = Modifier ,
@@ -83,79 +95,173 @@ fun ImagenScreen(viewModel: ImagenViewModel = hiltViewModel()) {
83
95
Text (text = stringResource(R .string.title_image_generation_screen))
84
96
},
85
97
actions = {
86
- SeeCodeButton (context )
98
+ SeeCodeButton ()
87
99
},
88
100
)
89
101
},
90
102
) { innerPadding ->
91
103
Column (
92
104
Modifier
93
- .padding(12 .dp)
94
105
.verticalScroll(rememberScrollState())
106
+ .padding(16 .dp)
95
107
.padding(innerPadding),
96
108
) {
97
- Card (
98
- modifier = Modifier .size(
99
- width = 400 .dp,
100
- height = 400 .dp,
101
- ).align(Alignment .CenterHorizontally ),
102
- ) {
103
- generatedBitmap?.let {
104
- Image (
105
- bitmap = it.asImageBitmap(),
106
- contentDescription = " Picture" ,
107
- contentScale = ContentScale .Fit ,
108
- modifier = Modifier .fillMaxSize(),
109
- )
110
- }
111
- if (isGenerating) {
112
- Text (
113
- text = stringResource(R .string.generating_label),
114
- modifier = Modifier
115
- .fillMaxSize()
116
- .wrapContentSize(Alignment .Center ),
117
- textAlign = TextAlign .Center ,
118
- )
119
- }
120
- }
121
- Spacer (modifier = Modifier .height(24 .dp))
122
- TextField (
123
- value = editTextValue,
124
- onValueChange = { editTextValue = it },
125
- label = { Text (stringResource(R .string.prompt_label)) },
109
+ GeneratedContent (
110
+ uiState = uiState,
111
+ modifier = Modifier
112
+ .fillMaxWidth()
113
+ .aspectRatio(1f ),
114
+ )
115
+
116
+ Spacer (modifier = Modifier .height(16 .dp))
117
+
118
+ GenerationInput (
119
+ onGenerateClick = onGenerateClick,
120
+ enabled = ! isGenerating,
126
121
modifier = Modifier .fillMaxWidth(),
127
122
)
128
- Row {
129
- Button (
130
- modifier = Modifier .padding(vertical = 8 .dp),
131
- onClick = {
132
- viewModel.generateImage(editTextValue)
133
- },
134
- enabled = ! isGenerating,
135
- ) {
136
- Icon (Icons .Default .SmartToy , contentDescription = " Robot" )
137
- Text (modifier = Modifier .padding(start = 8 .dp), text = stringResource(R .string.generate_button))
138
- }
123
+
124
+ // Ensure the screen scrolls when the keyboard appears
125
+ Spacer (Modifier .windowInsetsBottomHeight(WindowInsets .ime))
126
+ }
127
+ }
128
+ }
129
+
130
+ @Composable
131
+ private fun GeneratedContent (uiState : ImagenUIState , modifier : Modifier = Modifier ) {
132
+ Card (
133
+ modifier = modifier,
134
+ ) {
135
+ when (uiState) {
136
+ ImagenUIState .Initial -> {
137
+ // no-op
138
+ }
139
+
140
+ ImagenUIState .Loading -> {
141
+ Text (
142
+ text = stringResource(R .string.generating_label),
143
+ modifier = Modifier
144
+ .fillMaxSize()
145
+ .wrapContentSize(Alignment .Center ),
146
+ textAlign = TextAlign .Center ,
147
+ )
148
+ }
149
+
150
+ is ImagenUIState .ImageGenerated -> {
151
+ Image (
152
+ bitmap = uiState.bitmap.asImageBitmap(),
153
+ contentDescription = uiState.contentDescription,
154
+ contentScale = ContentScale .Fit ,
155
+ modifier = Modifier .fillMaxSize(),
156
+ )
157
+ }
158
+
159
+ is ImagenUIState .Error -> {
160
+ Text (
161
+ text = uiState.message,
162
+ modifier = Modifier
163
+ .fillMaxSize()
164
+ .wrapContentSize(Alignment .Center ),
165
+ textAlign = TextAlign .Center ,
166
+ )
139
167
}
140
168
}
141
169
}
142
170
}
143
171
144
172
@Composable
145
- fun SeeCodeButton (context : Context ) {
173
+ private fun GenerationInput (onGenerateClick : (String ) -> Unit , enabled : Boolean , modifier : Modifier = Modifier ) {
174
+ val placeholder = stringResource(R .string.placeholder_prompt)
175
+ var textFieldValue by rememberSaveable { mutableStateOf(placeholder) }
176
+
177
+ Column (
178
+ verticalArrangement = spacedBy(8 .dp),
179
+ modifier = modifier,
180
+ ) {
181
+ TextField (
182
+ value = textFieldValue,
183
+ onValueChange = { textFieldValue = it },
184
+ label = { Text (stringResource(R .string.prompt_label)) },
185
+ modifier = Modifier .fillMaxWidth(),
186
+ enabled = enabled,
187
+ keyboardOptions = KeyboardOptions (imeAction = ImeAction .Send ),
188
+ keyboardActions = KeyboardActions (
189
+ onSend = {
190
+ onGenerateClick(textFieldValue)
191
+ },
192
+ ),
193
+ )
194
+ Button (
195
+ onClick = {
196
+ onGenerateClick(textFieldValue)
197
+ },
198
+ enabled = enabled,
199
+ contentPadding = ButtonDefaults .ButtonWithIconContentPadding ,
200
+ modifier = Modifier .fillMaxWidth(),
201
+ ) {
202
+ Icon (
203
+ Icons .Default .SmartToy ,
204
+ contentDescription = null ,
205
+ modifier = Modifier .size(ButtonDefaults .IconSize ),
206
+ )
207
+ Spacer (Modifier .size(ButtonDefaults .IconSpacing ))
208
+ Text (text = stringResource(R .string.generate_button))
209
+ }
210
+ }
211
+ }
212
+
213
+ @Composable
214
+ private fun SeeCodeButton () {
215
+ val context = LocalContext .current
146
216
val githubLink = " https://github.com/android/ai-samples/tree/main/ai-catalog/samples/imagen"
147
217
Button (
148
218
onClick = {
149
- val intent = Intent (Intent .ACTION_VIEW , Uri .parse(githubLink ))
219
+ val intent = Intent (Intent .ACTION_VIEW , githubLink.toUri( ))
150
220
context.startActivity(intent)
151
221
},
152
- modifier = Modifier .padding(end = 8 .dp) ,
222
+ contentPadding = ButtonDefaults . ButtonWithIconContentPadding ,
153
223
) {
154
- Icon (Icons .Filled .Code , contentDescription = " See code" )
224
+ Icon (Icons .Filled .Code , contentDescription = null )
225
+ Spacer (Modifier .size(ButtonDefaults .IconSpacing ))
155
226
Text (
156
- modifier = Modifier .padding(start = 8 .dp),
157
- fontSize = 12 .sp,
158
227
text = stringResource(R .string.see_code),
159
228
)
160
229
}
161
230
}
231
+
232
+ @Preview
233
+ @Composable
234
+ @OptIn(ExperimentalMaterial3Api ::class )
235
+ private fun ImagenScreenPreview () {
236
+ ImagenScreen (
237
+ uiState = ImagenUIState .Initial ,
238
+ onGenerateClick = {},
239
+ )
240
+ }
241
+
242
+ @Preview
243
+ @Composable
244
+ private fun GeneratedContentPreview () {
245
+ GeneratedContent (
246
+ uiState = ImagenUIState .Initial ,
247
+ modifier = Modifier .size(400 .dp),
248
+ )
249
+ }
250
+
251
+ @Preview
252
+ @Composable
253
+ private fun GeneratedContentLoadingPreview () {
254
+ GeneratedContent (
255
+ uiState = ImagenUIState .Loading ,
256
+ modifier = Modifier .size(400 .dp),
257
+ )
258
+ }
259
+
260
+ @Preview
261
+ @Composable
262
+ private fun GenerationInputPreview () {
263
+ GenerationInput (
264
+ onGenerateClick = {},
265
+ enabled = true ,
266
+ )
267
+ }
0 commit comments