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 @@
1import { ActivityIndicator, StatusBar, View } from "react-native";
1import { StatusBar, View } from "react-native";
22import { useSafeAreaInsets } from "react-native-safe-area-context";
3import { AppActivityIndicator } from "./AppActivityIndicator";
34import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
45
56import { AppText as Text } from "./AppText";
......@@ -26,7 +27,7 @@ export function LoadingScreen(props: {
2627 {messagePlacement === "above-spinner" ? (
2728 <Text className="font-t3-bold text-lg text-foreground">{props.message}</Text>
2829 ) : null}
29 <ActivityIndicator size="large" />
30 <AppActivityIndicator size="large" />
3031 {messagePlacement === "below-spinner" ? (
3132 <Text className="font-t3-bold text-lg text-foreground">{props.message}</Text>
3233 ) : null}
apps/mobile/src/components/MaterialButton.android.tsx+2-3
......@@ -1,7 +1,7 @@
11import {
22 Box,
33 Button,
4 CircularProgressIndicator,
4 LoadingIndicator,
55 FilledTonalButton,
66 Host,
77 Row,
......@@ -78,9 +78,8 @@ export function MaterialButton(props: MaterialButtonProps) {
7878 <Row verticalAlignment="center">
7979 {props.loading ? (
8080 <>
81 <CircularProgressIndicator
81 <LoadingIndicator
8282 modifiers={[size(mediumIconSize, mediumIconSize)]}
83 strokeWidth={2}
8483 color={colors["--color-foreground-muted"]}
8584 />
8685 <Box modifiers={[size(8 * scale, 1)]} />
apps/mobile/src/components/MediaVideoPlayer.tsx+4-3
......@@ -2,8 +2,9 @@ import { useIsFocused } from "@react-navigation/native";
22import { useEvent } from "expo";
33import { useVideoPlayer, VideoView } from "expo-video";
44import { 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";
78import { AppText } from "./AppText";
89import { SymbolView } from "./AppSymbol";
910import { VideoThumbnailImage } from "./VideoThumbnailImage";
......@@ -96,7 +97,7 @@ function LoadedMediaVideo(props: {
9697 </View>
9798 ) : loadState === "pending" || status === "loading" ? (
9899 <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" />
100101 </View>
101102 ) : null}
102103 </View>
......@@ -160,7 +161,7 @@ function MediaVideoPlayerContent(props: MediaVideoPlayerProps) {
160161 {props.unavailable ? (
161162 <AppText className="text-sm text-white/80">Video unavailable</AppText>
162163 ) : props.uri === null ? (
163 <ActivityIndicator color="#ffffff" accessibilityLabel="Loading video" />
164 <AppActivityIndicator color="#ffffff" accessibilityLabel="Loading video" />
164165 ) : (
165166 <View className="size-12 items-center justify-center rounded-full bg-black/60">
166167 <SymbolView name="play" size={28} tintColor="#ffffff" type="monochrome" />
apps/mobile/src/components/ProjectCloneBanner.tsx+3-2
......@@ -3,8 +3,9 @@ import {
33 projectCloneProgressSummary,
44 type ProjectCloneSnapshot,
55} from "@t3tools/contracts";
6import { ActivityIndicator, Pressable, View } from "react-native";
6import { Pressable, View } from "react-native";
77
8import { AppActivityIndicator } from "./AppActivityIndicator";
89import { cn } from "../lib/cn";
910import { AppText as Text } from "./AppText";
1011
......@@ -24,7 +25,7 @@ export function ProjectCloneBanner(props: {
2425 if (clone.phase === "running") {
2526 return (
2627 <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" />
2829 <View className="min-w-0 flex-1">
2930 <Text className="font-t3-medium text-sm" numberOfLines={1}>
3031 Cloning {name}
apps/mobile/src/components/VideoPreviewModal.tsx+3-2
......@@ -1,9 +1,10 @@
11import { useIsFocused } from "@react-navigation/native";
22import { videoMimeType } from "@t3tools/shared/video";
33import { useEffect, useRef, useState } from "react";
4import { ActivityIndicator, Keyboard, Modal, Pressable, View } from "react-native";
4import { Keyboard, Modal, Pressable, View } from "react-native";
55import { useSafeAreaInsets } from "react-native-safe-area-context";
66
7import { AppActivityIndicator } from "./AppActivityIndicator";
78import { loadLocalAttachmentPreview } from "../lib/localAttachmentPreview";
89import { useMediaActions } from "../state/mediaActions";
910import { type MediaActionsSource } from "../lib/mediaActionsSource";
......@@ -165,7 +166,7 @@ function OpenVideoPreviewModal(props: {
165166 <MediaSourceCaption source={mediaActions.title} />
166167 {playback.uri === null && !playback.unavailable ? (
167168 <View className="flex-1 items-center justify-center gap-3 px-6">
168 <ActivityIndicator color="#ffffff" />
169 <AppActivityIndicator color="#ffffff" />
169170 <AppText className="text-sm text-white/80">Loading video...</AppText>
170171 </View>
171172 ) : (
apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx+3-9
......@@ -10,17 +10,11 @@ import {
1010 resolveEnvironmentMachineKind,
1111} from "@t3tools/contracts";
1212import { useNavigation } from "@react-navigation/native";
13import { AppActivityIndicator } from "../../components/AppActivityIndicator";
1314import { ScreenHeader } from "../../components/ScreenHeader";
1415import { SymbolView } from "../../components/AppSymbol";
1516import { useCallback, useMemo, useRef, type ComponentProps } from "react";
16import {
17 ActivityIndicator,
18 Platform,
19 Pressable,
20 RefreshControl,
21 useWindowDimensions,
22 View,
23} from "react-native";
17import { Platform, Pressable, RefreshControl, useWindowDimensions, View } from "react-native";
2418import { GestureDetector, useNativeGesture } from "react-native-gesture-handler";
2519import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable";
2620
......@@ -399,7 +393,7 @@ export function ArchivedThreadsScreen(props: {
399393 if (isInitialLoad) {
400394 return (
401395 <View className="items-center py-16">
402 <ActivityIndicator colorClassName="accent-icon" />
396 <AppActivityIndicator colorClassName="accent-icon" />
403397 <Text className="mt-3 text-sm text-foreground-muted">Loading archive...</Text>
404398 </View>
405399 );
apps/mobile/src/features/cloud/T3ConnectProfilePage.tsx+7-11
......@@ -7,16 +7,9 @@ import type { MenuAction } from "@react-native-menu/menu";
77import type { EnvironmentId } from "@t3tools/contracts";
88import type { RelayClientEnvironmentRecord } from "@t3tools/contracts/relay";
99import { type ReactNode, useRef, useState } from "react";
10import {
11 ActivityIndicator,
12 Alert,
13 Pressable,
14 RefreshControl,
15 ScrollView,
16 Text,
17 View,
18} from "react-native";
10import { Alert, Pressable, RefreshControl, ScrollView, Text, View } from "react-native";
1911
12import { AppActivityIndicator } from "../../components/AppActivityIndicator";
2013import { SymbolView } from "../../components/AppSymbol";
2114import { showConfirmDialog } from "../../components/ConfirmDialogHost";
2215import { ControlPillMenu } from "../../components/ControlPill";
......@@ -167,7 +160,7 @@ export function T3ConnectProfilePage() {
167160 </>
168161 ) : isInitialLoad ? (
169162 <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" />
171164 <Text className="text-base text-clerk-foreground-muted">Loading environments</Text>
172165 </View>
173166 ) : environments.length > 0 ? (
......@@ -178,7 +171,10 @@ export function T3ConnectProfilePage() {
178171 subtitle={`${linkedAtLabel(environment.linkedAt)} · ${endpointLabel(environment)}`}
179172 accessory={
180173 deregisteringEnvironmentId === environment.environmentId ? (
181 <ActivityIndicator colorClassName={"accent-clerk-foreground-muted"} size="small" />
174 <AppActivityIndicator
175 colorClassName={"accent-clerk-foreground-muted"}
176 size="small"
177 />
182178 ) : (
183179 <ControlPillMenu
184180 actions={ENVIRONMENT_MENU_ACTIONS}
apps/mobile/src/features/connection/CloudEnvironmentRows.tsx+3-2
......@@ -1,3 +1,4 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
12import { ConnectionTraceId } from "./ConnectionTraceId";
23import { useAuth } from "@clerk/expo";
34import { SymbolView } from "../../components/AppSymbol";
......@@ -13,7 +14,7 @@ import {
1314} from "@t3tools/contracts";
1415import { useAtomValue } from "@effect/atom-react";
1516import { useCallback, useState } from "react";
16import { ActivityIndicator, Pressable, type TextLayoutEvent, View } from "react-native";
17import { Pressable, type TextLayoutEvent, View } from "react-native";
1718
1819import { AppText as Text } from "../../components/AppText";
1920import { EnvironmentMachineSymbol } from "../../components/EnvironmentMachineSymbol";
......@@ -151,7 +152,7 @@ function CloudEnvironmentRowsContent(
151152 </View>
152153 ) : controller.relayDiscovery.isRefreshing ? (
153154 <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"} />
155156 <Text className="text-center text-sm leading-normal text-foreground-muted">
156157 Loading linked cloud environments.
157158 </Text>
apps/mobile/src/features/connection/EnvironmentConnectionNotice.tsx+3-2
......@@ -1,10 +1,11 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
12import { ConnectionTraceId } from "./ConnectionTraceId";
23import {
34 type EnvironmentConnectionPhase,
45 type EnvironmentConnectionPresentation,
56} from "@t3tools/client-runtime/connection";
67import { SymbolView } from "../../components/AppSymbol";
7import { ActivityIndicator, Pressable, View } from "react-native";
8import { Pressable, View } from "react-native";
89
910import { AppText as Text } from "../../components/AppText";
1011
......@@ -65,7 +66,7 @@ export function EnvironmentConnectionNotice(props: {
6566 <View className="flex-1 items-center justify-center px-8">
6667 <View className="max-w-[320px] items-center gap-3">
6768 {isRetrying ? (
68 <ActivityIndicator size="small" colorClassName={"accent-icon-muted"} />
69 <AppActivityIndicator size="small" colorClassName={"accent-icon-muted"} />
6970 ) : (
7071 <SymbolView
7172 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";
99import { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from "react";
1010import * as Haptics from "expo-haptics";
1111import { Accelerometer } from "expo-sensors";
12import {
13 ActivityIndicator,
14 Alert,
15 AppState,
16 BackHandler,
17 Pressable,
18 StatusBar,
19 View,
20} from "react-native";
12import { Alert, AppState, BackHandler, Pressable, StatusBar, View } from "react-native";
2113import Animated, { FadeIn, FadeOut, ReduceMotion } from "react-native-reanimated";
2214import { useSafeAreaInsets } from "react-native-safe-area-context";
2315
2416import { SymbolView } from "../../components/AppSymbol";
17import { AppActivityIndicator } from "../../components/AppActivityIndicator";
2518import { AppText } from "../../components/AppText";
2619import { ControlPill, ControlPillMenu } from "../../components/ControlPill";
2720import { GlassSurface } from "../../components/GlassSurface";
......@@ -295,7 +288,7 @@ function DevicePreviewScreen({
295288 </Pressable>
296289 </>
297290 ) : focused && foreground ? (
298 <ActivityIndicator color={themeVariables["--color-icon"]} />
291 <AppActivityIndicator color={themeVariables["--color-icon"]} />
299292 ) : null}
300293 </View>
301294 )}
......@@ -428,7 +421,7 @@ function OpenDevicePreview({
428421 </>
429422 ) : (
430423 <>
431 <ActivityIndicator color={themeVariables["--color-icon"]} />
424 <AppActivityIndicator color={themeVariables["--color-icon"]} />
432425 <AppText className="text-sm text-foreground-muted">Connecting to device...</AppText>
433426 </>
434427 )}
apps/mobile/src/features/devices/DeviceStreamWebView.tsx+3-2
......@@ -9,10 +9,11 @@ import {
99 useState,
1010 type Ref,
1111} from "react";
12import { ActivityIndicator, Platform, Pressable, View } from "react-native";
12import { Platform, Pressable, View } from "react-native";
1313import { WebView } from "react-native-webview";
1414import type { DeviceStreamStatus } from "@t3tools/client-runtime/device/stream";
1515
16import { AppActivityIndicator } from "../../components/AppActivityIndicator";
1617import { AppText } from "../../components/AppText";
1718
1819import {
......@@ -187,7 +188,7 @@ function DeviceStreamDocumentView({
187188 className="absolute inset-0 items-center justify-center gap-4 px-6"
188189 style={{ backgroundColor: background }}
189190 >
190 {status === "connecting" ? <ActivityIndicator colorClassName="accent-icon" /> : null}
191 {status === "connecting" ? <AppActivityIndicator colorClassName="accent-icon" /> : null}
191192 <AppText
192193 accessibilityLiveRegion="polite"
193194 className="text-center text-sm text-foreground-muted"
apps/mobile/src/features/diagnostics/SettingsDiagnosticsRouteScreen.tsx+3-2
......@@ -1,8 +1,9 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
12import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
23import Constants from "expo-constants";
34import * as Updates from "expo-updates";
45import { useEffect, useState } from "react";
5import { ActivityIndicator, Platform, View } from "react-native";
6import { Platform, View } from "react-native";
67import { useSafeAreaInsets } from "react-native-safe-area-context";
78
89import { SymbolView } from "../../components/AppSymbol";
......@@ -85,7 +86,7 @@ export function SettingsDiagnosticsRouteScreen() {
8586 <SettingsSection title="Startup crashes">
8687 {state.status === "loading" ? (
8788 <View className="items-center gap-3 px-6 py-8">
88 <ActivityIndicator />
89 <AppActivityIndicator />
8990 <Text className="text-center text-sm text-foreground-muted">Reading crash log…</Text>
9091 </View>
9192 ) : state.status === "unavailable" ? (
apps/mobile/src/features/files/FilePreviewFeedback.tsx+3-2
......@@ -1,5 +1,6 @@
1import { ActivityIndicator, View } from "react-native";
1import { View } from "react-native";
22
3import { AppActivityIndicator } from "../../components/AppActivityIndicator";
34import { AppText as Text } from "../../components/AppText";
45import { cn } from "../../lib/cn";
56
......@@ -14,7 +15,7 @@ export function FilePreviewLoading(props: {
1415 props.background === "card" ? "bg-card" : "bg-sheet",
1516 )}
1617 >
17 <ActivityIndicator />
18 <AppActivityIndicator />
1819 <Text className="text-center text-sm text-foreground-muted">{props.message}</Text>
1920 </View>
2021 );
apps/mobile/src/features/files/FileTreeBrowser.tsx+4-3
......@@ -1,8 +1,9 @@
11import { LegendList } from "@legendapp/list/react-native";
22import type { ProjectEntry } from "@t3tools/contracts";
3import { AppActivityIndicator } from "../../components/AppActivityIndicator";
34import { SymbolView } from "../../components/AppSymbol";
45import { 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";
67import { useSafeAreaInsets } from "react-native-safe-area-context";
78
89import { AppText as Text } from "../../components/AppText";
......@@ -96,7 +97,7 @@ const FileTreeRow = memo(function FileTreeRow(props: {
9697 {node.name}
9798 </Text>
9899 {node.kind === "directory" && props.expanded && props.loading ? (
99 <ActivityIndicator size="small" accessibilityLabel={`Loading ${node.name}`} />
100 <AppActivityIndicator size="small" accessibilityLabel={`Loading ${node.name}`} />
100101 ) : null}
101102 </Pressable>
102103 );
......@@ -296,7 +297,7 @@ export function FileTreeBrowser(props: {
296297 </Pressable>
297298 </>
298299 ) : props.isPending ? (
299 <ActivityIndicator size="small" />
300 <AppActivityIndicator size="small" />
300301 ) : (
301302 <>
302303 <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 @@
11import { useState } from "react";
2import { ActivityIndicator, View } from "react-native";
2import { View } from "react-native";
33import { WebView } from "react-native-webview";
44
5import { AppActivityIndicator } from "../../components/AppActivityIndicator";
56import { AppText as Text } from "../../components/AppText";
67import { FilePreviewLoading } from "./FilePreviewFeedback";
78import { LoadingStrip } from "../../components/LoadingStrip";
......@@ -46,7 +47,7 @@ export function WorkspaceFileWebPreview(props: { readonly uri: string | null })
4647 }}
4748 renderLoading={() => (
4849 <View className="absolute inset-0 items-center justify-center bg-card">
49 <ActivityIndicator />
50 <AppActivityIndicator />
5051 </View>
5152 )}
5253 style={{ flex: 1, backgroundColor: "transparent" }}
apps/mobile/src/features/home/HomeScreen.tsx+2-2
......@@ -1,3 +1,4 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
12import { useAndroidControlSizing } from "../../components/useAndroidControlSizing";
23import type { ThreadMoveDestination } from "../threads/threadOrder";
34import { computeThreadMoveAvailability } from "../threads/threadOrder";
......@@ -15,7 +16,6 @@ import { useAtomValue } from "@effect/atom-react";
1516import { useFocusEffect } from "@react-navigation/native";
1617import { use, useCallback, useEffect, useMemo, useRef, useState, type ComponentProps } from "react";
1718import {
18 ActivityIndicator,
1919 Platform,
2020 ScrollView,
2121 View,
......@@ -947,7 +947,7 @@ export function HomeScreen(props: HomeScreenProps) {
947947 />
948948 {emptyState.loading ? (
949949 <View className="mt-4 items-center">
950 <ActivityIndicator colorClassName="accent-icon-muted" />
950 <AppActivityIndicator colorClassName="accent-icon-muted" />
951951 </View>
952952 ) : null}
953953 </View>
apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx+3-2
......@@ -1,7 +1,8 @@
11import type { NativeStackNavigationOptions } from "@react-navigation/native-stack";
22import { 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";
56import { SymbolView } from "../../components/AppSymbol";
67import { AppText as Text } from "../../components/AppText";
78import { useAndroidControlSizing } from "../../components/useAndroidControlSizing";
......@@ -134,7 +135,7 @@ export function WorkspaceConnectionTitle(props: {
134135 ]}
135136 >
136137 {status.showsProgress ? (
137 <ActivityIndicator
138 <AppActivityIndicator
138139 colorClassName={"accent-icon-muted"}
139140 size={Platform.OS === "android" ? Math.round(20 * scale) : "small"}
140141 />
apps/mobile/src/features/projects/AddProjectScreen.tsx+5-4
......@@ -1,3 +1,4 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
12import { MaterialListRow } from "../../components/MaterialListRow";
23import { SettingsScreen } from "../settings/components/SettingsScreen";
34import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
......@@ -55,7 +56,7 @@ import {
5556import { CommonActions, StackActions, useNavigation } from "@react-navigation/native";
5657import { SymbolView } from "../../components/AppSymbol";
5758import { 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";
5960import { useSafeAreaInsets } from "react-native-safe-area-context";
6061import * as Arr from "effect/Array";
6162import * as Cause from "effect/Cause";
......@@ -265,7 +266,7 @@ function PrimaryActionButton(props: {
265266 className="h-12 items-center justify-center rounded-full bg-primary active:opacity-70 disabled:opacity-45"
266267 >
267268 {props.loading ? (
268 <ActivityIndicator colorClassName={String("accent-primary-foreground")} />
269 <AppActivityIndicator colorClassName={String("accent-primary-foreground")} />
269270 ) : (
270271 <Text className="text-base font-t3-bold text-primary-foreground">{props.label}</Text>
271272 )}
......@@ -665,7 +666,7 @@ export function AddProjectSourceScreen() {
665666 )}
666667 </ListSection>
667668 {discoveryState.isPending ? (
668 <ActivityIndicator colorClassName="accent-icon-muted" />
669 <AppActivityIndicator colorClassName="accent-icon-muted" />
669670 ) : null}
670671 </>
671672 ) : null}
......@@ -918,7 +919,7 @@ function FolderBrowser(props: {
918919 <ListSection>
919920 {fileAccess.isPending || (browseState.isPending && browseState.data === null) ? (
920921 <View className="items-center py-5">
921 <ActivityIndicator colorClassName="accent-icon-muted" />
922 <AppActivityIndicator colorClassName="accent-icon-muted" />
922923 </View>
923924 ) : null}
924925 {browsePath.canBrowseUp ? (
apps/mobile/src/features/review/ReviewSheet.tsx+2-2
......@@ -1,6 +1,7 @@
11import type { EnvironmentId, ThreadId } from "@t3tools/contracts";
22import { formatDiffCount } from "@t3tools/client-runtime/diff-count";
33import { useNavigation, type StaticScreenProps } from "@react-navigation/native";
4import { AppActivityIndicator } from "../../components/AppActivityIndicator";
45import { nativeHeaderScrollEdgeEffects } from "../../native/StackHeader";
56import { ScreenHeader } from "../../components/ScreenHeader";
67import type { ScreenHeaderMenuItem } from "../../components/ScreenHeader.types";
......@@ -19,7 +20,6 @@ import {
1920 useState,
2021} from "react";
2122import {
22 ActivityIndicator,
2323 FlatList,
2424 Platform,
2525 Pressable,
......@@ -855,7 +855,7 @@ export function ReviewSheet(props: ReviewSheetProps) {
855855 Platform.OS !== "android" && "border-b border-border bg-card",
856856 )}
857857 >
858 <ActivityIndicator size="small" />
858 <AppActivityIndicator size="small" />
859859 <Text className="text-xs text-foreground-muted">Loading diff…</Text>
860860 </View>
861861 ) : parsedDiff.kind === "empty" ? (
apps/mobile/src/features/settings/SettingsClientStorageRouteScreen.tsx+3-2
......@@ -1,9 +1,10 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
12import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
23import { useAtomSet, useAtomValue } from "@effect/atom-react";
34import { type EnvironmentMachineKind, resolveEnvironmentMachineKind } from "@t3tools/contracts";
45import { AsyncResult } from "effect/reactivity";
56import { useMemo } from "react";
6import { ActivityIndicator, Alert, Pressable, View } from "react-native";
7import { Alert, Pressable, View } from "react-native";
78import { useSafeAreaInsets } from "react-native-safe-area-context";
89
910import { AppText as Text } from "../../components/AppText";
......@@ -99,7 +100,7 @@ export function SettingsClientStorageRouteScreen() {
99100 </View>
100101 ) : !summary ? (
101102 <View className="items-center gap-3 px-6 py-8">
102 <ActivityIndicator />
103 <AppActivityIndicator />
103104 <Text className="text-center text-sm text-foreground-muted">
104105 Inspecting cached data…
105106 </Text>
apps/mobile/src/features/settings/components/SettingsActionRow.tsx+5-2
......@@ -1,6 +1,7 @@
11import type { ComponentProps } from "react";
2import { ActivityIndicator, Platform, Pressable } from "react-native";
2import { Platform, Pressable } from "react-native";
33
4import { AppActivityIndicator } from "../../../components/AppActivityIndicator";
45import { SymbolView } from "../../../components/AppSymbol";
56import { AppText as Text } from "../../../components/AppText";
67import { MaterialListRow } from "../../../components/MaterialListRow";
......@@ -26,7 +27,9 @@ export function SettingsActionRow(props: {
2627 weight="regular"
2728 />
2829 );
29 const spinner = props.loading ? <ActivityIndicator colorClassName={iconColorClassName} /> : null;
30 const spinner = props.loading ? (
31 <AppActivityIndicator colorClassName={iconColorClassName} />
32 ) : null;
3033
3134 if (Platform.OS === "android") {
3235 return (
apps/mobile/src/features/settings/components/SettingsLegalDocumentRouteScreen.tsx+3-2
......@@ -1,8 +1,9 @@
11import { type NavigationProp, type ParamListBase, useNavigation } from "@react-navigation/native";
22import { useCallback, useLayoutEffect, useState } from "react";
3import { ActivityIndicator, Linking, Pressable, View } from "react-native";
3import { Linking, Pressable, View } from "react-native";
44import { WebView } from "react-native-webview";
55
6import { AppActivityIndicator } from "../../../components/AppActivityIndicator";
67import { AppText as Text } from "../../../components/AppText";
78import { LoadingStrip } from "../../../components/LoadingStrip";
89import { SymbolView } from "../../../components/AppSymbol";
......@@ -172,7 +173,7 @@ export function SettingsLegalDocumentRouteScreen({
172173 }}
173174 renderLoading={() => (
174175 <View className="absolute inset-0 items-center justify-center bg-sheet">
175 <ActivityIndicator />
176 <AppActivityIndicator />
176177 </View>
177178 )}
178179 style={{ flex: 1, backgroundColor: "transparent" }}
apps/mobile/src/features/threads/GitActionProgressOverlay.tsx+3-2
......@@ -1,8 +1,9 @@
11import * as Haptics from "expo-haptics";
22import { GlassView } from "expo-glass-effect";
3import { AppActivityIndicator } from "../../components/AppActivityIndicator";
34import { SymbolView } from "../../components/AppSymbol";
45import { useCallback, useEffect, useRef } from "react";
5import { ActivityIndicator, Pressable, StyleSheet, useColorScheme, View } from "react-native";
6import { Pressable, StyleSheet, useColorScheme, View } from "react-native";
67import Animated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated";
78import { useSafeAreaInsets } from "react-native-safe-area-context";
89
......@@ -158,7 +159,7 @@ function OverlayContent(props: { readonly progress: GitActionProgress }) {
158159function OverlayIcon(props: { readonly phase: GitActionProgress["phase"] }) {
159160 switch (props.phase) {
160161 case "running":
161 return <ActivityIndicator size="small" colorClassName={"accent-icon"} />;
162 return <AppActivityIndicator size="small" colorClassName={"accent-icon"} />;
162163 case "success":
163164 return (
164165 <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 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
12import { MaterialListRow } from "../../components/MaterialListRow";
23import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell";
34import { shouldCheckoutNewTaskBranch } from "./new-task-context-presentation";
......@@ -11,15 +12,7 @@ import {
1112import * as Haptics from "expo-haptics";
1213import { useNavigation } from "@react-navigation/native";
1314import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
14import {
15 ActivityIndicator,
16 Alert,
17 Platform,
18 Pressable,
19 ScrollView,
20 TextInput,
21 View,
22} from "react-native";
15import { Alert, Platform, Pressable, ScrollView, TextInput, View } from "react-native";
2316import { useSafeAreaInsets } from "react-native-safe-area-context";
2417
2518import { AndroidScreenHeader } from "../../components/AndroidScreenHeader";
......@@ -486,7 +479,7 @@ export function BranchPickerScreen(props: {
486479 : 0,
487480 }}
488481 >
489 {props.loading ? <ActivityIndicator /> : null}
482 {props.loading ? <AppActivityIndicator /> : null}
490483 <Text className="text-center text-sm text-foreground-muted">
491484 {props.loading
492485 ? "Loading branches…"
......@@ -525,7 +518,7 @@ export function BranchPickerScreen(props: {
525518 ListFooterComponent={
526519 props.refreshing ? (
527520 <View className="items-center py-4">
528 <ActivityIndicator />
521 <AppActivityIndicator />
529522 </View>
530523 ) : null
531524 }
apps/mobile/src/features/threads/NewTaskRouteScreen.tsx+3-2
......@@ -1,3 +1,4 @@
1import { AppActivityIndicator } from "../../components/AppActivityIndicator";
12import { MaterialListRow } from "../../components/MaterialListRow";
23import { ScreenHeader } from "../../components/ScreenHeader";
34import {
......@@ -15,7 +16,7 @@ import {
1516} from "@t3tools/client-runtime/state/runtime";
1617import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell";
1718import { useEffect, useRef, useState } from "react";
18import { ActivityIndicator, Alert, Platform, Pressable, View } from "react-native";
19import { Alert, Platform, Pressable, View } from "react-native";
1920import { useSafeAreaInsets } from "react-native-safe-area-context";
2021import { cn } from "../../lib/cn";
2122import { MaterialScreenContent } from "../../components/MaterialScreenContent";
......@@ -369,7 +370,7 @@ export function NewTaskRouteScreen({ route }: StaticScreenProps<NewTaskRoutePara
369370 )}
370371 >
371372 {projectEmptyState.loading ? (
372 <ActivityIndicator colorClassName="accent-icon-muted" />
373 <AppActivityIndicator colorClassName="accent-icon-muted" />
373374 ) : null}
374375 <Text className="text-center text-lg font-t3-bold text-foreground">
375376 {projectEmptyState.title}
apps/mobile/src/features/threads/ThreadFeed.tsx+5-5
......@@ -1,6 +1,7 @@
11import { ThreadContextDivider } from "./thread-context-divider";
22import { ThreadHandoffRow } from "./thread-handoff-row";
33import { SecretRequestCard } from "./SecretRequestCard";
4import { AppActivityIndicator } from "../../components/AppActivityIndicator";
45import {
56 WorktreeWorkingHeader,
67 WorktreeSetupCard,
......@@ -79,7 +80,6 @@ import {
7980 type PartialMarkdownTheme,
8081} from "react-native-nitro-markdown";
8182import {
82 ActivityIndicator,
8383 Alert,
8484 Image,
8585 Platform,
......@@ -378,7 +378,7 @@ function AssistantForkButton(props: {
378378 className="h-7 w-7 items-center justify-center disabled:opacity-40"
379379 >
380380 {busy ? (
381 <ActivityIndicator size="small" />
381 <AppActivityIndicator size="small" />
382382 ) : (
383383 <SymbolView
384384 name="arrow.triangle.branch"
......@@ -416,7 +416,7 @@ function MessageAttachmentImage(props: {
416416 if (uri === null) {
417417 return (
418418 <View className={`${props.className} items-center justify-center`}>
419 <ActivityIndicator />
419 <AppActivityIndicator />
420420 </View>
421421 );
422422 }
......@@ -630,7 +630,7 @@ function MessageAttachmentFile(props: {
630630 >
631631 <View className="h-12 w-10 shrink-0 items-center justify-center rounded-lg bg-subtle">
632632 {opening ? (
633 <ActivityIndicator size="small" />
633 <AppActivityIndicator size="small" />
634634 ) : (
635635 <PierreEntryIcon path={attachment.name} kind="file" size={26} />
636636 )}
......@@ -3310,7 +3310,7 @@ function ThreadFeedLoadEarlierControl(props: ThreadFeedHistoryControls) {
33103310 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"
33113311 >
33123312 {props.loading ? (
3313 <ActivityIndicator size="small" color={accentColor} />
3313 <AppActivityIndicator size="small" color={accentColor} />
33143314 ) : (
33153315 <SymbolView name="chevron.up" size={12} tintColor={accentColor} type="monochrome" />
33163316 )}
apps/mobile/src/features/threads/ThreadMarkdownImage.tsx+3-9
......@@ -1,14 +1,8 @@
11import type { AssetResource, EnvironmentId } from "@t3tools/contracts";
22import { createContext, useContext, useEffect, useId, useState } from "react";
3import {
4 ActivityIndicator,
5 Image,
6 Pressable,
7 StyleSheet,
8 View,
9 type ViewStyle,
10} from "react-native";
3import { Image, Pressable, StyleSheet, View, type ViewStyle } from "react-native";
114
5import { AppActivityIndicator } from "../../components/AppActivityIndicator";
126import { AppText as Text } from "../../components/AppText";
137import type { FilePreviewSource } from "../../components/FilePreviewModal";
148import { MediaActionsMenu } from "../../components/MediaActionsMenu";
......@@ -96,7 +90,7 @@ export function ThreadMarkdownImageView(props: {
9690 {failed ? (
9791 <Text className="text-xs text-foreground-muted">Image unavailable</Text>
9892 ) : (
99 <ActivityIndicator />
93 <AppActivityIndicator />
10094 )}
10195 </Pressable>
10296 </MediaActionsMenu>
apps/mobile/src/features/threads/floating-working-control.tsx+3-3
......@@ -3,7 +3,6 @@ import { formatDuration } from "@t3tools/shared/orchestrationTiming";
33import { GlassContainer, GlassView } from "expo-glass-effect";
44import { type ReactNode, useEffect, useRef, useState } from "react";
55import {
6 ActivityIndicator,
76 type LayoutChangeEvent,
87 Pressable,
98 Text as SystemText,
......@@ -22,6 +21,7 @@ import Animated, {
2221} from "react-native-reanimated";
2322import { withUniwind } from "uniwind";
2423
24import { AppActivityIndicator } from "../../components/AppActivityIndicator";
2525import { AppText as Text } from "../../components/AppText";
2626import { SymbolView } from "../../components/AppSymbol";
2727import { ControlPill } from "../../components/ControlPill";
......@@ -364,7 +364,7 @@ function FloatingStatusLabel(props: {
364364 className="gap-2"
365365 onLayout={props.onLayout}
366366 >
367 <ActivityIndicator size="small" colorClassName="accent-icon-muted" />
367 <AppActivityIndicator size="small" colorClassName="accent-icon-muted" />
368368 <Text className="shrink font-t3-medium text-xs text-foreground" numberOfLines={1}>
369369 {props.status.label}
370370 </Text>
......@@ -402,7 +402,7 @@ function FloatingStatusLabel(props: {
402402 onPress={props.status.onPress}
403403 >
404404 {props.status.tone === "reconnecting" ? (
405 <ActivityIndicator size="small" colorClassName="accent-icon-muted" />
405 <AppActivityIndicator size="small" colorClassName="accent-icon-muted" />
406406 ) : (
407407 <View className="h-2 w-2 rounded-full bg-red-500" />
408408 )}
apps/mobile/src/features/threads/worktree-setup-card.tsx+10-5
......@@ -6,8 +6,9 @@ import {
66import { worktreeSetupAgentStarted } from "@t3tools/client-runtime/worktree-setup";
77import { formatDuration } from "@t3tools/shared/orchestrationTiming";
88import { useEffect, useState } from "react";
9import { ActivityIndicator, AppState, Pressable, ScrollView, View } from "react-native";
9import { AppState, Platform, Pressable, ScrollView, View } from "react-native";
1010import { useSafeAreaInsets } from "react-native-safe-area-context";
11import { AppActivityIndicator } from "../../components/AppActivityIndicator";
1112import { AppText as Text } from "../../components/AppText";
1213import { SymbolView, type AppSymbolName } from "../../components/AppSymbol";
1314import { WorktreeSetupSheet } from "./worktree-setup-sheet";
......@@ -97,10 +98,14 @@ export function WorktreeSetupCard(props: WorktreeSetupCardProps) {
9798 }
9899 >
99100 {backgroundSetup ? (
100 <ActivityIndicator
101 size="small"
101 <AppActivityIndicator
102 size={Platform.OS === "android" ? 12 : "small"}
102103 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 }}
104109 />
105110 ) : failed ? (
106111 <SymbolView
......@@ -302,7 +307,7 @@ function StageRow({
302307 >
303308 <View className="w-6 items-center">
304309 {stage.status === "running" && animate ? (
305 <ActivityIndicator
310 <AppActivityIndicator
306311 size="small"
307312 colorClassName="accent-icon-muted"
308313 style={{ transform: [{ scale: 0.75 }] }}
apps/mobile/src/features/voice-input/ComposerDictationControl.tsx+2-2
......@@ -1,7 +1,6 @@
11import type { VoiceInputPhase, VoiceInputState } from "@t3tools/client-runtime/voice-input";
22import { memo, useCallback, useLayoutEffect, useState, type ReactNode } from "react";
33import {
4 ActivityIndicator,
54 Linking,
65 Platform,
76 Pressable,
......@@ -20,6 +19,7 @@ import Animated, {
2019 type SharedValue,
2120} from "react-native-reanimated";
2221
22import { AppActivityIndicator } from "../../components/AppActivityIndicator";
2323import { AppText as Text } from "../../components/AppText";
2424import { SymbolView, type AppSymbolName } from "../../components/AppSymbol";
2525import { cn } from "../../lib/cn";
......@@ -304,7 +304,7 @@ function VoiceActionButton(props: {
304304 ) : null}
305305 <View className="absolute inset-0 items-center justify-center">
306306 {props.loading ? (
307 <ActivityIndicator size="small" colorClassName="accent-icon-muted" />
307 <AppActivityIndicator size="small" colorClassName="accent-icon-muted" />
308308 ) : (
309309 <SymbolView
310310 name={props.icon}