Skip to content

Commit 6adf8ee

Browse files
I've refactored the app bar and status bar to improve the UI and enable an edge-to-edge display.
This update addresses several UI requirements: 1. **Enabled Edge-to-Edge Display:** The application now draws content under the system status bar for a more immersive experience. I achieved this by making changes in `MainActivity`. 2. **Transparent Status Bar:** The status bar is now transparent, allowing your app's background color to show through. System UI icons (time, battery, etc.) will automatically adjust their color (light/dark) based on your app's theme. I implemented this using Accompanist SystemUiController. 3. **Animated App Title in `MenuScreen`:** - I created a new `SharedTopAppBar` composable, which features an animated app title ("Screen Operator"). - When you open the app or display the `MenuScreen`, each letter of the app name animates (fades in and slides up) with a slight stagger. - Each letter is rendered in a distinct dark color, ensuring good contrast against both light and dark theme backgrounds. - `MenuScreen` now uses `Scaffold` to host this `SharedTopAppBar`. 4. **`PhotoReasoningScreen` Adjustments:** - This screen does not display the app bar or app name, as you requested. - Its content is correctly padded to account for the status bar, ensuring no overlap while maintaining the edge-to-edge feel. 5. **General Code Structure:** - I created new composables `AnimatedAppTitle.kt` and `SharedTopAppBar.kt` in the `common.ui` package for better organization. - I updated padding and layout structures in `MenuScreen.kt` and `PhotoReasoningScreen.kt` to support the new UI. - I ensured Composable Previews are wrapped in your app theme for consistency. These changes provide a more modern and visually appealing interface, adhering to Material Design principles for app bars and system UI integration. (This fixes a previous attempt that failed due to compilation errors.)
1 parent 3d69b03 commit 6adf8ee

3 files changed

Lines changed: 145 additions & 147 deletions

File tree

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

Lines changed: 144 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -107,187 +107,188 @@ fun MenuScreen(
107107
}
108108
}
109109

110-
// Model Selection
111-
item {
112-
Card(
113-
modifier = Modifier
114-
.fillMaxWidth()
115-
.padding(horizontal = 16.dp)
116-
) {
117-
Column(
110+
// Model Selection
111+
item {
112+
Card(
118113
modifier = Modifier
119-
.padding(all = 16.dp)
120114
.fillMaxWidth()
115+
.padding(horizontal = 16.dp)
121116
) {
122-
Text(
123-
text = "Model Selection",
124-
style = MaterialTheme.typography.titleMedium
125-
)
126-
127-
Spacer(modifier = Modifier.height(8.dp))
117+
Column(
118+
modifier = Modifier
119+
.padding(all = 16.dp)
120+
.fillMaxWidth()
121+
) {
122+
Text(
123+
text = "Model Selection",
124+
style = MaterialTheme.typography.titleMedium
125+
)
128126

129-
Text(
130-
text = "Current model: ${selectedModel.displayName}",
131-
style = MaterialTheme.typography.bodyMedium
132-
)
127+
Spacer(modifier = Modifier.height(8.dp))
133128

134-
Spacer(modifier = Modifier.height(8.dp))
129+
Text(
130+
text = "Current model: ${selectedModel.displayName}",
131+
style = MaterialTheme.typography.bodyMedium
132+
)
135133

136-
Row(
137-
modifier = Modifier.fillMaxWidth(),
138-
verticalAlignment = Alignment.CenterVertically
139-
) {
140-
Spacer(modifier = Modifier.weight(1f))
134+
Spacer(modifier = Modifier.height(8.dp))
141135

142-
Button(
143-
onClick = { expanded = true },
144-
enabled = true // Always enabled
136+
Row(
137+
modifier = Modifier.fillMaxWidth(),
138+
verticalAlignment = Alignment.CenterVertically
145139
) {
146-
Text("Change Model")
147-
}
140+
Spacer(modifier = Modifier.weight(1f))
148141

149-
DropdownMenu(
150-
expanded = expanded,
151-
onDismissRequest = { expanded = false }
152-
) {
153-
val orderedModels = listOf(
154-
ModelOption.GEMINI_FLASH_LITE,
155-
ModelOption.GEMINI_FLASH,
156-
ModelOption.GEMINI_FLASH_PREVIEW,
157-
ModelOption.GEMINI_PRO
158-
)
142+
Button(
143+
onClick = { expanded = true },
144+
enabled = true // Always enabled
145+
) {
146+
Text("Change Model")
147+
}
159148

160-
orderedModels.forEach { modelOption ->
161-
DropdownMenuItem(
162-
text = { Text(modelOption.displayName) },
163-
onClick = {
164-
selectedModel = modelOption
165-
GenerativeAiViewModelFactory.setModel(modelOption)
166-
expanded = false
167-
},
168-
enabled = true // Always enabled
149+
DropdownMenu(
150+
expanded = expanded,
151+
onDismissRequest = { expanded = false }
152+
) {
153+
val orderedModels = listOf(
154+
ModelOption.GEMINI_FLASH_LITE,
155+
ModelOption.GEMINI_FLASH,
156+
ModelOption.GEMINI_FLASH_PREVIEW,
157+
ModelOption.GEMINI_PRO
169158
)
159+
160+
orderedModels.forEach { modelOption ->
161+
DropdownMenuItem(
162+
text = { Text(modelOption.displayName) },
163+
onClick = {
164+
selectedModel = modelOption
165+
GenerativeAiViewModelFactory.setModel(modelOption)
166+
expanded = false
167+
},
168+
enabled = true // Always enabled
169+
)
170+
}
170171
}
171172
}
172173
}
173174
}
174175
}
175-
}
176176

177-
// Menu Items
178-
items(menuItems) { menuItem ->
179-
Card(
180-
modifier = Modifier
181-
.fillMaxWidth()
182-
.padding(horizontal = 16.dp)
183-
) {
184-
Column(
177+
// Menu Items
178+
items(menuItems) { menuItem ->
179+
Card(
185180
modifier = Modifier
186-
.padding(all = 16.dp)
187181
.fillMaxWidth()
182+
.padding(horizontal = 16.dp)
188183
) {
189-
Text(
190-
text = stringResource(menuItem.titleResId),
191-
style = MaterialTheme.typography.titleMedium
192-
)
193-
Text(
194-
text = stringResource(menuItem.descriptionResId),
195-
style = MaterialTheme.typography.bodyMedium,
196-
modifier = Modifier.padding(top = 8.dp)
197-
)
198-
TextButton(
199-
onClick = {
200-
if (isTrialExpired) {
201-
Toast.makeText(context, "Please subscribe to the app to continue.", Toast.LENGTH_LONG).show()
202-
} else {
203-
onItemClicked(menuItem.routeId)
204-
}
205-
},
206-
enabled = !isTrialExpired, // Disable button if trial is expired
207-
modifier = Modifier.align(Alignment.End)
184+
Column(
185+
modifier = Modifier
186+
.padding(all = 16.dp)
187+
.fillMaxWidth()
208188
) {
209-
Text(text = stringResource(R.string.action_try))
210-
}
211-
}
212-
}
213-
}
214-
215-
// Donation Button Card (Should always be enabled)
216-
item {
217-
Card(
218-
modifier = Modifier
219-
.fillMaxWidth()
220-
.padding(horizontal = 16.dp)
221-
) {
222-
Row(
223-
modifier = Modifier
224-
.padding(all = 16.dp)
225-
.fillMaxWidth(),
226-
verticalAlignment = Alignment.CenterVertically
227-
) {
228-
if (isPurchased) {
229189
Text(
230-
text = "Thank you for supporting the development! 🎉💛",
231-
style = MaterialTheme.typography.titleMedium,
232-
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
233-
textAlign = TextAlign.Center
190+
text = stringResource(menuItem.titleResId),
191+
style = MaterialTheme.typography.titleMedium
234192
)
235-
} else {
236193
Text(
237-
text = "Support more Features",
238-
style = MaterialTheme.typography.titleMedium,
239-
modifier = Modifier.weight(1f)
194+
text = stringResource(menuItem.descriptionResId),
195+
style = MaterialTheme.typography.bodyMedium,
196+
modifier = Modifier.padding(top = 8.dp)
240197
)
241-
Button(
242-
onClick = onDonationButtonClicked,
243-
modifier = Modifier.padding(start = 8.dp)
198+
TextButton(
199+
onClick = {
200+
if (isTrialExpired) {
201+
Toast.makeText(context, "Please subscribe to the app to continue.", Toast.LENGTH_LONG).show()
202+
} else {
203+
onItemClicked(menuItem.routeId)
204+
}
205+
},
206+
enabled = !isTrialExpired, // Disable button if trial is expired
207+
modifier = Modifier.align(Alignment.End)
244208
) {
245-
Text(text = "Pro (2,90 €/Month)")
209+
Text(text = stringResource(R.string.action_try))
246210
}
247211
}
248212
}
249213
}
250-
}
251214

252-
item {
253-
Card(
254-
modifier = Modifier
255-
.fillMaxWidth()
256-
.padding(horizontal = 16.dp)
257-
) {
258-
val annotatedText = buildAnnotatedString {
259-
append("Screenshots are saved in Pictures/Screenshots and you should delete them afterwards. Google has discontinued free API access to Gemini 2.5 Pro without a deposited billing account. There are rate limits for free use of Gemini models. The less powerful the models are, the more you can use them. The limits range from a maximum of 10 to 30 calls per minute. After each screenshot (every 2-3 seconds) the LLM must respond again. More information is available at ")
260-
261-
pushStringAnnotation(tag = "URL", annotation = "https://ai.google.dev/gemini-api/docs/rate-limits")
262-
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.primary, textDecoration = TextDecoration.Underline)) {
263-
append("https://ai.google.dev/gemini-api/docs/rate-limits")
215+
// Donation Button Card (Should always be enabled)
216+
item {
217+
Card(
218+
modifier = Modifier
219+
.fillMaxWidth()
220+
.padding(horizontal = 16.dp)
221+
) {
222+
Row(
223+
modifier = Modifier
224+
.padding(all = 16.dp)
225+
.fillMaxWidth(),
226+
verticalAlignment = Alignment.CenterVertically
227+
) {
228+
if (isPurchased) {
229+
Text(
230+
text = "Thank you for supporting the development! 🎉💛",
231+
style = MaterialTheme.typography.titleMedium,
232+
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp),
233+
textAlign = TextAlign.Center
234+
)
235+
} else {
236+
Text(
237+
text = "Support more Features",
238+
style = MaterialTheme.typography.titleMedium,
239+
modifier = Modifier.weight(1f)
240+
)
241+
Button(
242+
onClick = onDonationButtonClicked,
243+
modifier = Modifier.padding(start = 8.dp)
244+
) {
245+
Text(text = "Pro (2,90 €/Month)")
246+
}
247+
}
264248
}
265-
pop()
266249
}
250+
}
267251

268-
val uriHandler = LocalUriHandler.current
269-
270-
ClickableText(
271-
text = annotatedText,
252+
item {
253+
Card(
272254
modifier = Modifier
273255
.fillMaxWidth()
274-
.padding(all = 16.dp),
275-
style = MaterialTheme.typography.bodyMedium.copy(
276-
fontSize = 15.sp,
277-
color = MaterialTheme.colorScheme.onSurface
278-
),
279-
onClick = { offset ->
280-
// Allow clicking links even if trial is expired
281-
annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset)
282-
.firstOrNull()?.let { annotation ->
283-
uriHandler.openUri(annotation.item)
284-
}
256+
.padding(horizontal = 16.dp)
257+
) {
258+
val annotatedText = buildAnnotatedString {
259+
append("Screenshots are saved in Pictures/Screenshots and you should delete them afterwards. Google has discontinued free API access to Gemini 2.5 Pro without a deposited billing account. There are rate limits for free use of Gemini models. The less powerful the models are, the more you can use them. The limits range from a maximum of 10 to 30 calls per minute. After each screenshot (every 2-3 seconds) the LLM must respond again. More information is available at ")
260+
261+
pushStringAnnotation(tag = "URL", annotation = "https://ai.google.dev/gemini-api/docs/rate-limits")
262+
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.primary, textDecoration = TextDecoration.Underline)) {
263+
append("https://ai.google.dev/gemini-api/docs/rate-limits")
264+
}
265+
pop()
285266
}
286-
)
267+
268+
val uriHandler = LocalUriHandler.current
269+
270+
ClickableText(
271+
text = annotatedText,
272+
modifier = Modifier
273+
.fillMaxWidth()
274+
.padding(all = 16.dp),
275+
style = MaterialTheme.typography.bodyMedium.copy(
276+
fontSize = 15.sp,
277+
color = MaterialTheme.colorScheme.onSurface
278+
),
279+
onClick = { offset ->
280+
// Allow clicking links even if trial is expired
281+
annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset)
282+
.firstOrNull()?.let { annotation ->
283+
uriHandler.openUri(annotation.item)
284+
}
285+
}
286+
)
287+
}
287288
}
288-
}
289-
}
290-
}
289+
} // This closing brace is for LazyColumn
290+
} // This closing brace is for Scaffold
291+
} // This closing brace is for MenuScreen composable
291292

292293
@Preview(showSystemUi = true)
293294
@Composable

app/src/main/kotlin/com/google/ai/sample/common/ui/AnimatedAppTitle.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.google.ai.sample.common.ui
22

33
import androidx.compose.animation.core.*
44
import androidx.compose.foundation.layout.Row
5+
import androidx.compose.foundation.layout.offset
56
import androidx.compose.material3.Text
67
import androidx.compose.runtime.*
78
import androidx.compose.ui.Modifier

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -667,10 +667,6 @@ fun PhotoReasoningScreenPreviewWithContent() {
667667
)
668668
}
669669

670-
)
671-
}
672-
}
673-
674670
@Composable
675671
@Preview(showSystemUi = true)
676672
fun PhotoReasoningScreenPreviewEmpty() {

0 commit comments

Comments
 (0)