git.iko.soy/opencode-android
on master help sign in
Add search-field clear buttons and hidden-line counts on collapsed diffs/tool output
fe5705biko
app/src/main/java/soy/iko/opencode/ui/chat/AgentPickerSheet.kt+9
@@ -13,10 +13,12 @@ import androidx.compose.foundation.text.KeyboardActions
13import androidx.compose.foundation.text.KeyboardOptions
13import androidx.compose.foundation.text.KeyboardOptions
14import androidx.compose.material.icons.Icons
14import androidx.compose.material.icons.Icons
15import androidx.compose.material.icons.filled.Check
15import androidx.compose.material.icons.filled.Check
16+import androidx.compose.material.icons.filled.Close
16import androidx.compose.material.icons.filled.Search
17import androidx.compose.material.icons.filled.Search
17import androidx.compose.material3.CircularProgressIndicator
18import androidx.compose.material3.CircularProgressIndicator
18import androidx.compose.material3.ExperimentalMaterial3Api
19import androidx.compose.material3.ExperimentalMaterial3Api
19import androidx.compose.material3.Icon
20import androidx.compose.material3.Icon
21+import androidx.compose.material3.IconButton
20import androidx.compose.material3.MaterialTheme
22import androidx.compose.material3.MaterialTheme
21import androidx.compose.material3.ModalBottomSheet
23import androidx.compose.material3.ModalBottomSheet
22import androidx.compose.material3.OutlinedTextField
24import androidx.compose.material3.OutlinedTextField
@@ -97,6 +99,13 @@ fun AgentPickerSheet(
97 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 8.dp),
99 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 8.dp),
98 placeholder = { Text(stringResource(R.string.search_agents)) },
100 placeholder = { Text(stringResource(R.string.search_agents)) },
99 leadingIcon = { Icon(Icons.Filled.Search, contentDescription = null) },
101 leadingIcon = { Icon(Icons.Filled.Search, contentDescription = null) },
102+ trailingIcon = if (query.isNotEmpty()) {
103+ {
104+ IconButton(onClick = { query = "" }) {
105+ Icon(Icons.Filled.Close, contentDescription = stringResource(R.string.clear_search))
106+ }
107+ }
108+ } else null,
100 singleLine = true,
109 singleLine = true,
101 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
110 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
102 keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }),
111 keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }),
app/src/main/java/soy/iko/opencode/ui/chat/CommandPickerSheet.kt+9
@@ -11,10 +11,12 @@ import androidx.compose.foundation.text.KeyboardActions
11import androidx.compose.foundation.text.KeyboardOptions
11import androidx.compose.foundation.text.KeyboardOptions
12import androidx.compose.material.icons.Icons
12import androidx.compose.material.icons.Icons
13import androidx.compose.material.icons.automirrored.filled.PlaylistPlay
13import androidx.compose.material.icons.automirrored.filled.PlaylistPlay
14+import androidx.compose.material.icons.filled.Close
14import androidx.compose.material.icons.filled.Search
15import androidx.compose.material.icons.filled.Search
15import androidx.compose.material3.CircularProgressIndicator
16import androidx.compose.material3.CircularProgressIndicator
16import androidx.compose.material3.ExperimentalMaterial3Api
17import androidx.compose.material3.ExperimentalMaterial3Api
17import androidx.compose.material3.Icon
18import androidx.compose.material3.Icon
19+import androidx.compose.material3.IconButton
18import androidx.compose.material3.MaterialTheme
20import androidx.compose.material3.MaterialTheme
19import androidx.compose.material3.ModalBottomSheet
21import androidx.compose.material3.ModalBottomSheet
20import androidx.compose.material3.OutlinedTextField
22import androidx.compose.material3.OutlinedTextField
@@ -100,6 +102,13 @@ fun CommandPickerSheet(
100 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 8.dp),
102 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 8.dp),
101 placeholder = { Text(stringResource(R.string.search_commands)) },
103 placeholder = { Text(stringResource(R.string.search_commands)) },
102 leadingIcon = { Icon(Icons.Filled.Search, contentDescription = null) },
104 leadingIcon = { Icon(Icons.Filled.Search, contentDescription = null) },
105+ trailingIcon = if (query.isNotEmpty()) {
106+ {
107+ IconButton(onClick = { query = "" }) {
108+ Icon(Icons.Filled.Close, contentDescription = stringResource(R.string.clear_search))
109+ }
110+ }
111+ } else null,
103 singleLine = true,
112 singleLine = true,
104 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
113 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
105 keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }),
114 keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }),
app/src/main/java/soy/iko/opencode/ui/chat/ModelPickerSheet.kt+9
@@ -13,10 +13,12 @@ import androidx.compose.foundation.text.KeyboardActions
13import androidx.compose.foundation.text.KeyboardOptions
13import androidx.compose.foundation.text.KeyboardOptions
14import androidx.compose.material.icons.Icons
14import androidx.compose.material.icons.Icons
15import androidx.compose.material.icons.filled.Check
15import androidx.compose.material.icons.filled.Check
16+import androidx.compose.material.icons.filled.Close
16import androidx.compose.material.icons.filled.Search
17import androidx.compose.material.icons.filled.Search
17import androidx.compose.material3.CircularProgressIndicator
18import androidx.compose.material3.CircularProgressIndicator
18import androidx.compose.material3.ExperimentalMaterial3Api
19import androidx.compose.material3.ExperimentalMaterial3Api
19import androidx.compose.material3.Icon
20import androidx.compose.material3.Icon
21+import androidx.compose.material3.IconButton
20import androidx.compose.material3.MaterialTheme
22import androidx.compose.material3.MaterialTheme
21import androidx.compose.material3.ModalBottomSheet
23import androidx.compose.material3.ModalBottomSheet
22import androidx.compose.material3.OutlinedTextField
24import androidx.compose.material3.OutlinedTextField
@@ -97,6 +99,13 @@ fun ModelPickerSheet(
97 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 8.dp),
99 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 8.dp),
98 placeholder = { Text(stringResource(R.string.search_models)) },
100 placeholder = { Text(stringResource(R.string.search_models)) },
99 leadingIcon = { Icon(Icons.Filled.Search, contentDescription = null) },
101 leadingIcon = { Icon(Icons.Filled.Search, contentDescription = null) },
102+ trailingIcon = if (query.isNotEmpty()) {
103+ {
104+ IconButton(onClick = { query = "" }) {
105+ Icon(Icons.Filled.Close, contentDescription = stringResource(R.string.clear_search))
106+ }
107+ }
108+ } else null,
100 singleLine = true,
109 singleLine = true,
101 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
110 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
102 keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }),
111 keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }),
app/src/main/java/soy/iko/opencode/ui/chat/PartRenderer.kt+13−1
@@ -293,6 +293,13 @@ private fun ToolCallView(part: ToolPart, modifier: Modifier) {
293 onToggleExpand = if (detail.length > COLLAPSED_LIMIT) {
293 onToggleExpand = if (detail.length > COLLAPSED_LIMIT) {
294 { expanded = !expanded }
294 { expanded = !expanded }
295 } else null,
295 } else null,
296+ // How many lines are hidden while collapsed, so the expand affordance can
297+ // tell the user the scale of what's truncated instead of a bare "Show more".
298+ // `collapsed` is a prefix of `detail`, so its line count never exceeds it;
299+ // 0 (a single long line cut mid-way) falls back to the generic label.
300+ moreLines = if (detail.length > COLLAPSED_LIMIT) {
301+ detail.lines().size - collapsed.lines().size
302+ } else 0,
296 expandedState = expandedState,
303 expandedState = expandedState,
297 collapsedState = collapsedState,
304 collapsedState = collapsedState,
298 onCopy = { copyToClipboard(context, "output", detail) },
305 onCopy = { copyToClipboard(context, "output", detail) },
@@ -315,6 +322,7 @@ private fun CollapsibleDetail(
315 keySuffix: String,
322 keySuffix: String,
316 expanded: Boolean = false,
323 expanded: Boolean = false,
317 onToggleExpand: (() -> Unit)? = null,
324 onToggleExpand: (() -> Unit)? = null,
325+ moreLines: Int = 0,
318 expandedState: String = "",
326 expandedState: String = "",
319 collapsedState: String = "",
327 collapsedState: String = "",
320 onCopy: (() -> Unit)? = null,
328 onCopy: (() -> Unit)? = null,
@@ -370,7 +378,11 @@ private fun CollapsibleDetail(
370 modifier = Modifier.size(16.dp),
378 modifier = Modifier.size(16.dp),
371 )
379 )
372 Text(
380 Text(
381+ when {
373− if (expanded) stringResource(R.string.show_less) else stringResource(R.string.show_more),
382+ expanded -> stringResource(R.string.show_less)
383+ moreLines > 0 -> stringResource(R.string.show_more_lines, moreLines)
384+ else -> stringResource(R.string.show_more)
385+ },
374 style = MaterialTheme.typography.labelSmall,
386 style = MaterialTheme.typography.labelSmall,
375 modifier = Modifier.padding(start = 4.dp),
387 modifier = Modifier.padding(start = 4.dp),
376 )
388 )
app/src/main/java/soy/iko/opencode/ui/components/DiffView.kt+1−1
@@ -189,7 +189,7 @@ fun DiffView(diff: String, modifier: Modifier = Modifier) {
189 onClick = { expanded = true },
189 onClick = { expanded = true },
190 contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 10.dp),
190 contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 10.dp),
191 ) {
191 ) {
192− Text(stringResource(R.string.show_more))
192+ Text(stringResource(R.string.show_more_lines, lines.size - COLLAPSED_DIFF_LINES))
193 }
193 }
194 }
194 }
195 Spacer(Modifier.height(10.dp))
195 Spacer(Modifier.height(10.dp))
app/src/main/java/soy/iko/opencode/ui/file/FileBrowserScreen.kt+8
@@ -19,6 +19,7 @@ import androidx.compose.foundation.text.KeyboardOptions
19import androidx.compose.material.icons.Icons
19import androidx.compose.material.icons.Icons
20import androidx.compose.material.icons.automirrored.filled.ArrowBack
20import androidx.compose.material.icons.automirrored.filled.ArrowBack
21import androidx.compose.material.icons.filled.ChevronRight
21import androidx.compose.material.icons.filled.ChevronRight
22+import androidx.compose.material.icons.filled.Close
22import androidx.compose.material.icons.filled.Description
23import androidx.compose.material.icons.filled.Description
23import androidx.compose.material.icons.filled.Folder
24import androidx.compose.material.icons.filled.Folder
24import androidx.compose.material.icons.filled.Home
25import androidx.compose.material.icons.filled.Home
@@ -105,6 +106,13 @@ fun FileBrowserScreen(
105 modifier = Modifier.fillMaxWidth().padding(12.dp).testTag("file_search"),
106 modifier = Modifier.fillMaxWidth().padding(12.dp).testTag("file_search"),
106 label = { Text(stringResource(R.string.search_files)) },
107 label = { Text(stringResource(R.string.search_files)) },
107 leadingIcon = { Icon(Icons.Filled.Search, contentDescription = null) },
108 leadingIcon = { Icon(Icons.Filled.Search, contentDescription = null) },
109+ trailingIcon = if (state.query.isNotEmpty()) {
110+ {
111+ IconButton(onClick = { vm.setQuery("") }) {
112+ Icon(Icons.Filled.Close, contentDescription = stringResource(R.string.clear_search))
113+ }
114+ }
115+ } else null,
108 singleLine = true,
116 singleLine = true,
109 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
117 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
110 keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }),
118 keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }),
app/src/main/java/soy/iko/opencode/ui/session/SessionListScreen.kt+8
@@ -24,6 +24,7 @@ import androidx.compose.material.icons.filled.Add
24import androidx.compose.material.icons.filled.ArrowDropDown
24import androidx.compose.material.icons.filled.ArrowDropDown
25import androidx.compose.material.icons.filled.ChatBubbleOutline
25import androidx.compose.material.icons.filled.ChatBubbleOutline
26import androidx.compose.material.icons.filled.CheckCircle
26import androidx.compose.material.icons.filled.CheckCircle
27+import androidx.compose.material.icons.filled.Close
27import androidx.compose.material.icons.filled.Delete
28import androidx.compose.material.icons.filled.Delete
28import androidx.compose.material.icons.filled.Folder
29import androidx.compose.material.icons.filled.Folder
29import androidx.compose.material.icons.filled.MoreVert
30import androidx.compose.material.icons.filled.MoreVert
@@ -374,6 +375,13 @@ private fun androidx.compose.foundation.layout.BoxScope.SessionListBody(
374 modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp).testTag("session_search"),
375 modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp).testTag("session_search"),
375 label = { Text(stringResource(R.string.search_sessions)) },
376 label = { Text(stringResource(R.string.search_sessions)) },
376 leadingIcon = { Icon(Icons.Filled.Search, contentDescription = null) },
377 leadingIcon = { Icon(Icons.Filled.Search, contentDescription = null) },
378+ trailingIcon = if (state.query.isNotEmpty()) {
379+ {
380+ IconButton(onClick = { onQueryChange("") }) {
381+ Icon(Icons.Filled.Close, contentDescription = stringResource(R.string.clear_search))
382+ }
383+ }
384+ } else null,
377 singleLine = true,
385 singleLine = true,
378 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
386 keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
379 keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }),
387 keyboardActions = KeyboardActions(onSearch = { keyboardController?.hide() }),
app/src/main/res/values/strings.xml+4
@@ -138,6 +138,7 @@
138 <string name="thoughts">Thoughts</string>
138 <string name="thoughts">Thoughts</string>
139 <string name="show_more">Show more</string>
139 <string name="show_more">Show more</string>
140 <string name="show_less">Show less</string>
140 <string name="show_less">Show less</string>
141+ <string name="show_more_lines">Show %1$d more lines</string>
141 <string name="tool_completed">Completed</string>
142 <string name="tool_completed">Completed</string>
142 <string name="tool_error">Error</string>
143 <string name="tool_error">Error</string>
143 <string name="tool_running">Running</string>
144 <string name="tool_running">Running</string>
@@ -270,4 +271,7 @@
270 <string name="session_deleted_chat">Session deleted</string>
271 <string name="session_deleted_chat">Session deleted</string>
271 <string name="today">Today</string>
272 <string name="today">Today</string>
272 <string name="yesterday">Yesterday</string>
273 <string name="yesterday">Yesterday</string>
274+
275+ <!-- Search -->
276+ <string name="clear_search">Clear search</string>
273</resources>
277</resources>