authorgravatar for me@pixp.ccAlex <me@pixp.cc> 2026-10-11 11:35:42-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-11 11:35:42-07:00
log8863990fcaecfb59dbd6c338e49aba603ecccdfd
tree6400a1e739e64fb536ea83ca29ed105bc524abcc
parent2e19207b179dca57d57ecd24a4b0b0776a41e0bf
signature Signed by PGP key B5690EEEBB952194

fix(android): Use MD3 loading spinners (#14282)


32 files changed, 159 insertions(+), 113 deletions(-)

apps/mobile/src/components/AppActivityIndicator.android.tsx created+49
...@@ -0,0 +1,49 @@
1import { LoadingIndicator, Host } from "@expo/ui/jetpack-compose";
2import { size as composeSize } from "@expo/ui/jetpack-compose/modifiers";
3import { StyleSheet, View, type ActivityIndicatorProps } from "react-native";
4import { useResolveClassNames, withUniwind } from "uniwind";
5
6function AndroidActivityIndicator({
7 animating = true,
8 color,
9 colorClassName: _colorClassName,
10 className: _className,
11 hidesWhenStopped: _hidesWhenStopped,
12 size = "small",
13 style,
14 ...viewProps
15}: ActivityIndicatorProps) {
16 const { color: primaryColor } = useResolveClassNames("text-primary");
17 const diameter = typeof size === "number" ? size : size === "large" ? 48 : 20;
18
19 return (
20 <View
21 accessible={animating}
22 accessibilityRole="progressbar"
23 accessibilityState={{ busy: animating }}
24 {...viewProps}
25 style={[styles.container, style]}
26 >
27 <View
28 pointerEvents="none"
29 importantForAccessibility="no-hide-descendants"
30 style={{ width: diameter, height: diameter }}
31 >
32 {animating ? (
33 <Host ignoreSafeAreaKeyboardInsets style={{ flex: 1 }}>
34 <LoadingIndicator
35 color={color ?? primaryColor}
36 modifiers={[composeSize(diameter, diameter)]}
37 />
38 </Host>
39 ) : null}
40 </View>
41 </View>
42 );
43}
44
45export const AppActivityIndicator = withUniwind(AndroidActivityIndicator);
46
47const styles = StyleSheet.create({
48 container: { alignItems: "center", justifyContent: "center" },
49});
apps/mobile/src/components/AppActivityIndicator.tsx created+3
...@@ -0,0 +1,3 @@
1import { ActivityIndicator } from "react-native";
2
3export const AppActivityIndicator = ActivityIndicator;
apps/mobile/src/components/LoadingScreen.tsx+3-2
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1import { ActivityIndicator, StatusBar, View } from "react-native";1import { StatusBar, View } from "react-native";
2import { useSafeAreaInsets } from "react-native-safe-area-context";2import { useSafeAreaInsets } from "react-native-safe-area-context";
3import { AppActivityIndicator } from "./AppActivityIndicator";
3import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";4import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
45
5import { AppText as Text } from "./AppText";6import { AppText as Text } from "./AppText";
...@@ -26,7 +27,7 @@ export function LoadingScreen(props: {...@@ -26,7 +27,7 @@ export function LoadingScreen(props: {
26 {messagePlacement === "above-spinner" ? (27 {messagePlacement === "above-spinner" ? (
27 <Text className="font-t3-bold text-lg text-foreground">{props.message}</Text>28 <Text className="font-t3-bold text-lg text-foreground">{props.message}</Text>
28 ) : null}29 ) : null}
29 <ActivityIndicator size="large" />30 <AppActivityIndicator size="large" />
30 {messagePlacement === "below-spinner" ? (31 {messagePlacement === "below-spinner" ? (
31 <Text className="font-t3-bold text-lg text-foreground">{props.message}</Text>32 <Text className="font-t3-bold text-lg text-foreground">{props.message}</Text>
32 ) : null}33 ) : null}
apps/mobile/src/components/MaterialButton.android.tsx+2-3
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1import {1import {
2 Box,2 Box,
3 Button,3 Button,
4 CircularProgressIndicator,4 LoadingIndicator,
5 FilledTonalButton,5 FilledTonalButton,
6 Host,6 Host,
7 Row,7 Row,
...@@ -78,9 +78,8 @@ export function MaterialButton(props: MaterialButtonProps) {...@@ -78,9 +78,8 @@ export function MaterialButton(props: MaterialButtonProps) {
78 <Row verticalAlignment="center">78 <Row verticalAlignment="center">
79 {props.loading ? (79 {props.loading ? (
80 <>80 <>
81 <CircularProgressIndicator81 <LoadingIndicator
82 modifiers={[size(mediumIconSize, mediumIconSize)]}82 modifiers={[size(mediumIconSize, mediumIconSize)]}
83 strokeWidth={2}
84 color={colors["--color-foreground-muted"]}83 color={colors["--color-foreground-muted"]}
85 />84 />
86 <Box modifiers={[size(8 * scale, 1)]} />85 <Box modifiers={[size(8 * scale, 1)]} />
apps/mobile/src/components/MediaVideoPlayer.tsx+4-3
...@@ -2,8 +2,9 @@ import { useIsFocused } from "@react-navigation/native";...@@ -2,8 +2,9 @@ import { useIsFocused } from "@react-navigation/native";
2import { useEvent } from "expo";2import { useEvent } from "expo";
3import { useVideoPlayer, VideoView } from "expo-video";3import { useVideoPlayer, VideoView } from "expo-video";
4import { useEffect, useEffectEvent, useRef, useState } from "react";4import { useEffect, useEffectEvent, useRef, useState } from "react";
5import { ActivityIndicator, AppState, Pressable, View } from "react-native";5import { AppState, Pressable, View } from "react-native";
66
7import { AppActivityIndicator } from "./AppActivityIndicator";
7import { AppText } from "./AppText";8import { AppText } from "./AppText";
8import { SymbolView } from "./AppSymbol";9import { SymbolView } from "./AppSymbol";
9import { VideoThumbnailImage } from "./VideoThumbnailImage";10import { VideoThumbnailImage } from "./VideoThumbnailImage";
...@@ -96,7 +97,7 @@ function LoadedMediaVideo(props: {...@@ -96,7 +97,7 @@ function LoadedMediaVideo(props: {
96 </View>97 </View>
97 ) : loadState === "pending" || status === "loading" ? (98 ) : loadState === "pending" || status === "loading" ? (
98 <View pointerEvents="none" className="absolute inset-0 items-center justify-center">99 <View pointerEvents="none" className="absolute inset-0 items-center justify-center">
99 <ActivityIndicator color="#ffffff" accessibilityLabel="Loading video" />100 <AppActivityIndicator color="#ffffff" accessibilityLabel="Loading video" />
100 </View>101 </View>
101 ) : null}102 ) : null}
102 </View>103 </View>
...@@ -160,7 +161,7 @@ function MediaVideoPlayerContent(props: MediaVideoPlayerProps) {...@@ -160,7 +161,7 @@ function MediaVideoPlayerContent(props: MediaVideoPlayerProps) {
160 {props.unavailable ? (161 {props.unavailable ? (
161 <AppText className="text-sm text-white/80">Video unavailable</AppText>162 <AppText className="text-sm text-white/80">Video unavailable</AppText>
162 ) : props.uri === null ? (163 ) : props.uri === null ? (
163 <ActivityIndicator color="#ffffff" accessibilityLabel="Loading video" />164 <AppActivityIndicator color="#ffffff" accessibilityLabel="Loading video" />
164 ) : (165 ) : (
165 <View className="size-12 items-center justify-center rounded-full bg-black/60">166 <View className="size-12 items-center justify-center rounded-full bg-black/60">
166 <SymbolView name="play" size={28} tintColor="#ffffff" type="monochrome" />167 <SymbolView name="play" size={28} tintColor="#ffffff" type="monochrome" />
apps/mobile/src/components/ProjectCloneBanner.tsx+3-2
...@@ -3,8 +3,9 @@ import {...@@ -3,8 +3,9 @@ import {
3 projectCloneProgressSummary,3 projectCloneProgressSummary,
4 type ProjectCloneSnapshot,4 type ProjectCloneSnapshot,
5} from "@t3tools/contracts";5} from "@t3tools/contracts";
6import { ActivityIndicator, Pressable, View } from "react-native";6import { Pressable, View } from "react-native";
77
8import { AppActivityIndicator } from "./AppActivityIndicator";
8import { cn } from "../lib/cn";9import { cn } from "../lib/cn";
9import { AppText as Text } from "./AppText";10import { AppText as Text } from "./AppText";
1011
...@@ -24,7 +25,7 @@ export function ProjectCloneBanner(props: {...@@ -24,7 +25,7 @@ export function ProjectCloneBanner(props: {
24 if (clone.phase === "running") {25 if (clone.phase === "running") {
25 return (26 return (
26 <View className="flex-row items-center gap-3 rounded-2xl border border-border bg-card px-3.5 py-3">27 <View className="flex-row items-center gap-3 rounded-2xl border border-border bg-card px-3.5 py-3">
27 <ActivityIndicator size="small" />28 <AppActivityIndicator size="small" />
28 <View className="min-w-0 flex-1">29 <View className="min-w-0 flex-1">
29 <Text className="font-t3-medium text-sm" numberOfLines={1}>30 <Text className="font-t3-medium text-sm" numberOfLines={1}>
30 Cloning {name}31 Cloning {name}
apps/mobile/src/components/VideoPreviewModal.tsx+3-2
...@@ -1,9 +1,10 @@...@@ -1,9 +1,10 @@
1import { useIsFocused } from "@react-navigation/native";1import { useIsFocused } from "@react-navigation/native";
2import { videoMimeType } from "@t3tools/shared/video";2import { videoMimeType } from "@t3tools/shared/video";
3import { useEffect, useRef, useState } from "react";3import { useEffect, useRef, useState } from "react";
4import { ActivityIndicator, Keyboard, Modal, Pressable, View } from "react-native";4import { Keyboard, Modal, Pressable, View } from "react-native";
5import { useSafeAreaInsets } from "react-native-safe-area-context";5import { useSafeAreaInsets } from "react-native-safe-area-context";
66
7import { AppActivityIndicator } from "./AppActivityIndicator";
7import { loadLocalAttachmentPreview } from "../lib/localAttachmentPreview";8import { loadLocalAttachmentPreview } from "../lib/localAttachmentPreview";
8import { useMediaActions } from "../state/mediaActions";9import { useMediaActions } from "../state/mediaActions";
9import { type MediaActionsSource } from "../lib/mediaActionsSource";10import { type MediaActionsSource } from "../lib/mediaActionsSource";
...@@ -165,7 +166,7 @@ function OpenVideoPreviewModal(props: {...@@ -165,7 +166,7 @@ function OpenVideoPreviewModal(props: {
165 <MediaSourceCaption source={mediaActions.title} />166 <MediaSourceCaption source={mediaActions.title} />
166 {playback.uri === null && !playback.unavailable ? (167 {playback.uri === null && !playback.unavailable ? (
167 <View className="flex-1 items-center justify-center gap-3 px-6">168 <View className="flex-1 items-center justify-center gap-3 px-6">
168 <ActivityIndicator color="#ffffff" />169 <AppActivityIndicator color="#ffffff" />
169 <AppText className="text-sm text-white/80">Loading video...</AppText>170 <AppText className="text-sm text-white/80">Loading video...</AppText>
170 </View>171 </View>
171 ) : (172 ) : (
apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx+3-9
...@@ -10,17 +10,11 @@ import {...@@ -10,17 +10,11 @@ import {
10 resolveEnvironmentMachineKind,10 resolveEnvironmentMachineKind,
11} from "@t3tools/contracts";11} from "@t3tools/contracts";
12import { useNavigation } from "@react-navigation/native";12import { useNavigation } from "@react-navigation/native";
13import { AppActivityIndicator } from "../../components/AppActivityIndicator";
13import { ScreenHeader } from "../../components/ScreenHeader";14import { ScreenHeader } from "../../components/ScreenHeader";
14import { SymbolView } from "../../components/AppSymbol";15import { SymbolView } from "../../components/AppSymbol";
15import { useCallback, useMemo, useRef, type ComponentProps } from "react";16import { useCallback, useMemo, useRef, type ComponentProps } from "react";
16import {17import { Platform, Pressable, RefreshControl, useWindowDimensions, View } from "react-native";
17 ActivityIndicator,
18 Platform,
19 Pressable,
20 RefreshControl,
21 useWindowDimensions,
22 View,
23} from "react-native";
24import { GestureDetector, useNativeGesture } from "react-native-gesture-handler";18import { GestureDetector, useNativeGesture } from "react-native-gesture-handler";
25import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable";19import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable";
2620
...@@ -399,7 +393,7 @@ export function ArchivedThreadsScreen(props: {...@@ -399,7 +393,7 @@ export function ArchivedThreadsScreen(props: {
399 if (isInitialLoad) {393 if (isInitialLoad) {
400 return (394 return (
401 <View className="items-center py-16">395 <View className="items-center py-16">
402 <ActivityIndicator colorClassName="accent-icon" />396 <AppActivityIndicator colorClassName="accent-icon" />
403 <Text className="mt-3 text-sm text-foreground-muted">Loading archive...</Text>397 <Text className="mt-3 text-sm text-foreground-muted">Loading archive...</Text>
404 </View>398 </View>
405 );399 );
apps/mobile/src/features/cloud/T3ConnectProfilePage.tsx+7-11
...@@ -7,16 +7,9 @@ import type { MenuAction } from "@react-native-menu/menu";...@@ -7,16 +7,9 @@ import type { MenuAction } from "@react-native-menu/menu";
7import type { EnvironmentId } from "@t3tools/contracts";7import type { EnvironmentId } from "@t3tools/contracts";
8import type { RelayClientEnvironmentRecord } from "@t3tools/contracts/relay";8import type { RelayClientEnvironmentRecord } from "@t3tools/contracts/relay";
9import { type ReactNode, useRef, useState } from "react";9import { type ReactNode, useRef, useState } from "react";
10import {10import { Alert, Pressable, RefreshControl, ScrollView, Text, View } from "react-native";
11 ActivityIndicator,
12 Alert,
13 Pressable,
14 RefreshControl,
15 ScrollView,
16 Text,
17 View,
18} from "react-native";
1911
12import { AppActivityIndicator } from "../../components/AppActivityIndicator";
20import { SymbolView } from "../../components/AppSymbol";13import { SymbolView } from "../../components/AppSymbol";
21import { showConfirmDialog } from "../../components/ConfirmDialogHost";14import { showConfirmDialog } from "../../components/ConfirmDialogHost";
22import { ControlPillMenu } from "../../components/ControlPill";15import { ControlPillMenu } from "../../components/ControlPill";
...@@ -167,7 +160,7 @@ export function T3ConnectProfilePage() {...@@ -167,7 +160,7 @@ export function T3ConnectProfilePage() {
167 </>160 </>
168 ) : isInitialLoad ? (161 ) : isInitialLoad ? (
169 <View className="flex-row items-center gap-3 px-6 py-4">162 <View className="flex-row items-center gap-3 px-6 py-4">
170 <ActivityIndicator colorClassName={"accent-clerk-foreground-muted"} size="small" />163 <AppActivityIndicator colorClassName={"accent-clerk-foreground-muted"} size="small" />
171 <Text className="text-base text-clerk-foreground-muted">Loading environments</Text>164 <Text className="text-base text-clerk-foreground-muted">Loading environments</Text>
172 </View>165 </View>
173 ) : environments.length > 0 ? (166 ) : environments.length > 0 ? (
...@@ -178,7 +171,10 @@ export function T3ConnectProfilePage() {...@@ -178,7 +171,10 @@ export function T3ConnectProfilePage() {
178 subtitle={`${linkedAtLabel(environment.linkedAt)} · ${endpointLabel(environment)}`}171 subtitle={`${linkedAtLabel(environment.linkedAt)} · ${endpointLabel(environment)}`}
179 accessory={172 accessory={
180 deregisteringEnvironmentId === environment.environmentId ? (173 deregisteringEnvironmentId === environment.environmentId ? (
181 <ActivityIndicator colorClassName={"accent-clerk-foreground-muted"} size="small" />174 <AppActivityIndicator
175 colorClassName={"accent-clerk-foreground-muted"}
176 size="small"
177 />
182 ) : (178 ) : (
183 <ControlPillMenu179 <ControlPillMenu
184 actions={ENVIRONMENT_MENU_ACTIONS}180 actions={ENVIRONMENT_MENU_ACTIONS}
apps/mobile/src/features/connection/CloudEnvironmentRows.tsx+3-2
...@@ -1,3 +1,4 @@...@@ -1,3 +1,4 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
1import { ConnectionTraceId } from "./ConnectionTraceId";2import { ConnectionTraceId } from "./ConnectionTraceId";
2import { useAuth } from "@clerk/expo";3import { useAuth } from "@clerk/expo";
3import { SymbolView } from "../../components/AppSymbol";4import { SymbolView } from "../../components/AppSymbol";
...@@ -13,7 +14,7 @@ import {...@@ -13,7 +14,7 @@ import {
13} from "@t3tools/contracts";14} from "@t3tools/contracts";
14import { useAtomValue } from "@effect/atom-react";15import { useAtomValue } from "@effect/atom-react";
15import { useCallback, useState } from "react";16import { useCallback, useState } from "react";
16import { ActivityIndicator, Pressable, type TextLayoutEvent, View } from "react-native";17import { Pressable, type TextLayoutEvent, View } from "react-native";
1718
18import { AppText as Text } from "../../components/AppText";19import { AppText as Text } from "../../components/AppText";
19import { EnvironmentMachineSymbol } from "../../components/EnvironmentMachineSymbol";20import { EnvironmentMachineSymbol } from "../../components/EnvironmentMachineSymbol";
...@@ -151,7 +152,7 @@ function CloudEnvironmentRowsContent(...@@ -151,7 +152,7 @@ function CloudEnvironmentRowsContent(
151 </View>152 </View>
152 ) : controller.relayDiscovery.isRefreshing ? (153 ) : controller.relayDiscovery.isRefreshing ? (
153 <View collapsable={false} className="items-center gap-3 rounded-[24px] bg-grouped-card p-6">154 <View collapsable={false} className="items-center gap-3 rounded-[24px] bg-grouped-card p-6">
154 <ActivityIndicator colorClassName={"accent-icon"} />155 <AppActivityIndicator colorClassName={"accent-icon"} />
155 <Text className="text-center text-sm leading-normal text-foreground-muted">156 <Text className="text-center text-sm leading-normal text-foreground-muted">
156 Loading linked cloud environments.157 Loading linked cloud environments.
157 </Text>158 </Text>
apps/mobile/src/features/connection/EnvironmentConnectionNotice.tsx+3-2
...@@ -1,10 +1,11 @@...@@ -1,10 +1,11 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
1import { ConnectionTraceId } from "./ConnectionTraceId";2import { ConnectionTraceId } from "./ConnectionTraceId";
2import {3import {
3 type EnvironmentConnectionPhase,4 type EnvironmentConnectionPhase,
4 type EnvironmentConnectionPresentation,5 type EnvironmentConnectionPresentation,
5} from "@t3tools/client-runtime/connection";6} from "@t3tools/client-runtime/connection";
6import { SymbolView } from "../../components/AppSymbol";7import { SymbolView } from "../../components/AppSymbol";
7import { ActivityIndicator, Pressable, View } from "react-native";8import { Pressable, View } from "react-native";
89
9import { AppText as Text } from "../../components/AppText";10import { AppText as Text } from "../../components/AppText";
1011
...@@ -65,7 +66,7 @@ export function EnvironmentConnectionNotice(props: {...@@ -65,7 +66,7 @@ export function EnvironmentConnectionNotice(props: {
65 <View className="flex-1 items-center justify-center px-8">66 <View className="flex-1 items-center justify-center px-8">
66 <View className="max-w-[320px] items-center gap-3">67 <View className="max-w-[320px] items-center gap-3">
67 {isRetrying ? (68 {isRetrying ? (
68 <ActivityIndicator size="small" colorClassName={"accent-icon-muted"} />69 <AppActivityIndicator size="small" colorClassName={"accent-icon-muted"} />
69 ) : (70 ) : (
70 <SymbolView71 <SymbolView
71 name={props.connection.phase === "offline" ? "wifi.slash" : "bolt.horizontal.circle"}72 name={props.connection.phase === "offline" ? "wifi.slash" : "bolt.horizontal.circle"}
apps/mobile/src/features/devices/DevicePreviewRouteScreen.tsx+4-11
...@@ -9,19 +9,12 @@ import * as Cause from "effect/Cause";...@@ -9,19 +9,12 @@ import * as Cause from "effect/Cause";
9import { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from "react";9import { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from "react";
10import * as Haptics from "expo-haptics";10import * as Haptics from "expo-haptics";
11import { Accelerometer } from "expo-sensors";11import { Accelerometer } from "expo-sensors";
12import {12import { Alert, AppState, BackHandler, Pressable, StatusBar, View } from "react-native";
13 ActivityIndicator,
14 Alert,
15 AppState,
16 BackHandler,
17 Pressable,
18 StatusBar,
19 View,
20} from "react-native";
21import Animated, { FadeIn, FadeOut, ReduceMotion } from "react-native-reanimated";13import Animated, { FadeIn, FadeOut, ReduceMotion } from "react-native-reanimated";
22import { useSafeAreaInsets } from "react-native-safe-area-context";14import { useSafeAreaInsets } from "react-native-safe-area-context";
2315
24import { SymbolView } from "../../components/AppSymbol";16import { SymbolView } from "../../components/AppSymbol";
17import { AppActivityIndicator } from "../../components/AppActivityIndicator";
25import { AppText } from "../../components/AppText";18import { AppText } from "../../components/AppText";
26import { ControlPill, ControlPillMenu } from "../../components/ControlPill";19import { ControlPill, ControlPillMenu } from "../../components/ControlPill";
27import { GlassSurface } from "../../components/GlassSurface";20import { GlassSurface } from "../../components/GlassSurface";
...@@ -295,7 +288,7 @@ function DevicePreviewScreen({...@@ -295,7 +288,7 @@ function DevicePreviewScreen({
295 </Pressable>288 </Pressable>
296 </>289 </>
297 ) : focused && foreground ? (290 ) : focused && foreground ? (
298 <ActivityIndicator color={themeVariables["--color-icon"]} />291 <AppActivityIndicator color={themeVariables["--color-icon"]} />
299 ) : null}292 ) : null}
300 </View>293 </View>
301 )}294 )}
...@@ -428,7 +421,7 @@ function OpenDevicePreview({...@@ -428,7 +421,7 @@ function OpenDevicePreview({
428 </>421 </>
429 ) : (422 ) : (
430 <>423 <>
431 <ActivityIndicator color={themeVariables["--color-icon"]} />424 <AppActivityIndicator color={themeVariables["--color-icon"]} />
432 <AppText className="text-sm text-foreground-muted">Connecting to device...</AppText>425 <AppText className="text-sm text-foreground-muted">Connecting to device...</AppText>
433 </>426 </>
434 )}427 )}
apps/mobile/src/features/devices/DeviceStreamWebView.tsx+3-2
...@@ -9,10 +9,11 @@ import {...@@ -9,10 +9,11 @@ import {
9 useState,9 useState,
10 type Ref,10 type Ref,
11} from "react";11} from "react";
12import { ActivityIndicator, Platform, Pressable, View } from "react-native";12import { Platform, Pressable, View } from "react-native";
13import { WebView } from "react-native-webview";13import { WebView } from "react-native-webview";
14import type { DeviceStreamStatus } from "@t3tools/client-runtime/device/stream";14import type { DeviceStreamStatus } from "@t3tools/client-runtime/device/stream";
1515
16import { AppActivityIndicator } from "../../components/AppActivityIndicator";
16import { AppText } from "../../components/AppText";17import { AppText } from "../../components/AppText";
1718
18import {19import {
...@@ -187,7 +188,7 @@ function DeviceStreamDocumentView({...@@ -187,7 +188,7 @@ function DeviceStreamDocumentView({
187 className="absolute inset-0 items-center justify-center gap-4 px-6"188 className="absolute inset-0 items-center justify-center gap-4 px-6"
188 style={{ backgroundColor: background }}189 style={{ backgroundColor: background }}
189 >190 >
190 {status === "connecting" ? <ActivityIndicator colorClassName="accent-icon" /> : null}191 {status === "connecting" ? <AppActivityIndicator colorClassName="accent-icon" /> : null}
191 <AppText192 <AppText
192 accessibilityLiveRegion="polite"193 accessibilityLiveRegion="polite"
193 className="text-center text-sm text-foreground-muted"194 className="text-center text-sm text-foreground-muted"
apps/mobile/src/features/diagnostics/SettingsDiagnosticsRouteScreen.tsx+3-2
...@@ -1,8 +1,9 @@...@@ -1,8 +1,9 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
1import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";2import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
2import Constants from "expo-constants";3import Constants from "expo-constants";
3import * as Updates from "expo-updates";4import * as Updates from "expo-updates";
4import { useEffect, useState } from "react";5import { useEffect, useState } from "react";
5import { ActivityIndicator, Platform, View } from "react-native";6import { Platform, View } from "react-native";
6import { useSafeAreaInsets } from "react-native-safe-area-context";7import { useSafeAreaInsets } from "react-native-safe-area-context";
78
8import { SymbolView } from "../../components/AppSymbol";9import { SymbolView } from "../../components/AppSymbol";
...@@ -85,7 +86,7 @@ export function SettingsDiagnosticsRouteScreen() {...@@ -85,7 +86,7 @@ export function SettingsDiagnosticsRouteScreen() {
85 <SettingsSection title="Startup crashes">86 <SettingsSection title="Startup crashes">
86 {state.status === "loading" ? (87 {state.status === "loading" ? (
87 <View className="items-center gap-3 px-6 py-8">88 <View className="items-center gap-3 px-6 py-8">
88 <ActivityIndicator />89 <AppActivityIndicator />
89 <Text className="text-center text-sm text-foreground-muted">Reading crash log…</Text>90 <Text className="text-center text-sm text-foreground-muted">Reading crash log…</Text>
90 </View>91 </View>
91 ) : state.status === "unavailable" ? (92 ) : state.status === "unavailable" ? (
apps/mobile/src/features/files/FilePreviewFeedback.tsx+3-2
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1import { ActivityIndicator, View } from "react-native";1import { View } from "react-native";
22
3import { AppActivityIndicator } from "../../components/AppActivityIndicator";
3import { AppText as Text } from "../../components/AppText";4import { AppText as Text } from "../../components/AppText";
4import { cn } from "../../lib/cn";5import { cn } from "../../lib/cn";
56
...@@ -14,7 +15,7 @@ export function FilePreviewLoading(props: {...@@ -14,7 +15,7 @@ export function FilePreviewLoading(props: {
14 props.background === "card" ? "bg-card" : "bg-sheet",15 props.background === "card" ? "bg-card" : "bg-sheet",
15 )}16 )}
16 >17 >
17 <ActivityIndicator />18 <AppActivityIndicator />
18 <Text className="text-center text-sm text-foreground-muted">{props.message}</Text>19 <Text className="text-center text-sm text-foreground-muted">{props.message}</Text>
19 </View>20 </View>
20 );21 );
apps/mobile/src/features/files/FileTreeBrowser.tsx+4-3
...@@ -1,8 +1,9 @@...@@ -1,8 +1,9 @@
1import { LegendList } from "@legendapp/list/react-native";1import { LegendList } from "@legendapp/list/react-native";
2import type { ProjectEntry } from "@t3tools/contracts";2import type { ProjectEntry } from "@t3tools/contracts";
3import { AppActivityIndicator } from "../../components/AppActivityIndicator";
3import { SymbolView } from "../../components/AppSymbol";4import { SymbolView } from "../../components/AppSymbol";
4import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";5import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
5import { ActivityIndicator, Platform, Pressable, RefreshControl, View } from "react-native";6import { Platform, Pressable, RefreshControl, View } from "react-native";
6import { useSafeAreaInsets } from "react-native-safe-area-context";7import { useSafeAreaInsets } from "react-native-safe-area-context";
78
8import { AppText as Text } from "../../components/AppText";9import { AppText as Text } from "../../components/AppText";
...@@ -96,7 +97,7 @@ const FileTreeRow = memo(function FileTreeRow(props: {...@@ -96,7 +97,7 @@ const FileTreeRow = memo(function FileTreeRow(props: {
96 {node.name}97 {node.name}
97 </Text>98 </Text>
98 {node.kind === "directory" && props.expanded && props.loading ? (99 {node.kind === "directory" && props.expanded && props.loading ? (
99 <ActivityIndicator size="small" accessibilityLabel={`Loading ${node.name}`} />100 <AppActivityIndicator size="small" accessibilityLabel={`Loading ${node.name}`} />
100 ) : null}101 ) : null}
101 </Pressable>102 </Pressable>
102 );103 );
...@@ -296,7 +297,7 @@ export function FileTreeBrowser(props: {...@@ -296,7 +297,7 @@ export function FileTreeBrowser(props: {
296 </Pressable>297 </Pressable>
297 </>298 </>
298 ) : props.isPending ? (299 ) : props.isPending ? (
299 <ActivityIndicator size="small" />300 <AppActivityIndicator size="small" />
300 ) : (301 ) : (
301 <>302 <>
302 <Text className="text-sm font-t3-bold text-foreground">No files found</Text>303 <Text className="text-sm font-t3-bold text-foreground">No files found</Text>
apps/mobile/src/features/files/WorkspaceFileWebPreview.tsx+3-2
...@@ -1,7 +1,8 @@...@@ -1,7 +1,8 @@
1import { useState } from "react";1import { useState } from "react";
2import { ActivityIndicator, View } from "react-native";2import { View } from "react-native";
3import { WebView } from "react-native-webview";3import { WebView } from "react-native-webview";
44
5import { AppActivityIndicator } from "../../components/AppActivityIndicator";
5import { AppText as Text } from "../../components/AppText";6import { AppText as Text } from "../../components/AppText";
6import { FilePreviewLoading } from "./FilePreviewFeedback";7import { FilePreviewLoading } from "./FilePreviewFeedback";
7import { LoadingStrip } from "../../components/LoadingStrip";8import { LoadingStrip } from "../../components/LoadingStrip";
...@@ -46,7 +47,7 @@ export function WorkspaceFileWebPreview(props: { readonly uri: string | null })...@@ -46,7 +47,7 @@ export function WorkspaceFileWebPreview(props: { readonly uri: string | null })
46 }}47 }}
47 renderLoading={() => (48 renderLoading={() => (
48 <View className="absolute inset-0 items-center justify-center bg-card">49 <View className="absolute inset-0 items-center justify-center bg-card">
49 <ActivityIndicator />50 <AppActivityIndicator />
50 </View>51 </View>
51 )}52 )}
52 style={{ flex: 1, backgroundColor: "transparent" }}53 style={{ flex: 1, backgroundColor: "transparent" }}
apps/mobile/src/features/home/HomeScreen.tsx+2-2
...@@ -1,3 +1,4 @@...@@ -1,3 +1,4 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
1import { useAndroidControlSizing } from "../../components/useAndroidControlSizing";2import { useAndroidControlSizing } from "../../components/useAndroidControlSizing";
2import type { ThreadMoveDestination } from "../threads/threadOrder";3import type { ThreadMoveDestination } from "../threads/threadOrder";
3import { computeThreadMoveAvailability } from "../threads/threadOrder";4import { computeThreadMoveAvailability } from "../threads/threadOrder";
...@@ -15,7 +16,6 @@ import { useAtomValue } from "@effect/atom-react";...@@ -15,7 +16,6 @@ import { useAtomValue } from "@effect/atom-react";
15import { useFocusEffect } from "@react-navigation/native";16import { useFocusEffect } from "@react-navigation/native";
16import { use, useCallback, useEffect, useMemo, useRef, useState, type ComponentProps } from "react";17import { use, useCallback, useEffect, useMemo, useRef, useState, type ComponentProps } from "react";
17import {18import {
18 ActivityIndicator,
19 Platform,19 Platform,
20 ScrollView,20 ScrollView,
21 View,21 View,
...@@ -947,7 +947,7 @@ export function HomeScreen(props: HomeScreenProps) {...@@ -947,7 +947,7 @@ export function HomeScreen(props: HomeScreenProps) {
947 />947 />
948 {emptyState.loading ? (948 {emptyState.loading ? (
949 <View className="mt-4 items-center">949 <View className="mt-4 items-center">
950 <ActivityIndicator colorClassName="accent-icon-muted" />950 <AppActivityIndicator colorClassName="accent-icon-muted" />
951 </View>951 </View>
952 ) : null}952 ) : null}
953 </View>953 </View>
apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx+3-2
...@@ -1,7 +1,8 @@...@@ -1,7 +1,8 @@
1import type { NativeStackNavigationOptions } from "@react-navigation/native-stack";1import type { NativeStackNavigationOptions } from "@react-navigation/native-stack";
2import { useEffect, useRef, useState, type ReactNode } from "react";2import { useEffect, useRef, useState, type ReactNode } from "react";
3import { ActivityIndicator, Animated, Platform, Pressable, View } from "react-native";3import { Animated, Platform, Pressable, View } from "react-native";
44
5import { AppActivityIndicator } from "../../components/AppActivityIndicator";
5import { SymbolView } from "../../components/AppSymbol";6import { SymbolView } from "../../components/AppSymbol";
6import { AppText as Text } from "../../components/AppText";7import { AppText as Text } from "../../components/AppText";
7import { useAndroidControlSizing } from "../../components/useAndroidControlSizing";8import { useAndroidControlSizing } from "../../components/useAndroidControlSizing";
...@@ -134,7 +135,7 @@ export function WorkspaceConnectionTitle(props: {...@@ -134,7 +135,7 @@ export function WorkspaceConnectionTitle(props: {
134 ]}135 ]}
135 >136 >
136 {status.showsProgress ? (137 {status.showsProgress ? (
137 <ActivityIndicator138 <AppActivityIndicator
138 colorClassName={"accent-icon-muted"}139 colorClassName={"accent-icon-muted"}
139 size={Platform.OS === "android" ? Math.round(20 * scale) : "small"}140 size={Platform.OS === "android" ? Math.round(20 * scale) : "small"}
140 />141 />
apps/mobile/src/features/projects/AddProjectScreen.tsx+5-4
...@@ -1,3 +1,4 @@...@@ -1,3 +1,4 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
1import { MaterialListRow } from "../../components/MaterialListRow";2import { MaterialListRow } from "../../components/MaterialListRow";
2import { SettingsScreen } from "../settings/components/SettingsScreen";3import { SettingsScreen } from "../settings/components/SettingsScreen";
3import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";4import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
...@@ -55,7 +56,7 @@ import {...@@ -55,7 +56,7 @@ import {
55import { CommonActions, StackActions, useNavigation } from "@react-navigation/native";56import { CommonActions, StackActions, useNavigation } from "@react-navigation/native";
56import { SymbolView } from "../../components/AppSymbol";57import { SymbolView } from "../../components/AppSymbol";
57import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";58import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
58import { Platform, ActivityIndicator, Alert, Pressable, View } from "react-native";59import { Platform, Alert, Pressable, View } from "react-native";
59import { useSafeAreaInsets } from "react-native-safe-area-context";60import { useSafeAreaInsets } from "react-native-safe-area-context";
60import * as Arr from "effect/Array";61import * as Arr from "effect/Array";
61import * as Cause from "effect/Cause";62import * as Cause from "effect/Cause";
...@@ -265,7 +266,7 @@ function PrimaryActionButton(props: {...@@ -265,7 +266,7 @@ function PrimaryActionButton(props: {
265 className="h-12 items-center justify-center rounded-full bg-primary active:opacity-70 disabled:opacity-45"266 className="h-12 items-center justify-center rounded-full bg-primary active:opacity-70 disabled:opacity-45"
266 >267 >
267 {props.loading ? (268 {props.loading ? (
268 <ActivityIndicator colorClassName={String("accent-primary-foreground")} />269 <AppActivityIndicator colorClassName={String("accent-primary-foreground")} />
269 ) : (270 ) : (
270 <Text className="text-base font-t3-bold text-primary-foreground">{props.label}</Text>271 <Text className="text-base font-t3-bold text-primary-foreground">{props.label}</Text>
271 )}272 )}
...@@ -665,7 +666,7 @@ export function AddProjectSourceScreen() {...@@ -665,7 +666,7 @@ export function AddProjectSourceScreen() {
665 )}666 )}
666 </ListSection>667 </ListSection>
667 {discoveryState.isPending ? (668 {discoveryState.isPending ? (
668 <ActivityIndicator colorClassName="accent-icon-muted" />669 <AppActivityIndicator colorClassName="accent-icon-muted" />
669 ) : null}670 ) : null}
670 </>671 </>
671 ) : null}672 ) : null}
...@@ -918,7 +919,7 @@ function FolderBrowser(props: {...@@ -918,7 +919,7 @@ function FolderBrowser(props: {
918 <ListSection>919 <ListSection>
919 {fileAccess.isPending || (browseState.isPending && browseState.data === null) ? (920 {fileAccess.isPending || (browseState.isPending && browseState.data === null) ? (
920 <View className="items-center py-5">921 <View className="items-center py-5">
921 <ActivityIndicator colorClassName="accent-icon-muted" />922 <AppActivityIndicator colorClassName="accent-icon-muted" />
922 </View>923 </View>
923 ) : null}924 ) : null}
924 {browsePath.canBrowseUp ? (925 {browsePath.canBrowseUp ? (
apps/mobile/src/features/review/ReviewSheet.tsx+2-2
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1import type { EnvironmentId, ThreadId } from "@t3tools/contracts";1import type { EnvironmentId, ThreadId } from "@t3tools/contracts";
2import { formatDiffCount } from "@t3tools/client-runtime/diff-count";2import { formatDiffCount } from "@t3tools/client-runtime/diff-count";
3import { useNavigation, type StaticScreenProps } from "@react-navigation/native";3import { useNavigation, type StaticScreenProps } from "@react-navigation/native";
4import { AppActivityIndicator } from "../../components/AppActivityIndicator";
4import { nativeHeaderScrollEdgeEffects } from "../../native/StackHeader";5import { nativeHeaderScrollEdgeEffects } from "../../native/StackHeader";
5import { ScreenHeader } from "../../components/ScreenHeader";6import { ScreenHeader } from "../../components/ScreenHeader";
6import type { ScreenHeaderMenuItem } from "../../components/ScreenHeader.types";7import type { ScreenHeaderMenuItem } from "../../components/ScreenHeader.types";
...@@ -19,7 +20,6 @@ import {...@@ -19,7 +20,6 @@ import {
19 useState,20 useState,
20} from "react";21} from "react";
21import {22import {
22 ActivityIndicator,
23 FlatList,23 FlatList,
24 Platform,24 Platform,
25 Pressable,25 Pressable,
...@@ -855,7 +855,7 @@ export function ReviewSheet(props: ReviewSheetProps) {...@@ -855,7 +855,7 @@ export function ReviewSheet(props: ReviewSheetProps) {
855 Platform.OS !== "android" && "border-b border-border bg-card",855 Platform.OS !== "android" && "border-b border-border bg-card",
856 )}856 )}
857 >857 >
858 <ActivityIndicator size="small" />858 <AppActivityIndicator size="small" />
859 <Text className="text-xs text-foreground-muted">Loading diff…</Text>859 <Text className="text-xs text-foreground-muted">Loading diff…</Text>
860 </View>860 </View>
861 ) : parsedDiff.kind === "empty" ? (861 ) : parsedDiff.kind === "empty" ? (
apps/mobile/src/features/settings/SettingsClientStorageRouteScreen.tsx+3-2
...@@ -1,9 +1,10 @@...@@ -1,9 +1,10 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
1import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";2import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
2import { useAtomSet, useAtomValue } from "@effect/atom-react";3import { useAtomSet, useAtomValue } from "@effect/atom-react";
3import { type EnvironmentMachineKind, resolveEnvironmentMachineKind } from "@t3tools/contracts";4import { type EnvironmentMachineKind, resolveEnvironmentMachineKind } from "@t3tools/contracts";
4import { AsyncResult } from "effect/reactivity";5import { AsyncResult } from "effect/reactivity";
5import { useMemo } from "react";6import { useMemo } from "react";
6import { ActivityIndicator, Alert, Pressable, View } from "react-native";7import { Alert, Pressable, View } from "react-native";
7import { useSafeAreaInsets } from "react-native-safe-area-context";8import { useSafeAreaInsets } from "react-native-safe-area-context";
89
9import { AppText as Text } from "../../components/AppText";10import { AppText as Text } from "../../components/AppText";
...@@ -99,7 +100,7 @@ export function SettingsClientStorageRouteScreen() {...@@ -99,7 +100,7 @@ export function SettingsClientStorageRouteScreen() {
99 </View>100 </View>
100 ) : !summary ? (101 ) : !summary ? (
101 <View className="items-center gap-3 px-6 py-8">102 <View className="items-center gap-3 px-6 py-8">
102 <ActivityIndicator />103 <AppActivityIndicator />
103 <Text className="text-center text-sm text-foreground-muted">104 <Text className="text-center text-sm text-foreground-muted">
104 Inspecting cached data…105 Inspecting cached data…
105 </Text>106 </Text>
apps/mobile/src/features/settings/components/SettingsActionRow.tsx+5-2
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1import type { ComponentProps } from "react";1import type { ComponentProps } from "react";
2import { ActivityIndicator, Platform, Pressable } from "react-native";2import { Platform, Pressable } from "react-native";
33
4import { AppActivityIndicator } from "../../../components/AppActivityIndicator";
4import { SymbolView } from "../../../components/AppSymbol";5import { SymbolView } from "../../../components/AppSymbol";
5import { AppText as Text } from "../../../components/AppText";6import { AppText as Text } from "../../../components/AppText";
6import { MaterialListRow } from "../../../components/MaterialListRow";7import { MaterialListRow } from "../../../components/MaterialListRow";
...@@ -26,7 +27,9 @@ export function SettingsActionRow(props: {...@@ -26,7 +27,9 @@ export function SettingsActionRow(props: {
26 weight="regular"27 weight="regular"
27 />28 />
28 );29 );
29 const spinner = props.loading ? <ActivityIndicator colorClassName={iconColorClassName} /> : null;30 const spinner = props.loading ? (
31 <AppActivityIndicator colorClassName={iconColorClassName} />
32 ) : null;
3033
31 if (Platform.OS === "android") {34 if (Platform.OS === "android") {
32 return (35 return (
apps/mobile/src/features/settings/components/SettingsLegalDocumentRouteScreen.tsx+3-2
...@@ -1,8 +1,9 @@...@@ -1,8 +1,9 @@
1import { type NavigationProp, type ParamListBase, useNavigation } from "@react-navigation/native";1import { type NavigationProp, type ParamListBase, useNavigation } from "@react-navigation/native";
2import { useCallback, useLayoutEffect, useState } from "react";2import { useCallback, useLayoutEffect, useState } from "react";
3import { ActivityIndicator, Linking, Pressable, View } from "react-native";3import { Linking, Pressable, View } from "react-native";
4import { WebView } from "react-native-webview";4import { WebView } from "react-native-webview";
55
6import { AppActivityIndicator } from "../../../components/AppActivityIndicator";
6import { AppText as Text } from "../../../components/AppText";7import { AppText as Text } from "../../../components/AppText";
7import { LoadingStrip } from "../../../components/LoadingStrip";8import { LoadingStrip } from "../../../components/LoadingStrip";
8import { SymbolView } from "../../../components/AppSymbol";9import { SymbolView } from "../../../components/AppSymbol";
...@@ -172,7 +173,7 @@ export function SettingsLegalDocumentRouteScreen({...@@ -172,7 +173,7 @@ export function SettingsLegalDocumentRouteScreen({
172 }}173 }}
173 renderLoading={() => (174 renderLoading={() => (
174 <View className="absolute inset-0 items-center justify-center bg-sheet">175 <View className="absolute inset-0 items-center justify-center bg-sheet">
175 <ActivityIndicator />176 <AppActivityIndicator />
176 </View>177 </View>
177 )}178 )}
178 style={{ flex: 1, backgroundColor: "transparent" }}179 style={{ flex: 1, backgroundColor: "transparent" }}
apps/mobile/src/features/threads/GitActionProgressOverlay.tsx+3-2
...@@ -1,8 +1,9 @@...@@ -1,8 +1,9 @@
1import * as Haptics from "expo-haptics";1import * as Haptics from "expo-haptics";
2import { GlassView } from "expo-glass-effect";2import { GlassView } from "expo-glass-effect";
3import { AppActivityIndicator } from "../../components/AppActivityIndicator";
3import { SymbolView } from "../../components/AppSymbol";4import { SymbolView } from "../../components/AppSymbol";
4import { useCallback, useEffect, useRef } from "react";5import { useCallback, useEffect, useRef } from "react";
5import { ActivityIndicator, Pressable, StyleSheet, useColorScheme, View } from "react-native";6import { Pressable, StyleSheet, useColorScheme, View } from "react-native";
6import Animated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated";7import Animated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated";
7import { useSafeAreaInsets } from "react-native-safe-area-context";8import { useSafeAreaInsets } from "react-native-safe-area-context";
89
...@@ -158,7 +159,7 @@ function OverlayContent(props: { readonly progress: GitActionProgress }) {...@@ -158,7 +159,7 @@ function OverlayContent(props: { readonly progress: GitActionProgress }) {
158function OverlayIcon(props: { readonly phase: GitActionProgress["phase"] }) {159function OverlayIcon(props: { readonly phase: GitActionProgress["phase"] }) {
159 switch (props.phase) {160 switch (props.phase) {
160 case "running":161 case "running":
161 return <ActivityIndicator size="small" colorClassName={"accent-icon"} />;162 return <AppActivityIndicator size="small" colorClassName={"accent-icon"} />;
162 case "success":163 case "success":
163 return (164 return (
164 <View className="h-6 w-6 items-center justify-center rounded-full bg-green-500">165 <View className="h-6 w-6 items-center justify-center rounded-full bg-green-500">
apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx+4-11
...@@ -1,3 +1,4 @@...@@ -1,3 +1,4 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
1import { MaterialListRow } from "../../components/MaterialListRow";2import { MaterialListRow } from "../../components/MaterialListRow";
2import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell";3import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell";
3import { shouldCheckoutNewTaskBranch } from "./new-task-context-presentation";4import { shouldCheckoutNewTaskBranch } from "./new-task-context-presentation";
...@@ -11,15 +12,7 @@ import {...@@ -11,15 +12,7 @@ import {
11import * as Haptics from "expo-haptics";12import * as Haptics from "expo-haptics";
12import { useNavigation } from "@react-navigation/native";13import { useNavigation } from "@react-navigation/native";
13import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";14import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
14import {15import { Alert, Platform, Pressable, ScrollView, TextInput, View } from "react-native";
15 ActivityIndicator,
16 Alert,
17 Platform,
18 Pressable,
19 ScrollView,
20 TextInput,
21 View,
22} from "react-native";
23import { useSafeAreaInsets } from "react-native-safe-area-context";16import { useSafeAreaInsets } from "react-native-safe-area-context";
2417
25import { AndroidScreenHeader } from "../../components/AndroidScreenHeader";18import { AndroidScreenHeader } from "../../components/AndroidScreenHeader";
...@@ -486,7 +479,7 @@ export function BranchPickerScreen(props: {...@@ -486,7 +479,7 @@ export function BranchPickerScreen(props: {
486 : 0,479 : 0,
487 }}480 }}
488 >481 >
489 {props.loading ? <ActivityIndicator /> : null}482 {props.loading ? <AppActivityIndicator /> : null}
490 <Text className="text-center text-sm text-foreground-muted">483 <Text className="text-center text-sm text-foreground-muted">
491 {props.loading484 {props.loading
492 ? "Loading branches…"485 ? "Loading branches…"
...@@ -525,7 +518,7 @@ export function BranchPickerScreen(props: {...@@ -525,7 +518,7 @@ export function BranchPickerScreen(props: {
525 ListFooterComponent={518 ListFooterComponent={
526 props.refreshing ? (519 props.refreshing ? (
527 <View className="items-center py-4">520 <View className="items-center py-4">
528 <ActivityIndicator />521 <AppActivityIndicator />
529 </View>522 </View>
530 ) : null523 ) : null
531 }524 }
apps/mobile/src/features/threads/NewTaskRouteScreen.tsx+3-2
...@@ -1,3 +1,4 @@...@@ -1,3 +1,4 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
1import { MaterialListRow } from "../../components/MaterialListRow";2import { MaterialListRow } from "../../components/MaterialListRow";
2import { ScreenHeader } from "../../components/ScreenHeader";3import { ScreenHeader } from "../../components/ScreenHeader";
3import {4import {
...@@ -15,7 +16,7 @@ import {...@@ -15,7 +16,7 @@ import {
15} from "@t3tools/client-runtime/state/runtime";16} from "@t3tools/client-runtime/state/runtime";
16import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell";17import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell";
17import { useEffect, useRef, useState } from "react";18import { useEffect, useRef, useState } from "react";
18import { ActivityIndicator, Alert, Platform, Pressable, View } from "react-native";19import { Alert, Platform, Pressable, View } from "react-native";
19import { useSafeAreaInsets } from "react-native-safe-area-context";20import { useSafeAreaInsets } from "react-native-safe-area-context";
20import { cn } from "../../lib/cn";21import { cn } from "../../lib/cn";
21import { MaterialScreenContent } from "../../components/MaterialScreenContent";22import { MaterialScreenContent } from "../../components/MaterialScreenContent";
...@@ -369,7 +370,7 @@ export function NewTaskRouteScreen({ route }: StaticScreenProps<NewTaskRoutePara...@@ -369,7 +370,7 @@ export function NewTaskRouteScreen({ route }: StaticScreenProps<NewTaskRoutePara
369 )}370 )}
370 >371 >
371 {projectEmptyState.loading ? (372 {projectEmptyState.loading ? (
372 <ActivityIndicator colorClassName="accent-icon-muted" />373 <AppActivityIndicator colorClassName="accent-icon-muted" />
373 ) : null}374 ) : null}
374 <Text className="text-center text-lg font-t3-bold text-foreground">375 <Text className="text-center text-lg font-t3-bold text-foreground">
375 {projectEmptyState.title}376 {projectEmptyState.title}
apps/mobile/src/features/threads/ThreadFeed.tsx+5-5
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1import { ThreadContextDivider } from "./thread-context-divider";1import { ThreadContextDivider } from "./thread-context-divider";
2import { ThreadHandoffRow } from "./thread-handoff-row";2import { ThreadHandoffRow } from "./thread-handoff-row";
3import { SecretRequestCard } from "./SecretRequestCard";3import { SecretRequestCard } from "./SecretRequestCard";
4import { AppActivityIndicator } from "../../components/AppActivityIndicator";
4import {5import {
5 WorktreeWorkingHeader,6 WorktreeWorkingHeader,
6 WorktreeSetupCard,7 WorktreeSetupCard,
...@@ -79,7 +80,6 @@ import {...@@ -79,7 +80,6 @@ import {
79 type PartialMarkdownTheme,80 type PartialMarkdownTheme,
80} from "react-native-nitro-markdown";81} from "react-native-nitro-markdown";
81import {82import {
82 ActivityIndicator,
83 Alert,83 Alert,
84 Image,84 Image,
85 Platform,85 Platform,
...@@ -378,7 +378,7 @@ function AssistantForkButton(props: {...@@ -378,7 +378,7 @@ function AssistantForkButton(props: {
378 className="h-7 w-7 items-center justify-center disabled:opacity-40"378 className="h-7 w-7 items-center justify-center disabled:opacity-40"
379 >379 >
380 {busy ? (380 {busy ? (
381 <ActivityIndicator size="small" />381 <AppActivityIndicator size="small" />
382 ) : (382 ) : (
383 <SymbolView383 <SymbolView
384 name="arrow.triangle.branch"384 name="arrow.triangle.branch"
...@@ -416,7 +416,7 @@ function MessageAttachmentImage(props: {...@@ -416,7 +416,7 @@ function MessageAttachmentImage(props: {
416 if (uri === null) {416 if (uri === null) {
417 return (417 return (
418 <View className={`${props.className} items-center justify-center`}>418 <View className={`${props.className} items-center justify-center`}>
419 <ActivityIndicator />419 <AppActivityIndicator />
420 </View>420 </View>
421 );421 );
422 }422 }
...@@ -630,7 +630,7 @@ function MessageAttachmentFile(props: {...@@ -630,7 +630,7 @@ function MessageAttachmentFile(props: {
630 >630 >
631 <View className="h-12 w-10 shrink-0 items-center justify-center rounded-lg bg-subtle">631 <View className="h-12 w-10 shrink-0 items-center justify-center rounded-lg bg-subtle">
632 {opening ? (632 {opening ? (
633 <ActivityIndicator size="small" />633 <AppActivityIndicator size="small" />
634 ) : (634 ) : (
635 <PierreEntryIcon path={attachment.name} kind="file" size={26} />635 <PierreEntryIcon path={attachment.name} kind="file" size={26} />
636 )}636 )}
...@@ -3310,7 +3310,7 @@ function ThreadFeedLoadEarlierControl(props: ThreadFeedHistoryControls) {...@@ -3310,7 +3310,7 @@ function ThreadFeedLoadEarlierControl(props: ThreadFeedHistoryControls) {
3310 className="min-h-9 flex-row items-center justify-center gap-2 rounded-full border border-border/60 bg-surface/80 px-4 py-2 disabled:opacity-50"3310 className="min-h-9 flex-row items-center justify-center gap-2 rounded-full border border-border/60 bg-surface/80 px-4 py-2 disabled:opacity-50"
3311 >3311 >
3312 {props.loading ? (3312 {props.loading ? (
3313 <ActivityIndicator size="small" color={accentColor} />3313 <AppActivityIndicator size="small" color={accentColor} />
3314 ) : (3314 ) : (
3315 <SymbolView name="chevron.up" size={12} tintColor={accentColor} type="monochrome" />3315 <SymbolView name="chevron.up" size={12} tintColor={accentColor} type="monochrome" />
3316 )}3316 )}
apps/mobile/src/features/threads/ThreadMarkdownImage.tsx+3-9
...@@ -1,14 +1,8 @@...@@ -1,14 +1,8 @@
1import type { AssetResource, EnvironmentId } from "@t3tools/contracts";1import type { AssetResource, EnvironmentId } from "@t3tools/contracts";
2import { createContext, useContext, useEffect, useId, useState } from "react";2import { createContext, useContext, useEffect, useId, useState } from "react";
3import {3import { Image, Pressable, StyleSheet, View, type ViewStyle } from "react-native";
4 ActivityIndicator,
5 Image,
6 Pressable,
7 StyleSheet,
8 View,
9 type ViewStyle,
10} from "react-native";
114
5import { AppActivityIndicator } from "../../components/AppActivityIndicator";
12import { AppText as Text } from "../../components/AppText";6import { AppText as Text } from "../../components/AppText";
13import type { FilePreviewSource } from "../../components/FilePreviewModal";7import type { FilePreviewSource } from "../../components/FilePreviewModal";
14import { MediaActionsMenu } from "../../components/MediaActionsMenu";8import { MediaActionsMenu } from "../../components/MediaActionsMenu";
...@@ -96,7 +90,7 @@ export function ThreadMarkdownImageView(props: {...@@ -96,7 +90,7 @@ export function ThreadMarkdownImageView(props: {
96 {failed ? (90 {failed ? (
97 <Text className="text-xs text-foreground-muted">Image unavailable</Text>91 <Text className="text-xs text-foreground-muted">Image unavailable</Text>
98 ) : (92 ) : (
99 <ActivityIndicator />93 <AppActivityIndicator />
100 )}94 )}
101 </Pressable>95 </Pressable>
102 </MediaActionsMenu>96 </MediaActionsMenu>
apps/mobile/src/features/threads/floating-working-control.tsx+3-3
...@@ -3,7 +3,6 @@ import { formatDuration } from "@t3tools/shared/orchestrationTiming";...@@ -3,7 +3,6 @@ import { formatDuration } from "@t3tools/shared/orchestrationTiming";
3import { GlassContainer, GlassView } from "expo-glass-effect";3import { GlassContainer, GlassView } from "expo-glass-effect";
4import { type ReactNode, useEffect, useRef, useState } from "react";4import { type ReactNode, useEffect, useRef, useState } from "react";
5import {5import {
6 ActivityIndicator,
7 type LayoutChangeEvent,6 type LayoutChangeEvent,
8 Pressable,7 Pressable,
9 Text as SystemText,8 Text as SystemText,
...@@ -22,6 +21,7 @@ import Animated, {...@@ -22,6 +21,7 @@ import Animated, {
22} from "react-native-reanimated";21} from "react-native-reanimated";
23import { withUniwind } from "uniwind";22import { withUniwind } from "uniwind";
2423
24import { AppActivityIndicator } from "../../components/AppActivityIndicator";
25import { AppText as Text } from "../../components/AppText";25import { AppText as Text } from "../../components/AppText";
26import { SymbolView } from "../../components/AppSymbol";26import { SymbolView } from "../../components/AppSymbol";
27import { ControlPill } from "../../components/ControlPill";27import { ControlPill } from "../../components/ControlPill";
...@@ -364,7 +364,7 @@ function FloatingStatusLabel(props: {...@@ -364,7 +364,7 @@ function FloatingStatusLabel(props: {
364 className="gap-2"364 className="gap-2"
365 onLayout={props.onLayout}365 onLayout={props.onLayout}
366 >366 >
367 <ActivityIndicator size="small" colorClassName="accent-icon-muted" />367 <AppActivityIndicator size="small" colorClassName="accent-icon-muted" />
368 <Text className="shrink font-t3-medium text-xs text-foreground" numberOfLines={1}>368 <Text className="shrink font-t3-medium text-xs text-foreground" numberOfLines={1}>
369 {props.status.label}369 {props.status.label}
370 </Text>370 </Text>
...@@ -402,7 +402,7 @@ function FloatingStatusLabel(props: {...@@ -402,7 +402,7 @@ function FloatingStatusLabel(props: {
402 onPress={props.status.onPress}402 onPress={props.status.onPress}
403 >403 >
404 {props.status.tone === "reconnecting" ? (404 {props.status.tone === "reconnecting" ? (
405 <ActivityIndicator size="small" colorClassName="accent-icon-muted" />405 <AppActivityIndicator size="small" colorClassName="accent-icon-muted" />
406 ) : (406 ) : (
407 <View className="h-2 w-2 rounded-full bg-red-500" />407 <View className="h-2 w-2 rounded-full bg-red-500" />
408 )}408 )}
apps/mobile/src/features/threads/worktree-setup-card.tsx+10-5
...@@ -6,8 +6,9 @@ import {...@@ -6,8 +6,9 @@ import {
6import { worktreeSetupAgentStarted } from "@t3tools/client-runtime/worktree-setup";6import { worktreeSetupAgentStarted } from "@t3tools/client-runtime/worktree-setup";
7import { formatDuration } from "@t3tools/shared/orchestrationTiming";7import { formatDuration } from "@t3tools/shared/orchestrationTiming";
8import { useEffect, useState } from "react";8import { useEffect, useState } from "react";
9import { ActivityIndicator, AppState, Pressable, ScrollView, View } from "react-native";9import { AppState, Platform, Pressable, ScrollView, View } from "react-native";
10import { useSafeAreaInsets } from "react-native-safe-area-context";10import { useSafeAreaInsets } from "react-native-safe-area-context";
11import { AppActivityIndicator } from "../../components/AppActivityIndicator";
11import { AppText as Text } from "../../components/AppText";12import { AppText as Text } from "../../components/AppText";
12import { SymbolView, type AppSymbolName } from "../../components/AppSymbol";13import { SymbolView, type AppSymbolName } from "../../components/AppSymbol";
13import { WorktreeSetupSheet } from "./worktree-setup-sheet";14import { WorktreeSetupSheet } from "./worktree-setup-sheet";
...@@ -97,10 +98,14 @@ export function WorktreeSetupCard(props: WorktreeSetupCardProps) {...@@ -97,10 +98,14 @@ export function WorktreeSetupCard(props: WorktreeSetupCardProps) {
97 }98 }
98 >99 >
99 {backgroundSetup ? (100 {backgroundSetup ? (
100 <ActivityIndicator101 <AppActivityIndicator
101 size="small"102 size={Platform.OS === "android" ? 12 : "small"}
102 colorClassName="accent-icon-muted"103 colorClassName="accent-icon-muted"
103 style={{ width: 12, height: 12, transform: [{ scale: 0.65 }] }}104 style={{
105 width: 12,
106 height: 12,
107 transform: Platform.OS === "android" ? undefined : [{ scale: 0.65 }],
108 }}
104 />109 />
105 ) : failed ? (110 ) : failed ? (
106 <SymbolView111 <SymbolView
...@@ -302,7 +307,7 @@ function StageRow({...@@ -302,7 +307,7 @@ function StageRow({
302 >307 >
303 <View className="w-6 items-center">308 <View className="w-6 items-center">
304 {stage.status === "running" && animate ? (309 {stage.status === "running" && animate ? (
305 <ActivityIndicator310 <AppActivityIndicator
306 size="small"311 size="small"
307 colorClassName="accent-icon-muted"312 colorClassName="accent-icon-muted"
308 style={{ transform: [{ scale: 0.75 }] }}313 style={{ transform: [{ scale: 0.75 }] }}
apps/mobile/src/features/voice-input/ComposerDictationControl.tsx+2-2
...@@ -1,7 +1,6 @@...@@ -1,7 +1,6 @@
1import type { VoiceInputPhase, VoiceInputState } from "@t3tools/client-runtime/voice-input";1import type { VoiceInputPhase, VoiceInputState } from "@t3tools/client-runtime/voice-input";
2import { memo, useCallback, useLayoutEffect, useState, type ReactNode } from "react";2import { memo, useCallback, useLayoutEffect, useState, type ReactNode } from "react";
3import {3import {
4 ActivityIndicator,
5 Linking,4 Linking,
6 Platform,5 Platform,
7 Pressable,6 Pressable,
...@@ -20,6 +19,7 @@ import Animated, {...@@ -20,6 +19,7 @@ import Animated, {
20 type SharedValue,19 type SharedValue,
21} from "react-native-reanimated";20} from "react-native-reanimated";
2221
22import { AppActivityIndicator } from "../../components/AppActivityIndicator";
23import { AppText as Text } from "../../components/AppText";23import { AppText as Text } from "../../components/AppText";
24import { SymbolView, type AppSymbolName } from "../../components/AppSymbol";24import { SymbolView, type AppSymbolName } from "../../components/AppSymbol";
25import { cn } from "../../lib/cn";25import { cn } from "../../lib/cn";
...@@ -304,7 +304,7 @@ function VoiceActionButton(props: {...@@ -304,7 +304,7 @@ function VoiceActionButton(props: {
304 ) : null}304 ) : null}
305 <View className="absolute inset-0 items-center justify-center">305 <View className="absolute inset-0 items-center justify-center">
306 {props.loading ? (306 {props.loading ? (
307 <ActivityIndicator size="small" colorClassName="accent-icon-muted" />307 <AppActivityIndicator size="small" colorClassName="accent-icon-muted" />
308 ) : (308 ) : (
309 <SymbolView309 <SymbolView
310 name={props.icon}310 name={props.icon}