Skip to content

Commit 7c3625e

Browse files
RunAnywhereSiddhesh2377
authored andcommitted
fix: Android app UI bug fixes, responsive dimensions, LoRA example prompts, and darker dark mode
- Fix nested verticalScroll inside LazyColumn (ThinkingToggle) causing broken scroll - Fix weight(1f) + verticalScroll overflow in VLMScreen DescriptionPanel - Add verticalScroll to MoreHubScreen to prevent clipping on small screens - Add imePadding to ConversationListSheet so keyboard doesn't cover search - Fix auto-scroll wrap logic in EmptyStateView using canScrollForward - Replace collectAsState with collectAsStateWithLifecycle in 3 screens - Replace deprecated STTMode.values() with .entries - Replace hardcoded Color.Gray with AppColors.statusGray for dark mode contrast - Remove redundant Color.White inside buttons with contentColor set - Replace hardcoded 300.dp bubble width with responsive Dimensions.messageBubbleMaxWidth - Add accessibility semantics role to VLMScreen clickable Column - Disable Image Generation card (placeholder feature) - Add responsive rDp/rSp utilities and convert Dimensions/AppSpacing to use them - Add LoRA example prompts with copy button to adapter picker and manager screens - Darken dark mode background colors
1 parent 03d6416 commit 7c3625e

16 files changed

Lines changed: 424 additions & 261 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.runanywhere.runanywhereai.data
2+
3+
/**
4+
* Example prompts for each LoRA adapter, keyed by adapter filename.
5+
* These are shown in the active LoRA card so users can quickly test the adapter.
6+
*/
7+
object LoraExamplePrompts {
8+
9+
private val promptsByFilename: Map<String, List<String>> = mapOf(
10+
"code-assistant-Q8_0.gguf" to listOf(
11+
"Write a Python function to reverse a linked list",
12+
"Explain the difference between a stack and a queue with code examples",
13+
),
14+
"reasoning-logic-Q8_0.gguf" to listOf(
15+
"If all roses are flowers and some flowers fade quickly, can we conclude some roses fade quickly?",
16+
"A farmer has 17 sheep. All but 9 die. How many are left?",
17+
),
18+
"medical-qa-Q8_0.gguf" to listOf(
19+
"What are the common symptoms of vitamin D deficiency?",
20+
"Explain the difference between Type 1 and Type 2 diabetes",
21+
),
22+
"creative-writing-Q8_0.gguf" to listOf(
23+
"Write a short story about a robot discovering emotions for the first time",
24+
"Describe a sunset over the ocean using vivid sensory language",
25+
),
26+
)
27+
28+
/**
29+
* Get example prompts for a loaded adapter by its file path.
30+
* Extracts the filename from the path and looks up prompts.
31+
*/
32+
fun forAdapterPath(path: String): List<String> {
33+
val filename = path.substringAfterLast("/")
34+
return promptsByFilename[filename] ?: emptyList()
35+
}
36+
}

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatScreen.kt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -894,7 +894,6 @@ fun ThinkingToggle(
894894
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
895895
shape = RoundedCornerShape(Dimensions.cornerRadiusRegular),
896896
) {
897-
val scrollState = rememberScrollState()
898897
Text(
899898
text = thinkingContent,
900899
style = MaterialTheme.typography.bodySmall.copy(
@@ -903,8 +902,8 @@ fun ThinkingToggle(
903902
color = MaterialTheme.colorScheme.onSurfaceVariant,
904903
modifier = Modifier
905904
.heightIn(max = 200.dp)
906-
.verticalScroll(scrollState)
907905
.padding(Dimensions.mediumLarge),
906+
overflow = TextOverflow.Ellipsis,
908907
)
909908
}
910909
}
@@ -1101,15 +1100,12 @@ fun EmptyStateView(
11011100
// Small delay before starting auto-scroll
11021101
kotlinx.coroutines.delay(800)
11031102
while (!userHasScrolled) {
1104-
val currentOffset = promptListState.firstVisibleItemScrollOffset
11051103
promptListState.animateScrollBy(
11061104
value = 1.5f,
11071105
animationSpec = tween(durationMillis = 16, easing = LinearEasing),
11081106
)
11091107
// If we can't scroll further, wrap back to start
1110-
if (promptListState.firstVisibleItemScrollOffset == currentOffset &&
1111-
promptListState.firstVisibleItemIndex == promptListState.layoutInfo.totalItemsCount - 1
1112-
) {
1108+
if (!promptListState.canScrollForward) {
11131109
promptListState.scrollToItem(0)
11141110
}
11151111
kotlinx.coroutines.delay(16)
@@ -1331,7 +1327,8 @@ fun ConversationListSheet(
13311327
modifier =
13321328
Modifier
13331329
.fillMaxWidth()
1334-
.fillMaxHeight(0.85f),
1330+
.fillMaxHeight(0.85f)
1331+
.imePadding(),
13351332
) {
13361333
// Header
13371334
Row(

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/lora/LoraAdapterPickerSheet.kt

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
1717
import androidx.compose.material.icons.Icons
1818
import androidx.compose.material.icons.filled.Close
1919
import androidx.compose.material.icons.filled.CloudDownload
20+
import androidx.compose.material.icons.filled.ContentCopy
2021
import androidx.compose.material.icons.filled.Delete
2122
import androidx.compose.material.icons.filled.LinkOff
2223
import androidx.compose.material.icons.filled.PlayArrow
@@ -35,16 +36,20 @@ import androidx.compose.material3.Text
3536
import androidx.compose.material3.TextButton
3637
import androidx.compose.material3.rememberModalBottomSheetState
3738
import androidx.compose.runtime.Composable
38-
import androidx.compose.runtime.collectAsState
39+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3940
import androidx.compose.runtime.getValue
4041
import androidx.compose.runtime.mutableFloatStateOf
4142
import androidx.compose.runtime.remember
4243
import androidx.compose.runtime.setValue
4344
import androidx.compose.ui.Alignment
4445
import androidx.compose.ui.Modifier
4546
import androidx.compose.ui.draw.clip
47+
import androidx.compose.ui.platform.LocalClipboardManager
48+
import androidx.compose.ui.text.AnnotatedString
4649
import androidx.compose.ui.text.font.FontWeight
50+
import androidx.compose.ui.text.style.TextOverflow
4751
import androidx.compose.ui.unit.dp
52+
import com.runanywhere.runanywhereai.data.LoraExamplePrompts
4853
import com.runanywhere.runanywhereai.ui.theme.AppColors
4954
import com.runanywhere.runanywhereai.ui.theme.Dimensions
5055
import com.runanywhere.sdk.public.extensions.LoraAdapterCatalogEntry
@@ -60,7 +65,7 @@ fun LoraAdapterPickerSheet(
6065
loraViewModel: LoraViewModel,
6166
onDismiss: () -> Unit,
6267
) {
63-
val state by loraViewModel.uiState.collectAsState()
68+
val state by loraViewModel.uiState.collectAsStateWithLifecycle()
6469

6570
ModalBottomSheet(
6671
onDismissRequest = onDismiss,
@@ -181,33 +186,80 @@ private fun LoadedAdapterRow(
181186
adapter: LoRAAdapterInfo,
182187
onRemove: () -> Unit,
183188
) {
184-
Row(
189+
val clipboardManager = LocalClipboardManager.current
190+
val examplePrompts = remember(adapter.path) { LoraExamplePrompts.forAdapterPath(adapter.path) }
191+
192+
Column(
185193
modifier = Modifier
186194
.fillMaxWidth()
187195
.clip(RoundedCornerShape(Dimensions.cornerRadiusRegular))
188196
.background(MaterialTheme.colorScheme.surfaceVariant)
189197
.padding(Dimensions.mediumLarge),
190-
verticalAlignment = Alignment.CenterVertically,
191198
) {
192-
Column(modifier = Modifier.weight(1f)) {
193-
Text(
194-
adapter.path.substringAfterLast("/"),
195-
style = MaterialTheme.typography.bodyMedium,
196-
fontWeight = FontWeight.Medium,
197-
)
199+
Row(
200+
modifier = Modifier.fillMaxWidth(),
201+
verticalAlignment = Alignment.CenterVertically,
202+
) {
203+
Column(modifier = Modifier.weight(1f)) {
204+
Text(
205+
adapter.path.substringAfterLast("/"),
206+
style = MaterialTheme.typography.bodyMedium,
207+
fontWeight = FontWeight.Medium,
208+
)
209+
Text(
210+
"Scale: %.2f".format(adapter.scale),
211+
style = MaterialTheme.typography.bodySmall,
212+
color = MaterialTheme.colorScheme.onSurfaceVariant,
213+
)
214+
}
215+
IconButton(onClick = onRemove) {
216+
Icon(
217+
Icons.Default.Close,
218+
contentDescription = "Remove",
219+
tint = AppColors.primaryRed,
220+
modifier = Modifier.size(20.dp),
221+
)
222+
}
223+
}
224+
225+
// Example prompts
226+
if (examplePrompts.isNotEmpty()) {
227+
Spacer(modifier = Modifier.height(Dimensions.smallMedium))
198228
Text(
199-
"Scale: %.2f".format(adapter.scale),
200-
style = MaterialTheme.typography.bodySmall,
229+
"Try it out:",
230+
style = MaterialTheme.typography.labelSmall,
201231
color = MaterialTheme.colorScheme.onSurfaceVariant,
232+
fontWeight = FontWeight.SemiBold,
202233
)
203-
}
204-
IconButton(onClick = onRemove) {
205-
Icon(
206-
Icons.Default.Close,
207-
contentDescription = "Remove",
208-
tint = AppColors.primaryRed,
209-
modifier = Modifier.size(20.dp),
210-
)
234+
Spacer(modifier = Modifier.height(Dimensions.xSmall))
235+
examplePrompts.forEach { prompt ->
236+
Row(
237+
modifier = Modifier
238+
.fillMaxWidth()
239+
.padding(vertical = Dimensions.xxSmall),
240+
verticalAlignment = Alignment.CenterVertically,
241+
) {
242+
Text(
243+
"\u201C$prompt\u201D",
244+
style = MaterialTheme.typography.labelSmall,
245+
color = AppColors.primaryPurple,
246+
modifier = Modifier.weight(1f),
247+
maxLines = 2,
248+
overflow = TextOverflow.Ellipsis,
249+
)
250+
IconButton(
251+
onClick = { clipboardManager.setText(AnnotatedString(prompt)) },
252+
modifier = Modifier.size(Dimensions.iconRegular),
253+
) {
254+
Icon(
255+
Icons.Default.ContentCopy,
256+
contentDescription = "Copy prompt",
257+
tint = MaterialTheme.colorScheme.onSurfaceVariant,
258+
modifier = Modifier.size(Dimensions.regular),
259+
)
260+
}
261+
}
262+
}
211263
}
212264
}
213265
}

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/lora/LoraManagerScreen.kt

Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
1717
import androidx.compose.material.icons.Icons
1818
import androidx.compose.material.icons.filled.Close
1919
import androidx.compose.material.icons.filled.CloudDownload
20+
import androidx.compose.material.icons.filled.ContentCopy
2021
import androidx.compose.material.icons.filled.Delete
2122
import androidx.compose.material.icons.filled.DeleteForever
2223
import androidx.compose.material.icons.filled.LinkOff
@@ -30,14 +31,19 @@ import androidx.compose.material3.IconButton
3031
import androidx.compose.material3.MaterialTheme
3132
import androidx.compose.material3.Text
3233
import androidx.compose.runtime.Composable
33-
import androidx.compose.runtime.collectAsState
34+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3435
import androidx.compose.runtime.getValue
36+
import androidx.compose.runtime.remember
3537
import androidx.compose.ui.Alignment
3638
import androidx.compose.ui.Modifier
3739
import androidx.compose.ui.draw.clip
40+
import androidx.compose.ui.platform.LocalClipboardManager
41+
import androidx.compose.ui.text.AnnotatedString
3842
import androidx.compose.ui.text.font.FontWeight
43+
import androidx.compose.ui.text.style.TextOverflow
3944
import androidx.compose.ui.unit.dp
4045
import androidx.lifecycle.viewmodel.compose.viewModel
46+
import com.runanywhere.runanywhereai.data.LoraExamplePrompts
4147
import com.runanywhere.runanywhereai.presentation.components.ConfigureTopBar
4248
import com.runanywhere.runanywhereai.ui.theme.AppColors
4349
import com.runanywhere.runanywhereai.ui.theme.Dimensions
@@ -52,7 +58,8 @@ fun LoraManagerScreen(
5258
onBack: () -> Unit = {},
5359
loraViewModel: LoraViewModel = viewModel(),
5460
) {
55-
val state by loraViewModel.uiState.collectAsState()
61+
val state by loraViewModel.uiState.collectAsStateWithLifecycle()
62+
val clipboardManager = LocalClipboardManager.current
5663

5764
ConfigureTopBar(title = "LoRA Adapters", showBack = true, onBack = onBack)
5865

@@ -74,43 +81,89 @@ fun LoraManagerScreen(
7481
)
7582
}
7683
items(state.loadedAdapters, key = { it.path }) { adapter ->
84+
val examplePrompts = remember(adapter.path) { LoraExamplePrompts.forAdapterPath(adapter.path) }
85+
7786
Card(
7887
modifier = Modifier.fillMaxWidth(),
7988
shape = RoundedCornerShape(Dimensions.cornerRadiusXLarge),
8089
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
8190
) {
82-
Row(
91+
Column(
8392
modifier = Modifier
8493
.fillMaxWidth()
8594
.padding(Dimensions.large),
86-
verticalAlignment = Alignment.CenterVertically,
8795
) {
88-
Column(modifier = Modifier.weight(1f)) {
89-
Text(
90-
adapter.path.substringAfterLast("/"),
91-
style = MaterialTheme.typography.bodyMedium,
92-
fontWeight = FontWeight.Medium,
93-
)
96+
Row(
97+
modifier = Modifier.fillMaxWidth(),
98+
verticalAlignment = Alignment.CenterVertically,
99+
) {
100+
Column(modifier = Modifier.weight(1f)) {
101+
Text(
102+
adapter.path.substringAfterLast("/"),
103+
style = MaterialTheme.typography.bodyMedium,
104+
fontWeight = FontWeight.Medium,
105+
)
106+
Text(
107+
"Scale: ${"%.2f".format(adapter.scale)} | ${if (adapter.applied) "Applied" else "Pending"}",
108+
style = MaterialTheme.typography.bodySmall,
109+
color = MaterialTheme.colorScheme.onSurfaceVariant,
110+
)
111+
}
112+
Button(
113+
onClick = { loraViewModel.unloadAdapter(adapter.path) },
114+
colors = ButtonDefaults.buttonColors(
115+
containerColor = AppColors.primaryRed.copy(alpha = 0.1f),
116+
),
117+
) {
118+
Icon(
119+
Icons.Default.LinkOff,
120+
contentDescription = null,
121+
tint = AppColors.primaryRed,
122+
modifier = Modifier.size(16.dp),
123+
)
124+
Spacer(modifier = Modifier.width(Dimensions.xSmall))
125+
Text("Unload", color = AppColors.primaryRed)
126+
}
127+
}
128+
129+
// Example prompts
130+
if (examplePrompts.isNotEmpty()) {
131+
Spacer(modifier = Modifier.height(Dimensions.smallMedium))
94132
Text(
95-
"Scale: ${"%.2f".format(adapter.scale)} | ${if (adapter.applied) "Applied" else "Pending"}",
96-
style = MaterialTheme.typography.bodySmall,
133+
"Try it out:",
134+
style = MaterialTheme.typography.labelSmall,
97135
color = MaterialTheme.colorScheme.onSurfaceVariant,
136+
fontWeight = FontWeight.SemiBold,
98137
)
99-
}
100-
Button(
101-
onClick = { loraViewModel.unloadAdapter(adapter.path) },
102-
colors = ButtonDefaults.buttonColors(
103-
containerColor = AppColors.primaryRed.copy(alpha = 0.1f),
104-
),
105-
) {
106-
Icon(
107-
Icons.Default.LinkOff,
108-
contentDescription = null,
109-
tint = AppColors.primaryRed,
110-
modifier = Modifier.size(16.dp),
111-
)
112-
Spacer(modifier = Modifier.width(Dimensions.xSmall))
113-
Text("Unload", color = AppColors.primaryRed)
138+
Spacer(modifier = Modifier.height(Dimensions.xSmall))
139+
examplePrompts.forEach { prompt ->
140+
Row(
141+
modifier = Modifier
142+
.fillMaxWidth()
143+
.padding(vertical = Dimensions.xxSmall),
144+
verticalAlignment = Alignment.CenterVertically,
145+
) {
146+
Text(
147+
"\u201C$prompt\u201D",
148+
style = MaterialTheme.typography.labelSmall,
149+
color = AppColors.primaryPurple,
150+
modifier = Modifier.weight(1f),
151+
maxLines = 2,
152+
overflow = TextOverflow.Ellipsis,
153+
)
154+
IconButton(
155+
onClick = { clipboardManager.setText(AnnotatedString(prompt)) },
156+
modifier = Modifier.size(Dimensions.iconRegular),
157+
) {
158+
Icon(
159+
Icons.Default.ContentCopy,
160+
contentDescription = "Copy prompt",
161+
tint = MaterialTheme.colorScheme.onSurfaceVariant,
162+
modifier = Modifier.size(Dimensions.regular),
163+
)
164+
}
165+
}
166+
}
114167
}
115168
}
116169
}

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/navigation/AppNavigation.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ fun AppNavigation() {
6060
val topBarState = remember { TopBarState() }
6161

6262
val density = LocalDensity.current
63-
val imeBottom = WindowInsets.ime.getBottom(density)
64-
val isKeyboardOpen = imeBottom > 0
63+
val isKeyboardOpen = WindowInsets.ime.getBottom(density) > 0
6564

6665
CompositionLocalProvider(LocalTopBarState provides topBarState) {
6766
Scaffold(
@@ -147,9 +146,6 @@ fun AppNavigation() {
147146
onNavigateToVLM = {
148147
navController.navigate(NavigationRoute.VLM)
149148
},
150-
onNavigateToImageGeneration = {
151-
// Future
152-
},
153149
)
154150
}
155151

0 commit comments

Comments
 (0)