git.iko.soy/opencode-android
on 66d239c help sign in
107 lines 4.7 kB history raw
1package soy.iko.opencode.ui.chat
3import androidx.compose.foundation.clickable
4import androidx.compose.foundation.layout.Column
5import androidx.compose.foundation.layout.Row
6import androidx.compose.foundation.layout.fillMaxWidth
7import androidx.compose.foundation.layout.heightIn
8import androidx.compose.foundation.layout.padding
9import androidx.compose.foundation.layout.size
10import androidx.compose.foundation.lazy.LazyColumn
11import androidx.compose.foundation.lazy.items
12import androidx.compose.material.icons.Icons
13import androidx.compose.material.icons.filled.Check
14import androidx.compose.material3.CircularProgressIndicator
15import androidx.compose.material3.ExperimentalMaterial3Api
16import androidx.compose.material3.Icon
17import androidx.compose.material3.MaterialTheme
18import androidx.compose.material3.ModalBottomSheet
19import androidx.compose.material3.Text
20import androidx.compose.runtime.Composable
21import androidx.compose.ui.Alignment
22import androidx.compose.ui.Modifier
23import androidx.compose.ui.res.stringResource
24import androidx.compose.ui.semantics.Role
25import androidx.compose.ui.semantics.role
26import androidx.compose.ui.semantics.selected
27import androidx.compose.ui.semantics.semantics
28import androidx.compose.ui.unit.dp
29import androidx.compose.foundation.layout.Box
30import soy.iko.opencode.data.model.ModelOption
31import soy.iko.opencode.R
33/** Bottom sheet that lists every provider/model and lets the user pick one. */
34@OptIn(ExperimentalMaterial3Api::class)
35@Composable
36fun ModelPickerSheet(
37 options: List<ModelOption>,
38 selected: ModelOption?,
39 loading: Boolean,
40 onSelect: (ModelOption) -> Unit,
41 onDismiss: () -> Unit,
42) {
43 ModalBottomSheet(onDismissRequest = onDismiss) {
44 Text(
45 stringResource(R.string.model),
46 style = MaterialTheme.typography.titleMedium,
47 modifier = Modifier.padding(start = 24.dp, end = 24.dp, bottom = 8.dp),
48 )
49 if (loading) {
50 Box(
51 modifier = Modifier.fillMaxWidth().padding(vertical = 24.dp),
52 contentAlignment = Alignment.Center,
53 ) {
54 CircularProgressIndicator()
55 }
56 } else if (options.isEmpty()) {
57 Text(
58 stringResource(R.string.no_models),
59 style = MaterialTheme.typography.bodyMedium,
60 color = MaterialTheme.colorScheme.onSurfaceVariant,
61 modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp),
62 )
63 } else {
64 LazyColumn(modifier = Modifier.fillMaxWidth().heightIn(max = 420.dp)) {
65 items(options, key = { it.providerID + "/" + it.modelID }) { option ->
66 val isSelected = option.providerID == selected?.providerID &&
67 option.modelID == selected.modelID
68 Column(
69 modifier = Modifier
70 .fillMaxWidth()
71 .semantics(mergeDescendants = true) {
72 role = Role.RadioButton
73 if (isSelected) this.selected = true
74 }
75 .clickable {
76 onSelect(option)
77 onDismiss()
78 }
79 .padding(horizontal = 24.dp, vertical = 12.dp),
80 ) {
81 Row(verticalAlignment = Alignment.CenterVertically) {
82 Text(
83 option.modelLabel,
84 style = MaterialTheme.typography.bodyLarge,
85 color = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface,
86 modifier = Modifier.weight(1f),
87 )
88 if (isSelected) {
89 Icon(
90 Icons.Filled.Check,
91 contentDescription = null,
92 tint = MaterialTheme.colorScheme.primary,
93 modifier = Modifier.size(18.dp),
94 )
95 }
96 }
97 Text(
98 option.providerLabel,
99 style = MaterialTheme.typography.bodySmall,
100 color = MaterialTheme.colorScheme.onSurfaceVariant,
101 )
102 }
103 }
104 }
105 }
106 }