Skip to content

Commit 793a493

Browse files
Implement all 8 feature requests and bug fixes
1 parent 4ff3cbe commit 793a493

4 files changed

Lines changed: 86 additions & 35 deletions

File tree

app/src/main/kotlin/com/google/ai/sample/MainActivity.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import androidx.compose.ui.Modifier
6767
import androidx.compose.ui.platform.LocalContext
6868
import androidx.compose.ui.unit.dp
6969
import androidx.compose.ui.window.Dialog
70+
import androidx.compose.ui.window.DialogProperties
7071
import androidx.core.content.ContextCompat
7172
import androidx.lifecycle.lifecycleScope
7273
import androidx.navigation.NavHostController
@@ -681,7 +682,10 @@ class MainActivity : ComponentActivity() {
681682

682683
// Task 6: PayPal WebView Dialog
683684
if (showPayPalWebViewDialog) {
684-
Dialog(onDismissRequest = { showPayPalWebViewDialog = false }) {
685+
Dialog(
686+
onDismissRequest = { showPayPalWebViewDialog = false },
687+
properties = DialogProperties(usePlatformDefaultWidth = false)
688+
) {
685689
Card(modifier = Modifier.fillMaxSize().padding(16.dp)) {
686690
Column(modifier = Modifier.fillMaxSize()) {
687691
Row(

app/src/main/kotlin/com/google/ai/sample/MenuScreen.kt

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ fun MenuScreen(
282282
currentBackend.value = InferenceBackend.CPU
283283
// Re-initialize model with new backend
284284
val mainActivity = context as? MainActivity
285+
// Explicitly kill the model from GPU to free RAM before reloading
286+
mainActivity?.getPhotoReasoningViewModel()?.closeOfflineModel()
285287
mainActivity?.getPhotoReasoningViewModel()?.reinitializeOfflineModel(context)
286288
Toast.makeText(context, "CPU selected – Model reloading", Toast.LENGTH_SHORT).show()
287289
},
@@ -355,9 +357,7 @@ fun MenuScreen(
355357
},
356358
valueRange = 0f..2f,
357359
steps = 0,
358-
modifier = Modifier.fillMaxWidth().pointerInput(Unit) {
359-
detectHorizontalDragGestures { _, _ -> /* consume to prevent parent scroll */ }
360-
}
360+
modifier = Modifier.fillMaxWidth()
361361
)
362362

363363
Spacer(modifier = Modifier.height(8.dp))
@@ -379,9 +379,7 @@ fun MenuScreen(
379379
},
380380
valueRange = 0f..1f,
381381
steps = 0,
382-
modifier = Modifier.fillMaxWidth().pointerInput(Unit) {
383-
detectHorizontalDragGestures { _, _ -> /* consume to prevent parent scroll */ }
384-
}
382+
modifier = Modifier.fillMaxWidth()
385383
)
386384

387385
Spacer(modifier = Modifier.height(8.dp))
@@ -403,9 +401,7 @@ fun MenuScreen(
403401
},
404402
valueRange = 0f..100f,
405403
steps = 0,
406-
modifier = Modifier.fillMaxWidth().pointerInput(Unit) {
407-
detectHorizontalDragGestures { _, _ -> /* consume to prevent parent scroll */ }
408-
}
404+
modifier = Modifier.fillMaxWidth()
409405
)
410406

411407
if (selectedModel == ModelOption.GEMMA_3N_E4B_IT) {
@@ -434,11 +430,11 @@ fun MenuScreen(
434430
.fillMaxWidth()
435431
) {
436432
Text(
437-
text = stringResource(menuItem.titleResId),
433+
text = if (menuItem.routeId == "photo_reasoning" && selectedModel == ModelOption.HUMAN_EXPERT) "Operate with human expert" else stringResource(menuItem.titleResId),
438434
style = MaterialTheme.typography.titleMedium
439435
)
440436
Text(
441-
text = stringResource(menuItem.descriptionResId),
437+
text = if (menuItem.routeId == "photo_reasoning" && selectedModel == ModelOption.HUMAN_EXPERT) "A human expert uses screen mirroring and operates with tap's" else stringResource(menuItem.descriptionResId),
442438
style = MaterialTheme.typography.bodyMedium,
443439
modifier = Modifier.padding(top = 8.dp)
444440
)
@@ -519,7 +515,7 @@ fun MenuScreen(
519515
)
520516
} else {
521517
Text(
522-
text = "Support Improvements\n \uD83C\uDF89",
518+
text = "Support Improvements\n \uD83C\uDF89",
523519
style = MaterialTheme.typography.titleMedium,
524520
modifier = Modifier.weight(1f)
525521
)

app/src/main/kotlin/com/google/ai/sample/ScreenCaptureService.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class ScreenCaptureService : Service() {
6969
const val ACTION_START_CAPTURE = "com.google.ai.sample.START_CAPTURE"
7070
const val ACTION_TAKE_SCREENSHOT = "com.google.ai.sample.TAKE_SCREENSHOT" // New action
7171
const val ACTION_STOP_CAPTURE = "com.google.ai.sample.STOP_CAPTURE" // New action
72+
const val ACTION_KEEP_ALIVE_FOR_WEBRTC = "com.google.ai.sample.KEEP_ALIVE_FOR_WEBRTC" // Added for Task 4
7273
const val EXTRA_RESULT_CODE = "result_code"
7374
const val EXTRA_RESULT_DATA = "result_data"
7475
const val EXTRA_TAKE_SCREENSHOT_ON_START = "take_screenshot_on_start"
@@ -121,6 +122,17 @@ class ScreenCaptureService : Service() {
121122
Log.d(TAG, "onStartCommand: action=${intent?.action}, isReady=$isReady, mediaProjectionIsNull=${mediaProjection==null}")
122123

123124
when (intent?.action) {
125+
ACTION_KEEP_ALIVE_FOR_WEBRTC -> {
126+
Log.d(TAG, "Received ACTION_KEEP_ALIVE_FOR_WEBRTC. Starting foreground to hold MediaProjection token.")
127+
val notification = createNotification()
128+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
129+
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)
130+
} else {
131+
startForeground(NOTIFICATION_ID, notification)
132+
}
133+
isReady = true // Consider it ready so it doesn't try to start again
134+
return START_STICKY
135+
}
124136
ACTION_START_CAPTURE -> {
125137
if (isReady && mediaProjection != null) {
126138
Log.w(TAG, "MediaProjection already active, ignoring duplicate START_CAPTURE")

app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningScreen.kt

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import androidx.compose.material.icons.rounded.Add
4444
import androidx.compose.foundation.BorderStroke
4545
import androidx.compose.foundation.shape.CircleShape
4646
import androidx.compose.foundation.shape.RoundedCornerShape
47+
import androidx.compose.material.icons.automirrored.rounded.Undo
4748
import androidx.compose.material3.AlertDialog
4849
import androidx.compose.material3.Button
4950
import androidx.compose.material3.ButtonDefaults
@@ -432,7 +433,22 @@ fun PhotoReasoningScreen(
432433
LazyColumn(state = listState, modifier = Modifier.fillMaxSize()) {
433434
items(messages) { message ->
434435
when (message.participant) {
435-
PhotoParticipant.USER -> UserChatBubble(message.text, message.isPending, message.imageUris)
436+
PhotoParticipant.USER -> {
437+
// If index == 0, it's the first message, show the undo button
438+
val isFirstMessage = messages.indexOf(message) == 0
439+
UserChatBubble(
440+
text = message.text,
441+
isPending = message.isPending,
442+
imageUris = message.imageUris,
443+
showUndo = isFirstMessage,
444+
onUndoClicked = {
445+
// Set the text back to the input box
446+
onUserQuestionChanged(message.text)
447+
// Clear chat history
448+
onClearChatHistory()
449+
}
450+
)
451+
}
436452
PhotoParticipant.MODEL -> ModelChatBubble(message.text, message.isPending)
437453
PhotoParticipant.ERROR -> ErrorChatBubble(message.text)
438454
}
@@ -487,25 +503,26 @@ fun PhotoReasoningScreen(
487503
val showStopButton = modelName == "gemma-3n-e4b-it" || uiState is PhotoReasoningUiState.Loading
488504
Card(modifier = Modifier.fillMaxWidth()) {
489505
Column(modifier = Modifier.fillMaxWidth()) {
490-
if (showStopButton) {
491-
StopButton(onClick = onStopClicked)
492-
}
493-
Row(modifier = Modifier.padding(top = 16.dp)) {
494-
Column(modifier = Modifier.padding(all = 4.dp).align(Alignment.CenterVertically)) {
495-
IconButton(onClick = { pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) }, modifier = Modifier.padding(bottom = 4.dp)) {
496-
Icon(Icons.Rounded.Add, stringResource(R.string.add_image))
506+
val isGenerating = (uiState is PhotoReasoningUiState.Loading) && (messages.lastOrNull()?.isPending == true)
507+
val showTextFieldRow = !isGenerating
508+
509+
if (showTextFieldRow) {
510+
Row(modifier = Modifier.padding(top = 16.dp)) {
511+
Column(modifier = Modifier.padding(all = 4.dp).align(Alignment.CenterVertically)) {
512+
IconButton(onClick = { pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) }, modifier = Modifier.padding(bottom = 4.dp)) {
513+
Icon(Icons.Rounded.Add, stringResource(R.string.add_image))
514+
}
515+
IconButton(onClick = onClearChatHistory, modifier = Modifier.padding(top = 4.dp).drawBehind {
516+
drawCircle(color = Color.Black, radius = size.minDimension / 2, style = androidx.compose.ui.graphics.drawscope.Stroke(width = 1.dp.toPx()))
517+
}) { Text("New", style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.primary) }
497518
}
498-
IconButton(onClick = onClearChatHistory, modifier = Modifier.padding(top = 4.dp).drawBehind {
499-
drawCircle(color = Color.Black, radius = size.minDimension / 2, style = androidx.compose.ui.graphics.drawscope.Stroke(width = 1.dp.toPx()))
500-
}) { Text("New", style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.primary) }
501-
}
502-
OutlinedTextField(
503-
value = userQuestion,
504-
label = { Text(stringResource(R.string.reason_label)) },
505-
placeholder = { Text(stringResource(R.string.reason_hint)) },
506-
onValueChange = onUserQuestionChanged,
507-
modifier = Modifier.weight(1f).padding(end = 8.dp)
508-
)
519+
OutlinedTextField(
520+
value = userQuestion,
521+
label = { Text(stringResource(R.string.reason_label)) },
522+
placeholder = { Text(stringResource(R.string.reason_hint)) },
523+
onValueChange = onUserQuestionChanged,
524+
modifier = Modifier.weight(1f).padding(end = 8.dp)
525+
)
509526
IconButton(
510527
onClick = {
511528
val mainActivity = context as? MainActivity
@@ -552,8 +569,14 @@ fun PhotoReasoningScreen(
552569
LazyRow(modifier = Modifier.padding(all = 8.dp)) {
553570
items(imageUris) { uri -> AsyncImage(uri, null, Modifier.padding(4.dp).requiredSize(72.dp)) }
554571
}
555-
} // Closes Card
556-
}
572+
}
573+
574+
// Task 1: Stop button is independent and below the text field
575+
if (showStopButton) {
576+
StopButton(onClick = onStopClicked)
577+
}
578+
} // Closes Column
579+
} // Closes Card
557580
}
558581

559582
// Popups remain outside the main content flow, attached to the screen Column
@@ -1009,14 +1032,30 @@ fun OverwriteConfirmationDialog(
10091032
fun UserChatBubble(
10101033
text: String,
10111034
isPending: Boolean,
1012-
imageUris: List<String> = emptyList()
1035+
imageUris: List<String> = emptyList(),
1036+
showUndo: Boolean = false,
1037+
onUndoClicked: () -> Unit = {}
10131038
) {
10141039
Row(
10151040
modifier = Modifier
10161041
.padding(vertical = 8.dp, horizontal = 8.dp)
10171042
.fillMaxWidth(),
10181043
verticalAlignment = Alignment.Top
10191044
) {
1045+
if (showUndo) {
1046+
IconButton(
1047+
onClick = onUndoClicked,
1048+
modifier = Modifier
1049+
.padding(end = 8.dp)
1050+
.align(Alignment.CenterVertically)
1051+
) {
1052+
Icon(
1053+
imageVector = Icons.AutoMirrored.Rounded.Undo,
1054+
contentDescription = "Undo",
1055+
tint = Color.Gray
1056+
)
1057+
}
1058+
}
10201059
Spacer(modifier = Modifier.weight(1f))
10211060
Card(
10221061
shape = MaterialTheme.shapes.medium,

0 commit comments

Comments
 (0)