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.magicselfie
16
+ package com.android.ai.samples.magicselfie.ui
17
17
18
18
import android.annotation.SuppressLint
19
19
import android.app.Activity
20
- import android.content.Context
21
20
import android.content.Intent
22
21
import android.graphics.Bitmap
23
- import android.graphics.Matrix
24
- import android.media.ExifInterface
25
- import android.net.Uri
26
22
import android.provider.MediaStore
27
23
import androidx.activity.compose.rememberLauncherForActivityResult
28
24
import androidx.activity.result.ActivityResult
@@ -35,13 +31,13 @@ import androidx.compose.foundation.layout.Spacer
35
31
import androidx.compose.foundation.layout.fillMaxSize
36
32
import androidx.compose.foundation.layout.fillMaxWidth
37
33
import androidx.compose.foundation.layout.height
34
+ import androidx.compose.foundation.layout.imePadding
38
35
import androidx.compose.foundation.layout.padding
39
36
import androidx.compose.foundation.layout.size
40
37
import androidx.compose.foundation.rememberScrollState
41
38
import androidx.compose.foundation.verticalScroll
42
39
import androidx.compose.material.icons.Icons
43
40
import androidx.compose.material.icons.filled.CameraAlt
44
- import androidx.compose.material.icons.filled.Code
45
41
import androidx.compose.material.icons.filled.SmartToy
46
42
import androidx.compose.material3.Button
47
43
import androidx.compose.material3.Card
@@ -58,7 +54,6 @@ import androidx.compose.material3.rememberTopAppBarState
58
54
import androidx.compose.runtime.Composable
59
55
import androidx.compose.runtime.collectAsState
60
56
import androidx.compose.runtime.getValue
61
- import androidx.compose.runtime.livedata.observeAsState
62
57
import androidx.compose.runtime.mutableStateOf
63
58
import androidx.compose.runtime.remember
64
59
import androidx.compose.runtime.setValue
@@ -69,16 +64,17 @@ import androidx.compose.ui.layout.ContentScale
69
64
import androidx.compose.ui.platform.LocalContext
70
65
import androidx.compose.ui.res.stringResource
71
66
import androidx.compose.ui.unit.dp
72
- import androidx.compose.ui.unit.sp
73
67
import androidx.core.content.FileProvider
74
68
import androidx.hilt.navigation.compose.hiltViewModel
69
+ import com.android.ai.samples.magicselfie.R
75
70
import java.io.File
76
71
77
72
@OptIn(ExperimentalMaterial3Api ::class )
78
73
@SuppressLint(" UnusedMaterial3ScaffoldPaddingParameter" )
79
74
@Composable
80
75
fun MagicSelfieScreen (viewModel : MagicSelfieViewModel = hiltViewModel()) {
81
76
val context = LocalContext .current
77
+ val uiState by viewModel.uiState.collectAsState()
82
78
83
79
val topAppBarState = rememberTopAppBarState()
84
80
val scrollBehavior = TopAppBarDefaults .pinnedScrollBehavior(topAppBarState)
@@ -95,8 +91,6 @@ fun MagicSelfieScreen(viewModel: MagicSelfieViewModel = hiltViewModel()) {
95
91
cameraIntent.addFlags(Intent .FLAG_GRANT_READ_URI_PERMISSION )
96
92
97
93
var selfieBitmap by remember { mutableStateOf<Bitmap ?>(null ) }
98
- val progress by viewModel.progress.observeAsState(null )
99
- val generatedBitmap by viewModel.foregroundBitmap.collectAsState()
100
94
var editTextValue by remember { mutableStateOf(" A very scenic view from the edge of the grand canyon" ) }
101
95
102
96
val resultLauncher =
@@ -132,6 +126,7 @@ fun MagicSelfieScreen(viewModel: MagicSelfieViewModel = hiltViewModel()) {
132
126
Modifier
133
127
.padding(12 .dp)
134
128
.padding(innerPadding)
129
+ .imePadding()
135
130
.verticalScroll(rememberScrollState()),
136
131
) {
137
132
Card (
@@ -141,12 +136,12 @@ fun MagicSelfieScreen(viewModel: MagicSelfieViewModel = hiltViewModel()) {
141
136
height = 450 .dp,
142
137
),
143
138
) {
144
-
145
- if (generatedBitmap != null ) {
139
+ if (uiState is MagicSelfieUiState . Success ) {
140
+ val successState = uiState as MagicSelfieUiState . Success
146
141
Image (
147
- bitmap = generatedBitmap !! .asImageBitmap(),
142
+ bitmap = successState.bitmap .asImageBitmap(),
148
143
contentDescription = " Picture" ,
149
- contentScale = ContentScale .Fit ,
144
+ contentScale = ContentScale .Crop ,
150
145
modifier = Modifier .fillMaxSize(),
151
146
)
152
147
} else if (selfieBitmap != null ) {
@@ -183,74 +178,43 @@ fun MagicSelfieScreen(viewModel: MagicSelfieViewModel = hiltViewModel()) {
183
178
viewModel.createMagicSelfie(selfieBitmap!! , editTextValue)
184
179
}
185
180
},
186
- enabled = progress == null ,
181
+ enabled = (uiState !is MagicSelfieUiState .RemovingBackground ) &&
182
+ (uiState !is MagicSelfieUiState .GeneratingBackground ),
187
183
) {
188
184
Icon (Icons .Default .SmartToy , contentDescription = " Robot" )
189
185
Text (modifier = Modifier .padding(start = 8 .dp), text = " Generate" )
190
186
}
191
187
192
- if (progress != null ) {
188
+ if (uiState is MagicSelfieUiState . RemovingBackground ) {
193
189
Spacer (
194
190
modifier = Modifier
195
191
.height(30 .dp)
196
192
.padding(12 .dp),
197
193
)
198
194
Text (
199
- text = progress!! ,
195
+ text = stringResource(R .string.removing_background),
196
+ )
197
+ } else if (uiState is MagicSelfieUiState .GeneratingBackground ) {
198
+ Spacer (
199
+ modifier = Modifier
200
+ .height(30 .dp)
201
+ .padding(12 .dp),
202
+ )
203
+ Text (
204
+ text = stringResource(R .string.generating_new_background),
205
+ )
206
+ } else if (uiState is MagicSelfieUiState .Error ) {
207
+ val errorState = uiState as MagicSelfieUiState .Error
208
+ Spacer (
209
+ modifier = Modifier
210
+ .height(30 .dp)
211
+ .padding(12 .dp),
212
+ )
213
+ Text (
214
+ text = errorState.message ? : stringResource(R .string.unknown_error),
215
+ color = MaterialTheme .colorScheme.error,
200
216
)
201
217
}
202
218
}
203
219
}
204
220
}
205
-
206
- fun rotateImageIfRequired (imageFile : File , bitmap : Bitmap ): Bitmap {
207
- val ei = ExifInterface (imageFile.absolutePath)
208
- val orientation = ei.getAttributeInt(
209
- ExifInterface .TAG_ORIENTATION ,
210
- ExifInterface .ORIENTATION_NORMAL ,
211
- )
212
-
213
- return when (orientation) {
214
- ExifInterface .ORIENTATION_ROTATE_90 -> rotateImage(bitmap, 90f )
215
- ExifInterface .ORIENTATION_ROTATE_180 -> rotateImage(bitmap, 180f )
216
- ExifInterface .ORIENTATION_ROTATE_270 -> rotateImage(bitmap, 270f )
217
- ExifInterface .ORIENTATION_FLIP_HORIZONTAL -> flipImage(bitmap, true , false )
218
- ExifInterface .ORIENTATION_FLIP_VERTICAL -> flipImage(bitmap, false , true )
219
- ExifInterface .ORIENTATION_TRANSPOSE -> flipImage(rotateImage(bitmap, 90f ), true , false )
220
- ExifInterface .ORIENTATION_TRANSVERSE -> flipImage(rotateImage(bitmap, 270f ), true , false )
221
- else -> bitmap
222
- }
223
- }
224
-
225
- fun rotateImage (bitmap : Bitmap , degrees : Float ): Bitmap {
226
- val matrix = Matrix ()
227
- matrix.postRotate(degrees)
228
- return Bitmap .createBitmap(bitmap, 0 , 0 , bitmap.width, bitmap.height, matrix, true )
229
- }
230
-
231
- fun flipImage (bitmap : Bitmap , horizontal : Boolean , vertical : Boolean ): Bitmap {
232
- val matrix = Matrix ()
233
- val scaleX = if (horizontal) - 1f else 1f
234
- val scaleY = if (vertical) - 1f else 1f
235
- matrix.setScale(scaleX, scaleY)
236
- return Bitmap .createBitmap(bitmap, 0 , 0 , bitmap.width, bitmap.height, matrix, true )
237
- }
238
-
239
- @Composable
240
- fun SeeCodeButton (context : Context ) {
241
- val githubLink = " https://github.com/android/ai-samples/tree/main/ai-catalog/samples/magic-selfie"
242
- Button (
243
- onClick = {
244
- val intent = Intent (Intent .ACTION_VIEW , Uri .parse(githubLink))
245
- context.startActivity(intent)
246
- },
247
- modifier = Modifier .padding(end = 8 .dp),
248
- ) {
249
- Icon (Icons .Filled .Code , contentDescription = " See code" )
250
- Text (
251
- modifier = Modifier .padding(start = 8 .dp),
252
- fontSize = 12 .sp,
253
- text = stringResource(R .string.see_code),
254
- )
255
- }
256
- }
0 commit comments