Skip to content

Commit 97c4343

Browse files
ubuntuclaude
andcommitted
fix: Polish watch UI — centering, button sizing, bezel safe areas
WatchFace: - Time truly centered (Arrangement.Center) — no more top-heavy layout - AI status dot moved to top-center, battery to subtle top-right (8sp) - MIC button: 36dp → 28dp, camera: 28dp → 22dp — less dominant - Bottom action row with spacedBy(16.dp) instead of Spacer hacks TranscriptionScreen: - Back button: dark background circle + arrow unicode (←) — visible on all bezels - Title centered via Box with Alignment.Center (not SpaceBetween) - Divider inset extra 4dp on watch to avoid round edge - Footer: entry count hidden on watch, "Clear all" centered, 8dp bottom padding CameraOverlay: - Close button: top-center on watch (not top-right corner) — avoids bezel clip - Brighter background (RunanywhereAI#333) for close button visibility - Capture/? buttons: bottom padding 16dp → 24dp — well within bezel - Preview: 120dp → 100dp — more breathing room around edges Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 14b0de7 commit 97c4343

13 files changed

+210
-149
lines changed

examples/android/RunAnywhereWatch/app/src/main/java/com/runanywhere/runanywherewatch/CameraOverlay.kt

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,22 +84,22 @@ fun CameraOverlay(
8484
}
8585
}
8686

87-
// Close button — top right
87+
// Close button — top, within bezel safe area
8888
IconButton(
8989
onClick = onCloseCamera,
9090
modifier = Modifier
91-
.align(Alignment.TopEnd)
91+
.align(if (cfg.isWatch) Alignment.TopCenter else Alignment.TopEnd)
9292
.padding(
93-
top = if (cfg.isWatch) 20.dp else 16.dp,
94-
end = if (cfg.isWatch) 20.dp else 16.dp
93+
top = if (cfg.isWatch) 16.dp else 16.dp,
94+
end = if (cfg.isWatch) 0.dp else 16.dp
9595
)
96-
.size(if (cfg.isWatch) 28.dp else 40.dp)
96+
.size(if (cfg.isWatch) 24.dp else 40.dp)
9797
.clip(CircleShape)
98-
.background(Color(0xFF1A1A1A))
98+
.background(Color(0xFF333333))
9999
) {
100100
Text(
101-
text = "X",
102-
fontSize = if (cfg.isWatch) 14.sp else 20.sp,
101+
text = "\u2715",
102+
fontSize = if (cfg.isWatch) 11.sp else 18.sp,
103103
color = Color.White
104104
)
105105
}
@@ -136,15 +136,15 @@ fun CameraOverlay(
136136
}
137137
}
138138

139-
// Capture button — bottom center
139+
// Capture button — bottom center, well inside bezel
140140
Row(
141141
modifier = Modifier
142142
.align(Alignment.BottomCenter)
143-
.padding(bottom = if (cfg.isWatch) 16.dp else 32.dp),
144-
horizontalArrangement = Arrangement.Center,
143+
.padding(bottom = if (cfg.isWatch) 24.dp else 32.dp),
144+
horizontalArrangement = Arrangement.spacedBy(if (cfg.isWatch) 10.dp else 0.dp),
145145
verticalAlignment = Alignment.CenterVertically
146146
) {
147-
// On watch: Quick Ask is a small button beside capture
147+
// On watch: Quick Ask beside capture
148148
if (cfg.isWatch) {
149149
Button(
150150
onClick = {
@@ -158,9 +158,8 @@ fun CameraOverlay(
158158
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF00E5FF)),
159159
contentPadding = PaddingValues(0.dp)
160160
) {
161-
Text(text = "?", fontSize = 12.sp)
161+
Text(text = "?", fontSize = 10.sp)
162162
}
163-
Spacer(modifier = Modifier.width(8.dp))
164163
}
165164

166165
IconButton(
@@ -235,7 +234,7 @@ fun CameraOverlay(
235234
},
236235
modifier = Modifier
237236
.align(Alignment.BottomCenter)
238-
.padding(bottom = if (cfg.isWatch) 16.dp else 32.dp)
237+
.padding(bottom = if (cfg.isWatch) 24.dp else 32.dp)
239238
.size(cfg.captureButtonSize)
240239
.clip(CircleShape)
241240
.background(Color(0xFF00E5FF))

examples/android/RunAnywhereWatch/app/src/main/java/com/runanywhere/runanywherewatch/MainActivity.kt

Lines changed: 148 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -87,64 +87,16 @@ fun WatchFaceScreen(
8787
Box(
8888
modifier = Modifier
8989
.fillMaxSize()
90-
.background(Color(0xFF0D0D0D))
90+
.background(Color(0xFF0D0D0D)),
91+
contentAlignment = Alignment.Center
9192
) {
92-
Column(
93-
modifier = Modifier
94-
.fillMaxSize()
95-
.padding(horizontal = cfg.edgePadding),
96-
horizontalAlignment = Alignment.CenterHorizontally
97-
) {
98-
// Top bar: AI status + camera button
99-
Row(
100-
modifier = Modifier
101-
.fillMaxWidth()
102-
.padding(
103-
top = if (cfg.isWatch) 20.dp else cfg.edgePadding,
104-
bottom = cfg.itemSpacing
105-
),
106-
horizontalArrangement = Arrangement.SpaceBetween,
107-
verticalAlignment = Alignment.CenterVertically
108-
) {
109-
// AI Status dot
110-
Box(
111-
modifier = Modifier
112-
.size(cfg.statusDotSize)
113-
.clip(CircleShape)
114-
.background(
115-
when (sdkStatus) {
116-
SDKStatus.NOT_LOADED -> Color(0xFF666666)
117-
SDKStatus.THINKING -> Color(0xFF00E5FF).copy(alpha = 0.5f)
118-
SDKStatus.READY -> Color(0xFF00FF00)
119-
}
120-
)
121-
)
122-
123-
// Camera button — hidden on watch to save space
124-
if (!cfg.isWatch) {
125-
IconButton(
126-
onClick = {
127-
showCameraOverlay = true
128-
cameraManager = CameraManager()
129-
onCameraClick()
130-
},
131-
modifier = Modifier
132-
.size(cfg.secondaryButtonSize)
133-
.clip(CircleShape)
134-
.background(Color(0xFF1A1A1A))
135-
) {
136-
Text("CAM", fontSize = cfg.captionFontSize, color = Color.White)
137-
}
138-
} else {
139-
Spacer(modifier = Modifier.size(cfg.statusDotSize))
140-
}
141-
}
142-
143-
Spacer(modifier = Modifier.weight(1f))
144-
145-
// Time Display — centered
93+
if (cfg.isWatch) {
94+
// ── Watch layout: time dominant, everything else minimal ──
95+
// Time group — true center of the face
14696
Column(
147-
horizontalAlignment = Alignment.CenterHorizontally
97+
modifier = Modifier.fillMaxSize(),
98+
horizontalAlignment = Alignment.CenterHorizontally,
99+
verticalArrangement = Arrangement.Center
148100
) {
149101
Text(
150102
text = getCurrentTime(),
@@ -153,44 +105,126 @@ fun WatchFaceScreen(
153105
fontSize = cfg.timeFontSize,
154106
color = Color(0xFF00E5FF)
155107
)
156-
157108
Text(
158109
text = getCurrentDate(),
159110
fontFamily = FontFamily.SansSerif,
160111
fontSize = cfg.dateFontSize,
161-
color = Color.White.copy(alpha = 0.7f),
162-
modifier = Modifier.padding(top = if (cfg.isWatch) 2.dp else 8.dp)
112+
color = Color.White.copy(alpha = 0.6f),
113+
modifier = Modifier.padding(top = 1.dp)
163114
)
164-
165115
Text(
166116
text = getCurrentSeconds(),
167117
fontFamily = FontFamily.Monospace,
168118
fontSize = cfg.secondsFontSize,
169-
color = Color(0xFF00E5FF).copy(alpha = 0.8f),
170-
modifier = Modifier.padding(top = 2.dp)
119+
color = Color(0xFF00E5FF).copy(alpha = 0.7f),
120+
modifier = Modifier.padding(top = 1.dp)
171121
)
172122
}
173123

174-
Spacer(modifier = Modifier.weight(1f))
124+
// AI status dot — top center, inside bezel
125+
Box(
126+
modifier = Modifier
127+
.align(Alignment.TopCenter)
128+
.padding(top = 18.dp)
129+
.size(cfg.statusDotSize)
130+
.clip(CircleShape)
131+
.background(
132+
when (sdkStatus) {
133+
SDKStatus.NOT_LOADED -> Color(0xFF666666)
134+
SDKStatus.THINKING -> Color(0xFF00E5FF).copy(alpha = 0.5f)
135+
SDKStatus.READY -> Color(0xFF00FF00)
136+
}
137+
)
138+
)
175139

176-
// Battery
140+
// Battery — top right, subtle
177141
Text(
178-
text = if (cfg.isWatch) "${batteryLevel}%" else "Battery: ${batteryLevel}%",
179-
fontSize = cfg.captionFontSize,
180-
color = Color.White.copy(alpha = 0.5f),
181-
modifier = Modifier.padding(bottom = cfg.itemSpacing)
142+
text = "${batteryLevel}%",
143+
fontSize = 8.sp,
144+
color = Color.White.copy(alpha = 0.35f),
145+
modifier = Modifier
146+
.align(Alignment.TopEnd)
147+
.padding(top = 30.dp, end = 28.dp)
182148
)
183149

184-
// Bottom bar: mic button (+ camera on watch)
150+
// Bottom action row — mic + camera, compact
185151
Row(
186152
modifier = Modifier
187-
.fillMaxWidth()
188-
.padding(bottom = if (cfg.isWatch) 16.dp else cfg.edgePadding),
189-
horizontalArrangement = Arrangement.Center,
153+
.align(Alignment.BottomCenter)
154+
.padding(bottom = 20.dp),
155+
horizontalArrangement = Arrangement.spacedBy(16.dp),
190156
verticalAlignment = Alignment.CenterVertically
191157
) {
192-
// Camera button on watch — small, beside mic
193-
if (cfg.isWatch) {
158+
// Camera
159+
IconButton(
160+
onClick = {
161+
showCameraOverlay = true
162+
cameraManager = CameraManager()
163+
onCameraClick()
164+
},
165+
modifier = Modifier
166+
.size(cfg.secondaryButtonSize)
167+
.clip(CircleShape)
168+
.background(Color(0xFF1A1A1A))
169+
) {
170+
Text("C", fontSize = 8.sp, color = Color(0xFF00E5FF))
171+
}
172+
// Mic
173+
IconButton(
174+
onClick = {
175+
cameraManager = CameraManager()
176+
onMicClick()
177+
},
178+
modifier = Modifier
179+
.size(cfg.primaryButtonSize)
180+
.clip(CircleShape)
181+
.background(Color(0xFF1A1A1A))
182+
.border(1.5.dp, Color(0xFF00E5FF), CircleShape)
183+
) {
184+
Text("M", fontSize = 9.sp, color = Color(0xFF00E5FF))
185+
}
186+
}
187+
188+
// Circular bezel border
189+
Canvas(
190+
modifier = Modifier
191+
.fillMaxSize()
192+
.padding(2.dp)
193+
) {
194+
drawCircle(
195+
color = Color(0xFF00E5FF).copy(alpha = 0.2f),
196+
radius = size.minDimension / 2,
197+
style = Stroke(width = 1.dp.toPx())
198+
)
199+
}
200+
} else {
201+
// ── Phone layout: spacious, full-featured ──
202+
Column(
203+
modifier = Modifier
204+
.fillMaxSize()
205+
.padding(horizontal = cfg.edgePadding),
206+
horizontalAlignment = Alignment.CenterHorizontally
207+
) {
208+
// Top bar: AI status + camera button
209+
Row(
210+
modifier = Modifier
211+
.fillMaxWidth()
212+
.padding(top = cfg.edgePadding, bottom = cfg.itemSpacing),
213+
horizontalArrangement = Arrangement.SpaceBetween,
214+
verticalAlignment = Alignment.CenterVertically
215+
) {
216+
Box(
217+
modifier = Modifier
218+
.size(cfg.statusDotSize)
219+
.clip(CircleShape)
220+
.background(
221+
when (sdkStatus) {
222+
SDKStatus.NOT_LOADED -> Color(0xFF666666)
223+
SDKStatus.THINKING -> Color(0xFF00E5FF).copy(alpha = 0.5f)
224+
SDKStatus.READY -> Color(0xFF00FF00)
225+
}
226+
)
227+
)
194228
IconButton(
195229
onClick = {
196230
showCameraOverlay = true
@@ -202,44 +236,62 @@ fun WatchFaceScreen(
202236
.clip(CircleShape)
203237
.background(Color(0xFF1A1A1A))
204238
) {
205-
Text("C", fontSize = cfg.captionFontSize, color = Color(0xFF00E5FF))
239+
Text("CAM", fontSize = cfg.captionFontSize, color = Color.White)
206240
}
207-
Spacer(modifier = Modifier.width(12.dp))
208241
}
209242

243+
Spacer(modifier = Modifier.weight(1f))
244+
245+
// Time
246+
Column(horizontalAlignment = Alignment.CenterHorizontally) {
247+
Text(
248+
text = getCurrentTime(),
249+
fontFamily = FontFamily.Monospace,
250+
fontWeight = FontWeight.Bold,
251+
fontSize = cfg.timeFontSize,
252+
color = Color(0xFF00E5FF)
253+
)
254+
Text(
255+
text = getCurrentDate(),
256+
fontFamily = FontFamily.SansSerif,
257+
fontSize = cfg.dateFontSize,
258+
color = Color.White.copy(alpha = 0.7f),
259+
modifier = Modifier.padding(top = 8.dp)
260+
)
261+
Text(
262+
text = getCurrentSeconds(),
263+
fontFamily = FontFamily.Monospace,
264+
fontSize = cfg.secondsFontSize,
265+
color = Color(0xFF00E5FF).copy(alpha = 0.8f),
266+
modifier = Modifier.padding(top = 4.dp)
267+
)
268+
}
269+
270+
Spacer(modifier = Modifier.weight(1f))
271+
272+
// Battery
273+
Text(
274+
text = "Battery: ${batteryLevel}%",
275+
fontSize = cfg.captionFontSize,
276+
color = Color.White.copy(alpha = 0.5f),
277+
modifier = Modifier.padding(bottom = cfg.itemSpacing)
278+
)
279+
280+
// Mic button
210281
IconButton(
211282
onClick = {
212283
cameraManager = CameraManager()
213284
onMicClick()
214285
},
215286
modifier = Modifier
287+
.padding(bottom = cfg.edgePadding)
216288
.size(cfg.primaryButtonSize)
217289
.clip(CircleShape)
218290
.background(Color(0xFF1A1A1A))
219291
.border(2.dp, Color(0xFF00E5FF), CircleShape)
220292
) {
221293
Text("MIC", fontSize = cfg.captionFontSize, color = Color(0xFF00E5FF))
222294
}
223-
224-
if (cfg.isWatch) {
225-
Spacer(modifier = Modifier.width(12.dp))
226-
Spacer(modifier = Modifier.size(cfg.secondaryButtonSize))
227-
}
228-
}
229-
}
230-
231-
// Circular border for round watch screen
232-
if (cfg.isWatch) {
233-
Canvas(
234-
modifier = Modifier
235-
.fillMaxSize()
236-
.padding(2.dp)
237-
) {
238-
drawCircle(
239-
color = Color(0xFF00E5FF).copy(alpha = 0.3f),
240-
radius = size.minDimension / 2,
241-
style = Stroke(width = 1.dp.toPx())
242-
)
243295
}
244296
}
245297

0 commit comments

Comments
 (0)