13
13
* See the License for the specific language governing permissions and
14
14
* limitations under the License.
15
15
*/
16
- package com.android.ai.samples.geminimultimodal
16
+ package com.android.ai.samples.geminimultimodal.ui
17
17
18
18
import android.annotation.SuppressLint
19
19
import android.content.Context
@@ -24,12 +24,14 @@ import androidx.activity.compose.rememberLauncherForActivityResult
24
24
import androidx.activity.result.contract.ActivityResultContracts.TakePicturePreview
25
25
import androidx.compose.foundation.Image
26
26
import androidx.compose.foundation.layout.Arrangement
27
+ import androidx.compose.foundation.layout.Box
27
28
import androidx.compose.foundation.layout.Column
28
29
import androidx.compose.foundation.layout.Row
29
30
import androidx.compose.foundation.layout.Spacer
30
31
import androidx.compose.foundation.layout.fillMaxSize
31
32
import androidx.compose.foundation.layout.fillMaxWidth
32
33
import androidx.compose.foundation.layout.height
34
+ import androidx.compose.foundation.layout.imePadding
33
35
import androidx.compose.foundation.layout.padding
34
36
import androidx.compose.foundation.layout.size
35
37
import androidx.compose.foundation.rememberScrollState
@@ -40,6 +42,7 @@ import androidx.compose.material.icons.filled.Code
40
42
import androidx.compose.material.icons.filled.SmartToy
41
43
import androidx.compose.material3.Button
42
44
import androidx.compose.material3.Card
45
+ import androidx.compose.material3.CircularProgressIndicator
43
46
import androidx.compose.material3.ExperimentalMaterial3Api
44
47
import androidx.compose.material3.Icon
45
48
import androidx.compose.material3.MaterialTheme
@@ -49,12 +52,11 @@ import androidx.compose.material3.TextField
49
52
import androidx.compose.material3.TopAppBar
50
53
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
51
54
import androidx.compose.runtime.Composable
52
- import androidx.compose.runtime.collectAsState
53
55
import androidx.compose.runtime.getValue
54
- import androidx.compose.runtime.livedata.observeAsState
55
56
import androidx.compose.runtime.mutableStateOf
56
57
import androidx.compose.runtime.remember
57
58
import androidx.compose.runtime.setValue
59
+ import androidx.compose.ui.Alignment
58
60
import androidx.compose.ui.Modifier
59
61
import androidx.compose.ui.graphics.asImageBitmap
60
62
import androidx.compose.ui.layout.ContentScale
@@ -63,16 +65,16 @@ import androidx.compose.ui.res.stringResource
63
65
import androidx.compose.ui.unit.dp
64
66
import androidx.compose.ui.unit.sp
65
67
import androidx.hilt.navigation.compose.hiltViewModel
68
+ import androidx.lifecycle.compose.collectAsStateWithLifecycle
69
+ import com.android.ai.samples.geminimultimodal.R
66
70
67
71
@OptIn(ExperimentalMaterial3Api ::class )
68
72
@SuppressLint(" UnusedMaterial3ScaffoldPaddingParameter" )
69
73
@Composable
70
74
fun GeminiMultimodalScreen (viewModel : GeminiMultimodalViewModel = hiltViewModel()) {
71
75
val context = LocalContext .current
72
76
var bitmap by remember { mutableStateOf<Bitmap ?>(null ) }
73
- val textResponse by viewModel.textGenerated.collectAsState()
74
- val isGenerating by viewModel.isGenerating.observeAsState(false )
75
- var pictureAvailable by remember { mutableStateOf(false ) }
77
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
76
78
77
79
val promptPlaceHolder = stringResource(id = R .string.geminimultimodal_prompt_placeholder)
78
80
var editTextValue by remember {
@@ -83,7 +85,6 @@ fun GeminiMultimodalScreen(viewModel: GeminiMultimodalViewModel = hiltViewModel(
83
85
val cameraLauncher = rememberLauncherForActivityResult(TakePicturePreview ()) { result ->
84
86
result?.let {
85
87
bitmap = it
86
- pictureAvailable = true
87
88
}
88
89
}
89
90
@@ -106,6 +107,7 @@ fun GeminiMultimodalScreen(viewModel: GeminiMultimodalViewModel = hiltViewModel(
106
107
Column (
107
108
Modifier
108
109
.padding(12 .dp)
110
+ .imePadding()
109
111
.verticalScroll(rememberScrollState())
110
112
.padding(innerPadding),
111
113
) {
@@ -116,13 +118,24 @@ fun GeminiMultimodalScreen(viewModel: GeminiMultimodalViewModel = hiltViewModel(
116
118
height = 450 .dp,
117
119
),
118
120
) {
119
- bitmap?.let {
121
+ val currentBitmap = bitmap
122
+ if (currentBitmap != null ) {
120
123
Image (
121
- bitmap = it .asImageBitmap(),
124
+ bitmap = currentBitmap .asImageBitmap(),
122
125
contentDescription = " Picture" ,
123
126
contentScale = ContentScale .Crop ,
124
127
modifier = Modifier .fillMaxSize(),
125
128
)
129
+ } else {
130
+ Box (
131
+ modifier = Modifier .fillMaxSize(),
132
+ contentAlignment = Alignment .Center ,
133
+ ) {
134
+ Text (
135
+ text = stringResource(id = R .string.geminimultimodal_take_a_picture),
136
+ style = MaterialTheme .typography.bodySmall,
137
+ )
138
+ }
126
139
}
127
140
}
128
141
Spacer (modifier = Modifier .height(6 .dp))
@@ -144,11 +157,12 @@ fun GeminiMultimodalScreen(viewModel: GeminiMultimodalViewModel = hiltViewModel(
144
157
Spacer (modifier = Modifier .height(8 .dp))
145
158
Button (
146
159
onClick = {
147
- if (bitmap != null ) {
148
- viewModel.generate(bitmap!! , editTextValue)
160
+ val currentBitmap = bitmap
161
+ if (currentBitmap != null ) {
162
+ viewModel.generate(currentBitmap, editTextValue)
149
163
}
150
164
},
151
- enabled = ! isGenerating && pictureAvailable ,
165
+ enabled = uiState !is GeminiMultimodalUiState . Loading && bitmap != null ,
152
166
) {
153
167
Icon (Icons .Default .SmartToy , contentDescription = " Robot" )
154
168
Text (modifier = Modifier .padding(start = 8 .dp), text = " Generate" )
@@ -158,14 +172,26 @@ fun GeminiMultimodalScreen(viewModel: GeminiMultimodalViewModel = hiltViewModel(
158
172
.height(24 .dp),
159
173
)
160
174
161
- if (isGenerating) {
162
- Text (
163
- text = stringResource(R .string.geminimultimodal_generating),
164
- )
165
- } else {
166
- Text (
167
- text = textResponse,
168
- )
175
+ when (uiState) {
176
+ is GeminiMultimodalUiState .Initial -> {
177
+ Text (
178
+ text = stringResource(id = R .string.geminimultimodal_generation_placeholder),
179
+ style = MaterialTheme .typography.bodySmall,
180
+ )
181
+ }
182
+ is GeminiMultimodalUiState .Loading -> {
183
+ CircularProgressIndicator ()
184
+ }
185
+ is GeminiMultimodalUiState .Success -> {
186
+ Text (
187
+ text = (uiState as GeminiMultimodalUiState .Success ).generatedText,
188
+ )
189
+ }
190
+ is GeminiMultimodalUiState .Error -> {
191
+ Text (
192
+ text = (uiState as GeminiMultimodalUiState .Error ).errorMessage ? : stringResource(R .string.unknown_error),
193
+ )
194
+ }
169
195
}
170
196
}
171
197
}
0 commit comments