Skip to content

Commit 86b10ff

Browse files
Android: polish workspace file navigation (#104873)
* fix(android): polish workspace file navigation * fix(android): serialize workspace file requests --------- Co-authored-by: IWhatsskill <[email protected]> Co-authored-by: Colin Johnson <[email protected]>
1 parent ec11440 commit 86b10ff

3 files changed

Lines changed: 72 additions & 18 deletions

File tree

apps/.i18n/native-source.json

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5659,79 +5659,87 @@
56595659
},
56605660
{
56615661
"kind": "ui-named-argument",
5662-
"line": 176,
5662+
"line": 198,
5663+
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/WorkspaceFilesScreen.kt",
5664+
"source": "Refresh",
5665+
"surface": "android",
5666+
"id": "native.android.066337249b9d9d30"
5667+
},
5668+
{
5669+
"kind": "ui-named-argument",
5670+
"line": 212,
56635671
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/WorkspaceFilesScreen.kt",
56645672
"source": "Files unavailable",
56655673
"surface": "android",
56665674
"id": "native.android.eb6ff96aba8f67b4"
56675675
},
56685676
{
56695677
"kind": "ui-named-argument",
5670-
"line": 182,
5678+
"line": 218,
56715679
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/WorkspaceFilesScreen.kt",
56725680
"source": "Empty folder",
56735681
"surface": "android",
56745682
"id": "native.android.fefb4a6c1717210d"
56755683
},
56765684
{
56775685
"kind": "ui-named-argument",
5678-
"line": 182,
5686+
"line": 218,
56795687
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/WorkspaceFilesScreen.kt",
56805688
"source": "This folder has no files yet.",
56815689
"surface": "android",
56825690
"id": "native.android.b74f90dc5f65b1da"
56835691
},
56845692
{
56855693
"kind": "ui-named-argument",
5686-
"line": 212,
5694+
"line": 249,
56875695
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/WorkspaceFilesScreen.kt",
56885696
"source": "Could not load this folder.",
56895697
"surface": "android",
56905698
"id": "native.android.8f0b5317c75e334d"
56915699
},
56925700
{
56935701
"kind": "ui-named-argument",
5694-
"line": 221,
5702+
"line": 258,
56955703
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/WorkspaceFilesScreen.kt",
56965704
"source": "Load more",
56975705
"surface": "android",
56985706
"id": "native.android.4e48916e1b0f86ab"
56995707
},
57005708
{
57015709
"kind": "ui-named-argument",
5702-
"line": 223,
5710+
"line": 260,
57035711
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/WorkspaceFilesScreen.kt",
57045712
"source": "${entries.size} of $totalEntries",
57055713
"surface": "android",
57065714
"id": "native.android.f26b7b179d4765b0"
57075715
},
57085716
{
57095717
"kind": "ui-named-argument",
5710-
"line": 316,
5718+
"line": 353,
57115719
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/WorkspaceFilesScreen.kt",
57125720
"source": "Back",
57135721
"surface": "android",
57145722
"id": "native.android.b2a525ca00a799df"
57155723
},
57165724
{
57175725
"kind": "ui-named-argument",
5718-
"line": 327,
5726+
"line": 364,
57195727
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/WorkspaceFilesScreen.kt",
57205728
"source": "Share file",
57215729
"surface": "android",
57225730
"id": "native.android.737edf881bc586d5"
57235731
},
57245732
{
57255733
"kind": "ui-named-argument",
5726-
"line": 358,
5734+
"line": 395,
57275735
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/WorkspaceFilesScreen.kt",
57285736
"source": "No preview",
57295737
"surface": "android",
57305738
"id": "native.android.095574301bf487a1"
57315739
},
57325740
{
57335741
"kind": "ui-named-argument",
5734-
"line": 358,
5742+
"line": 395,
57355743
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/WorkspaceFilesScreen.kt",
57365744
"source": "This image could not be decoded.",
57375745
"surface": "android",

apps/android/app/src/main/java/ai/openclaw/app/ui/WorkspaceFilesScreen.kt

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import androidx.activity.compose.BackHandler
1717
import androidx.compose.foundation.Image
1818
import androidx.compose.foundation.clickable
1919
import androidx.compose.foundation.layout.Arrangement
20+
import androidx.compose.foundation.layout.Box
2021
import androidx.compose.foundation.layout.Column
2122
import androidx.compose.foundation.layout.PaddingValues
2223
import androidx.compose.foundation.layout.Row
@@ -37,6 +38,7 @@ import androidx.compose.material.icons.Icons
3738
import androidx.compose.material.icons.automirrored.filled.ArrowBack
3839
import androidx.compose.material.icons.outlined.Description
3940
import androidx.compose.material.icons.outlined.Folder
41+
import androidx.compose.material.icons.outlined.Refresh
4042
import androidx.compose.material.icons.outlined.Share
4143
import androidx.compose.material3.CircularProgressIndicator
4244
import androidx.compose.material3.Icon
@@ -56,6 +58,7 @@ import androidx.compose.ui.Alignment
5658
import androidx.compose.ui.Modifier
5759
import androidx.compose.ui.draw.clip
5860
import androidx.compose.ui.platform.LocalContext
61+
import androidx.compose.ui.text.style.TextOverflow
5962
import androidx.compose.ui.unit.dp
6063
import androidx.compose.ui.unit.sp
6164
import androidx.core.content.FileProvider
@@ -112,6 +115,11 @@ internal fun WorkspaceFilesScreen(
112115
}
113116
}
114117

118+
internal fun isWorkspaceDirectoryRequestInFlight(
119+
loading: Boolean,
120+
loadingMore: Boolean,
121+
): Boolean = loading || loadingMore
122+
115123
@Composable
116124
private fun WorkspaceDirectoryScreen(
117125
viewModel: MainViewModel,
@@ -127,8 +135,10 @@ private fun WorkspaceDirectoryScreen(
127135
var loading by remember(path) { mutableStateOf(false) }
128136
var loadingMore by remember(path) { mutableStateOf(false) }
129137
var errorText by remember(path) { mutableStateOf<String?>(null) }
138+
var refreshNonce by remember(path) { mutableIntStateOf(0) }
139+
val requestInFlight = isWorkspaceDirectoryRequestInFlight(loading, loadingMore)
130140

131-
LaunchedEffect(path, isConnected) {
141+
LaunchedEffect(path, isConnected, refreshNonce) {
132142
if (!isConnected) {
133143
errorText = "Connect the gateway to browse workspace files."
134144
return@LaunchedEffect
@@ -162,12 +172,38 @@ private fun WorkspaceDirectoryScreen(
162172
horizontalArrangement = Arrangement.spacedBy(8.dp),
163173
) {
164174
ClawPlainIconButton(icon = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", onClick = onBack)
165-
Text(
166-
text = if (path.isEmpty()) "Files" else path.substringAfterLast('/'),
167-
style = ClawTheme.type.display.copy(fontSize = 24.sp, lineHeight = 28.sp),
168-
color = ClawTheme.colors.text,
169-
modifier = Modifier.weight(1f),
170-
)
175+
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) {
176+
Text(
177+
text = if (path.isEmpty()) "Files" else path.substringAfterLast('/'),
178+
style = ClawTheme.type.display.copy(fontSize = 24.sp, lineHeight = 28.sp),
179+
color = ClawTheme.colors.text,
180+
)
181+
if (path.isNotEmpty()) {
182+
Text(
183+
text = path,
184+
style = ClawTheme.type.caption,
185+
color = ClawTheme.colors.textMuted,
186+
maxLines = 1,
187+
overflow = TextOverflow.Ellipsis,
188+
)
189+
}
190+
}
191+
if (requestInFlight) {
192+
Box(modifier = Modifier.size(ClawTheme.spacing.touchTarget), contentAlignment = Alignment.Center) {
193+
CircularProgressIndicator(modifier = Modifier.size(18.dp), strokeWidth = 2.dp)
194+
}
195+
} else if (isConnected) {
196+
ClawPlainIconButton(
197+
icon = Icons.Outlined.Refresh,
198+
contentDescription = "Refresh",
199+
onClick = {
200+
if (!isWorkspaceDirectoryRequestInFlight(loading, loadingMore)) {
201+
loading = true
202+
refreshNonce += 1
203+
}
204+
},
205+
)
206+
}
171207
}
172208
}
173209

@@ -200,7 +236,8 @@ private fun WorkspaceDirectoryScreen(
200236
Modifier
201237
.fillMaxWidth()
202238
.clip(RoundedCornerShape(ClawTheme.radii.row))
203-
.clickable(enabled = !loadingMore) {
239+
.clickable(enabled = !requestInFlight) {
240+
if (isWorkspaceDirectoryRequestInFlight(loading, loadingMore)) return@clickable
204241
loadingMore = true
205242
scope.launch {
206243
try {

apps/android/app/src/test/java/ai/openclaw/app/WorkspaceFilesTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ai.openclaw.app
22

3+
import ai.openclaw.app.ui.isWorkspaceDirectoryRequestInFlight
34
import kotlinx.serialization.json.Json
45
import org.junit.Assert.assertEquals
56
import org.junit.Assert.assertNull
@@ -9,6 +10,14 @@ import org.junit.Test
910
class WorkspaceFilesTest {
1011
private val json = Json { ignoreUnknownKeys = true }
1112

13+
@Test
14+
fun tracksDirectoryRequestInFlightAcrossRefreshAndPagination() {
15+
assertEquals(false, isWorkspaceDirectoryRequestInFlight(loading = false, loadingMore = false))
16+
assertEquals(true, isWorkspaceDirectoryRequestInFlight(loading = true, loadingMore = false))
17+
assertEquals(true, isWorkspaceDirectoryRequestInFlight(loading = false, loadingMore = true))
18+
assertEquals(true, isWorkspaceDirectoryRequestInFlight(loading = true, loadingMore = true))
19+
}
20+
1221
@Test
1322
fun parsesListingEntriesAndPagination() {
1423
val payload =

0 commit comments

Comments
 (0)