diff --git a/apps/mobile/src/components/AppActivityIndicator.android.tsx b/apps/mobile/src/components/AppActivityIndicator.android.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0604db8e8a95309426a6397d7d30af8182dc81ed
--- /dev/null
+++ b/apps/mobile/src/components/AppActivityIndicator.android.tsx
@@ -0,0 +1,49 @@
+import { LoadingIndicator, Host } from "@expo/ui/jetpack-compose";
+import { size as composeSize } from "@expo/ui/jetpack-compose/modifiers";
+import { StyleSheet, View, type ActivityIndicatorProps } from "react-native";
+import { useResolveClassNames, withUniwind } from "uniwind";
+
+function AndroidActivityIndicator({
+ animating = true,
+ color,
+ colorClassName: _colorClassName,
+ className: _className,
+ hidesWhenStopped: _hidesWhenStopped,
+ size = "small",
+ style,
+ ...viewProps
+}: ActivityIndicatorProps) {
+ const { color: primaryColor } = useResolveClassNames("text-primary");
+ const diameter = typeof size === "number" ? size : size === "large" ? 48 : 20;
+
+ return (
+
+
+ {animating ? (
+
+
+
+ ) : null}
+
+
+ );
+}
+
+export const AppActivityIndicator = withUniwind(AndroidActivityIndicator);
+
+const styles = StyleSheet.create({
+ container: { alignItems: "center", justifyContent: "center" },
+});
diff --git a/apps/mobile/src/components/AppActivityIndicator.tsx b/apps/mobile/src/components/AppActivityIndicator.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b593a616075eec3e7a01791f7d5bebbc93432891
--- /dev/null
+++ b/apps/mobile/src/components/AppActivityIndicator.tsx
@@ -0,0 +1,3 @@
+import { ActivityIndicator } from "react-native";
+
+export const AppActivityIndicator = ActivityIndicator;
diff --git a/apps/mobile/src/components/LoadingScreen.tsx b/apps/mobile/src/components/LoadingScreen.tsx
index 3503685f74e5e601e18f880747fc767f9cd9be0e..057fa292a51e5df46595c1c43d75612b58789eb8 100644
--- a/apps/mobile/src/components/LoadingScreen.tsx
+++ b/apps/mobile/src/components/LoadingScreen.tsx
@@ -1,5 +1,6 @@
-import { ActivityIndicator, StatusBar, View } from "react-native";
+import { StatusBar, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
+import { AppActivityIndicator } from "./AppActivityIndicator";
import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider";
import { AppText as Text } from "./AppText";
@@ -26,7 +27,7 @@ export function LoadingScreen(props: {
{messagePlacement === "above-spinner" ? (
{props.message}
) : null}
-
+
{messagePlacement === "below-spinner" ? (
{props.message}
) : null}
diff --git a/apps/mobile/src/components/MaterialButton.android.tsx b/apps/mobile/src/components/MaterialButton.android.tsx
index f7a62f1ecb0b6ddf7a23f79661f7e0c6eaf6ef6e..0171ccbb3d8238ace43ea10244bd8eeca9e0483b 100644
--- a/apps/mobile/src/components/MaterialButton.android.tsx
+++ b/apps/mobile/src/components/MaterialButton.android.tsx
@@ -1,7 +1,7 @@
import {
Box,
Button,
- CircularProgressIndicator,
+ LoadingIndicator,
FilledTonalButton,
Host,
Row,
@@ -78,9 +78,8 @@ export function MaterialButton(props: MaterialButtonProps) {
{props.loading ? (
<>
-
diff --git a/apps/mobile/src/components/MediaVideoPlayer.tsx b/apps/mobile/src/components/MediaVideoPlayer.tsx
index c7d9e4e480568cd503918d2b52393ba47a014718..c35195cd3a5f3c989bfedf4c970eeae07f0c1a3d 100644
--- a/apps/mobile/src/components/MediaVideoPlayer.tsx
+++ b/apps/mobile/src/components/MediaVideoPlayer.tsx
@@ -2,8 +2,9 @@ import { useIsFocused } from "@react-navigation/native";
import { useEvent } from "expo";
import { useVideoPlayer, VideoView } from "expo-video";
import { useEffect, useEffectEvent, useRef, useState } from "react";
-import { ActivityIndicator, AppState, Pressable, View } from "react-native";
+import { AppState, Pressable, View } from "react-native";
+import { AppActivityIndicator } from "./AppActivityIndicator";
import { AppText } from "./AppText";
import { SymbolView } from "./AppSymbol";
import { VideoThumbnailImage } from "./VideoThumbnailImage";
@@ -96,7 +97,7 @@ function LoadedMediaVideo(props: {
) : loadState === "pending" || status === "loading" ? (
-
+
) : null}
@@ -160,7 +161,7 @@ function MediaVideoPlayerContent(props: MediaVideoPlayerProps) {
{props.unavailable ? (
Video unavailable
) : props.uri === null ? (
-
+
) : (
diff --git a/apps/mobile/src/components/ProjectCloneBanner.tsx b/apps/mobile/src/components/ProjectCloneBanner.tsx
index cca6a71e3f5e82f713a5095ceeb61b368aef32c0..06c121960d79c11d67705d1e3b5e34a993ff8b12 100644
--- a/apps/mobile/src/components/ProjectCloneBanner.tsx
+++ b/apps/mobile/src/components/ProjectCloneBanner.tsx
@@ -3,8 +3,9 @@ import {
projectCloneProgressSummary,
type ProjectCloneSnapshot,
} from "@t3tools/contracts";
-import { ActivityIndicator, Pressable, View } from "react-native";
+import { Pressable, View } from "react-native";
+import { AppActivityIndicator } from "./AppActivityIndicator";
import { cn } from "../lib/cn";
import { AppText as Text } from "./AppText";
@@ -24,7 +25,7 @@ export function ProjectCloneBanner(props: {
if (clone.phase === "running") {
return (
-
+
Cloning {name}
diff --git a/apps/mobile/src/components/VideoPreviewModal.tsx b/apps/mobile/src/components/VideoPreviewModal.tsx
index eea1eb72f1b6e26c14ba90db35680be9d5fa42e3..d82e6d9969f7e9d132ca3b3d25a3c7b737c568e3 100644
--- a/apps/mobile/src/components/VideoPreviewModal.tsx
+++ b/apps/mobile/src/components/VideoPreviewModal.tsx
@@ -1,9 +1,10 @@
import { useIsFocused } from "@react-navigation/native";
import { videoMimeType } from "@t3tools/shared/video";
import { useEffect, useRef, useState } from "react";
-import { ActivityIndicator, Keyboard, Modal, Pressable, View } from "react-native";
+import { Keyboard, Modal, Pressable, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
+import { AppActivityIndicator } from "./AppActivityIndicator";
import { loadLocalAttachmentPreview } from "../lib/localAttachmentPreview";
import { useMediaActions } from "../state/mediaActions";
import { type MediaActionsSource } from "../lib/mediaActionsSource";
@@ -165,7 +166,7 @@ function OpenVideoPreviewModal(props: {
{playback.uri === null && !playback.unavailable ? (
-
+
Loading video...
) : (
diff --git a/apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx b/apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx
index eacd5544ea02db1941a42782c2a90db73a5cba89..e0823dff698e9eaf856bc6dcb7db18304d265d22 100644
--- a/apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx
+++ b/apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx
@@ -10,17 +10,11 @@ import {
resolveEnvironmentMachineKind,
} from "@t3tools/contracts";
import { useNavigation } from "@react-navigation/native";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { ScreenHeader } from "../../components/ScreenHeader";
import { SymbolView } from "../../components/AppSymbol";
import { useCallback, useMemo, useRef, type ComponentProps } from "react";
-import {
- ActivityIndicator,
- Platform,
- Pressable,
- RefreshControl,
- useWindowDimensions,
- View,
-} from "react-native";
+import { Platform, Pressable, RefreshControl, useWindowDimensions, View } from "react-native";
import { GestureDetector, useNativeGesture } from "react-native-gesture-handler";
import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable";
@@ -399,7 +393,7 @@ export function ArchivedThreadsScreen(props: {
if (isInitialLoad) {
return (
-
+
Loading archive...
);
diff --git a/apps/mobile/src/features/cloud/T3ConnectProfilePage.tsx b/apps/mobile/src/features/cloud/T3ConnectProfilePage.tsx
index 702aa5f5e8eff3793ece31c967a1485e298248ee..a49a80709fbb1c8eafe2ffa0316ae8851458fe74 100644
--- a/apps/mobile/src/features/cloud/T3ConnectProfilePage.tsx
+++ b/apps/mobile/src/features/cloud/T3ConnectProfilePage.tsx
@@ -7,16 +7,9 @@ import type { MenuAction } from "@react-native-menu/menu";
import type { EnvironmentId } from "@t3tools/contracts";
import type { RelayClientEnvironmentRecord } from "@t3tools/contracts/relay";
import { type ReactNode, useRef, useState } from "react";
-import {
- ActivityIndicator,
- Alert,
- Pressable,
- RefreshControl,
- ScrollView,
- Text,
- View,
-} from "react-native";
+import { Alert, Pressable, RefreshControl, ScrollView, Text, View } from "react-native";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { SymbolView } from "../../components/AppSymbol";
import { showConfirmDialog } from "../../components/ConfirmDialogHost";
import { ControlPillMenu } from "../../components/ControlPill";
@@ -167,7 +160,7 @@ export function T3ConnectProfilePage() {
>
) : isInitialLoad ? (
-
+
Loading environments
) : environments.length > 0 ? (
@@ -178,7 +171,10 @@ export function T3ConnectProfilePage() {
subtitle={`${linkedAtLabel(environment.linkedAt)} · ${endpointLabel(environment)}`}
accessory={
deregisteringEnvironmentId === environment.environmentId ? (
-
+
) : (
) : controller.relayDiscovery.isRefreshing ? (
-
+
Loading linked cloud environments.
diff --git a/apps/mobile/src/features/connection/EnvironmentConnectionNotice.tsx b/apps/mobile/src/features/connection/EnvironmentConnectionNotice.tsx
index bb829673688c91062607a0596f6aca347110725e..fe389e443872430ce9bdb8deef4d88e9a66a7d56 100644
--- a/apps/mobile/src/features/connection/EnvironmentConnectionNotice.tsx
+++ b/apps/mobile/src/features/connection/EnvironmentConnectionNotice.tsx
@@ -1,10 +1,11 @@
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { ConnectionTraceId } from "./ConnectionTraceId";
import {
type EnvironmentConnectionPhase,
type EnvironmentConnectionPresentation,
} from "@t3tools/client-runtime/connection";
import { SymbolView } from "../../components/AppSymbol";
-import { ActivityIndicator, Pressable, View } from "react-native";
+import { Pressable, View } from "react-native";
import { AppText as Text } from "../../components/AppText";
@@ -65,7 +66,7 @@ export function EnvironmentConnectionNotice(props: {
{isRetrying ? (
-
+
) : (
>
) : focused && foreground ? (
-
+
) : null}
)}
@@ -428,7 +421,7 @@ function OpenDevicePreview({
>
) : (
<>
-
+
Connecting to device...
>
)}
diff --git a/apps/mobile/src/features/devices/DeviceStreamWebView.tsx b/apps/mobile/src/features/devices/DeviceStreamWebView.tsx
index c8831c59520237b9cf9394f1984c3f209576f5dc..8e0ed02affda0e29dc5f290e2042671cdd142014 100644
--- a/apps/mobile/src/features/devices/DeviceStreamWebView.tsx
+++ b/apps/mobile/src/features/devices/DeviceStreamWebView.tsx
@@ -9,10 +9,11 @@ import {
useState,
type Ref,
} from "react";
-import { ActivityIndicator, Platform, Pressable, View } from "react-native";
+import { Platform, Pressable, View } from "react-native";
import { WebView } from "react-native-webview";
import type { DeviceStreamStatus } from "@t3tools/client-runtime/device/stream";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { AppText } from "../../components/AppText";
import {
@@ -187,7 +188,7 @@ function DeviceStreamDocumentView({
className="absolute inset-0 items-center justify-center gap-4 px-6"
style={{ backgroundColor: background }}
>
- {status === "connecting" ? : null}
+ {status === "connecting" ? : null}
{state.status === "loading" ? (
-
+
Reading crash log…
) : state.status === "unavailable" ? (
diff --git a/apps/mobile/src/features/files/FilePreviewFeedback.tsx b/apps/mobile/src/features/files/FilePreviewFeedback.tsx
index 4c2f18e5b8320ea640d34e3be20dea4b5f32072e..b83b746659785b852ea5d2926b7819ac176d4c51 100644
--- a/apps/mobile/src/features/files/FilePreviewFeedback.tsx
+++ b/apps/mobile/src/features/files/FilePreviewFeedback.tsx
@@ -1,5 +1,6 @@
-import { ActivityIndicator, View } from "react-native";
+import { View } from "react-native";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { AppText as Text } from "../../components/AppText";
import { cn } from "../../lib/cn";
@@ -14,7 +15,7 @@ export function FilePreviewLoading(props: {
props.background === "card" ? "bg-card" : "bg-sheet",
)}
>
-
+
{props.message}
);
diff --git a/apps/mobile/src/features/files/FileTreeBrowser.tsx b/apps/mobile/src/features/files/FileTreeBrowser.tsx
index 4d313b47b49fcb851c58b95898f78fa1ac2d8129..30e034ac9859708c77d5b570b6359a9f08a42caa 100644
--- a/apps/mobile/src/features/files/FileTreeBrowser.tsx
+++ b/apps/mobile/src/features/files/FileTreeBrowser.tsx
@@ -1,8 +1,9 @@
import { LegendList } from "@legendapp/list/react-native";
import type { ProjectEntry } from "@t3tools/contracts";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { SymbolView } from "../../components/AppSymbol";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
-import { ActivityIndicator, Platform, Pressable, RefreshControl, View } from "react-native";
+import { Platform, Pressable, RefreshControl, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { AppText as Text } from "../../components/AppText";
@@ -96,7 +97,7 @@ const FileTreeRow = memo(function FileTreeRow(props: {
{node.name}
{node.kind === "directory" && props.expanded && props.loading ? (
-
+
) : null}
);
@@ -296,7 +297,7 @@ export function FileTreeBrowser(props: {
>
) : props.isPending ? (
-
+
) : (
<>
No files found
diff --git a/apps/mobile/src/features/files/WorkspaceFileWebPreview.tsx b/apps/mobile/src/features/files/WorkspaceFileWebPreview.tsx
index da95c687dd6638f614a3799f4f411e1f04f9792b..bc0c90da621259ebc1946fe9341c38a9889afff3 100644
--- a/apps/mobile/src/features/files/WorkspaceFileWebPreview.tsx
+++ b/apps/mobile/src/features/files/WorkspaceFileWebPreview.tsx
@@ -1,7 +1,8 @@
import { useState } from "react";
-import { ActivityIndicator, View } from "react-native";
+import { View } from "react-native";
import { WebView } from "react-native-webview";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { AppText as Text } from "../../components/AppText";
import { FilePreviewLoading } from "./FilePreviewFeedback";
import { LoadingStrip } from "../../components/LoadingStrip";
@@ -46,7 +47,7 @@ export function WorkspaceFileWebPreview(props: { readonly uri: string | null })
}}
renderLoading={() => (
-
+
)}
style={{ flex: 1, backgroundColor: "transparent" }}
diff --git a/apps/mobile/src/features/home/HomeScreen.tsx b/apps/mobile/src/features/home/HomeScreen.tsx
index 26230cd9422a837623782a3c36b0674a8b53444b..331606ffd4400b637a1b54c35f8a608560df7d5c 100644
--- a/apps/mobile/src/features/home/HomeScreen.tsx
+++ b/apps/mobile/src/features/home/HomeScreen.tsx
@@ -1,3 +1,4 @@
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { useAndroidControlSizing } from "../../components/useAndroidControlSizing";
import type { ThreadMoveDestination } from "../threads/threadOrder";
import { computeThreadMoveAvailability } from "../threads/threadOrder";
@@ -15,7 +16,6 @@ import { useAtomValue } from "@effect/atom-react";
import { useFocusEffect } from "@react-navigation/native";
import { use, useCallback, useEffect, useMemo, useRef, useState, type ComponentProps } from "react";
import {
- ActivityIndicator,
Platform,
ScrollView,
View,
@@ -947,7 +947,7 @@ export function HomeScreen(props: HomeScreenProps) {
/>
{emptyState.loading ? (
-
+
) : null}
diff --git a/apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx b/apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx
index d2b068a07c5996c679311a254d4988dbfe096bbc..1e461e3f0d7a88d9117d6d80dbf158dac56d3c92 100644
--- a/apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx
+++ b/apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx
@@ -1,7 +1,8 @@
import type { NativeStackNavigationOptions } from "@react-navigation/native-stack";
import { useEffect, useRef, useState, type ReactNode } from "react";
-import { ActivityIndicator, Animated, Platform, Pressable, View } from "react-native";
+import { Animated, Platform, Pressable, View } from "react-native";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { SymbolView } from "../../components/AppSymbol";
import { AppText as Text } from "../../components/AppText";
import { useAndroidControlSizing } from "../../components/useAndroidControlSizing";
@@ -134,7 +135,7 @@ export function WorkspaceConnectionTitle(props: {
]}
>
{status.showsProgress ? (
-
diff --git a/apps/mobile/src/features/projects/AddProjectScreen.tsx b/apps/mobile/src/features/projects/AddProjectScreen.tsx
index cc02851cb6ef364ea1832586ca38ccf7e205e795..88201bf359c4abd8352e06e5ff97002d403c369a 100644
--- a/apps/mobile/src/features/projects/AddProjectScreen.tsx
+++ b/apps/mobile/src/features/projects/AddProjectScreen.tsx
@@ -1,3 +1,4 @@
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { MaterialListRow } from "../../components/MaterialListRow";
import { SettingsScreen } from "../settings/components/SettingsScreen";
import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
@@ -55,7 +56,7 @@ import {
import { CommonActions, StackActions, useNavigation } from "@react-navigation/native";
import { SymbolView } from "../../components/AppSymbol";
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
-import { Platform, ActivityIndicator, Alert, Pressable, View } from "react-native";
+import { Platform, Alert, Pressable, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import * as Arr from "effect/Array";
import * as Cause from "effect/Cause";
@@ -265,7 +266,7 @@ function PrimaryActionButton(props: {
className="h-12 items-center justify-center rounded-full bg-primary active:opacity-70 disabled:opacity-45"
>
{props.loading ? (
-
+
) : (
{props.label}
)}
@@ -665,7 +666,7 @@ export function AddProjectSourceScreen() {
)}
{discoveryState.isPending ? (
-
+
) : null}
>
) : null}
@@ -918,7 +919,7 @@ function FolderBrowser(props: {
{fileAccess.isPending || (browseState.isPending && browseState.data === null) ? (
-
+
) : null}
{browsePath.canBrowseUp ? (
diff --git a/apps/mobile/src/features/review/ReviewSheet.tsx b/apps/mobile/src/features/review/ReviewSheet.tsx
index f77429bf79865c79d900eaacd58770ae6570223b..0ca575f938ff8345c4787b669384832765aeb012 100644
--- a/apps/mobile/src/features/review/ReviewSheet.tsx
+++ b/apps/mobile/src/features/review/ReviewSheet.tsx
@@ -1,6 +1,7 @@
import type { EnvironmentId, ThreadId } from "@t3tools/contracts";
import { formatDiffCount } from "@t3tools/client-runtime/diff-count";
import { useNavigation, type StaticScreenProps } from "@react-navigation/native";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { nativeHeaderScrollEdgeEffects } from "../../native/StackHeader";
import { ScreenHeader } from "../../components/ScreenHeader";
import type { ScreenHeaderMenuItem } from "../../components/ScreenHeader.types";
@@ -19,7 +20,6 @@ import {
useState,
} from "react";
import {
- ActivityIndicator,
FlatList,
Platform,
Pressable,
@@ -855,7 +855,7 @@ export function ReviewSheet(props: ReviewSheetProps) {
Platform.OS !== "android" && "border-b border-border bg-card",
)}
>
-
+
Loading diff…
) : parsedDiff.kind === "empty" ? (
diff --git a/apps/mobile/src/features/settings/SettingsClientStorageRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsClientStorageRouteScreen.tsx
index 3d25c749bc8b7a44ec71b9eb81e597466e6920bb..9533e10d43bb7390532f434b1d556d5023b25365 100644
--- a/apps/mobile/src/features/settings/SettingsClientStorageRouteScreen.tsx
+++ b/apps/mobile/src/features/settings/SettingsClientStorageRouteScreen.tsx
@@ -1,9 +1,10 @@
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
import { useAtomSet, useAtomValue } from "@effect/atom-react";
import { type EnvironmentMachineKind, resolveEnvironmentMachineKind } from "@t3tools/contracts";
import { AsyncResult } from "effect/reactivity";
import { useMemo } from "react";
-import { ActivityIndicator, Alert, Pressable, View } from "react-native";
+import { Alert, Pressable, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { AppText as Text } from "../../components/AppText";
@@ -99,7 +100,7 @@ export function SettingsClientStorageRouteScreen() {
) : !summary ? (
-
+
Inspecting cached data…
diff --git a/apps/mobile/src/features/settings/components/SettingsActionRow.tsx b/apps/mobile/src/features/settings/components/SettingsActionRow.tsx
index 98d5033ccbe38c06224783a9ffc964a4378fb3ae..efef1f0146684eb6ae7c85de28799c91339e29f6 100644
--- a/apps/mobile/src/features/settings/components/SettingsActionRow.tsx
+++ b/apps/mobile/src/features/settings/components/SettingsActionRow.tsx
@@ -1,6 +1,7 @@
import type { ComponentProps } from "react";
-import { ActivityIndicator, Platform, Pressable } from "react-native";
+import { Platform, Pressable } from "react-native";
+import { AppActivityIndicator } from "../../../components/AppActivityIndicator";
import { SymbolView } from "../../../components/AppSymbol";
import { AppText as Text } from "../../../components/AppText";
import { MaterialListRow } from "../../../components/MaterialListRow";
@@ -26,7 +27,9 @@ export function SettingsActionRow(props: {
weight="regular"
/>
);
- const spinner = props.loading ? : null;
+ const spinner = props.loading ? (
+
+ ) : null;
if (Platform.OS === "android") {
return (
diff --git a/apps/mobile/src/features/settings/components/SettingsLegalDocumentRouteScreen.tsx b/apps/mobile/src/features/settings/components/SettingsLegalDocumentRouteScreen.tsx
index 86e1008a14bd7518386b276f243dff752d8d7d26..5e197c30c094d583f79bc4479d84374c7c719cfd 100644
--- a/apps/mobile/src/features/settings/components/SettingsLegalDocumentRouteScreen.tsx
+++ b/apps/mobile/src/features/settings/components/SettingsLegalDocumentRouteScreen.tsx
@@ -1,8 +1,9 @@
import { type NavigationProp, type ParamListBase, useNavigation } from "@react-navigation/native";
import { useCallback, useLayoutEffect, useState } from "react";
-import { ActivityIndicator, Linking, Pressable, View } from "react-native";
+import { Linking, Pressable, View } from "react-native";
import { WebView } from "react-native-webview";
+import { AppActivityIndicator } from "../../../components/AppActivityIndicator";
import { AppText as Text } from "../../../components/AppText";
import { LoadingStrip } from "../../../components/LoadingStrip";
import { SymbolView } from "../../../components/AppSymbol";
@@ -172,7 +173,7 @@ export function SettingsLegalDocumentRouteScreen({
}}
renderLoading={() => (
-
+
)}
style={{ flex: 1, backgroundColor: "transparent" }}
diff --git a/apps/mobile/src/features/threads/GitActionProgressOverlay.tsx b/apps/mobile/src/features/threads/GitActionProgressOverlay.tsx
index 802759b5a2c7c4772581c35a45704727d01fe289..4bc0dabd701c7687b4d67007eb52b4b9fbf60262 100644
--- a/apps/mobile/src/features/threads/GitActionProgressOverlay.tsx
+++ b/apps/mobile/src/features/threads/GitActionProgressOverlay.tsx
@@ -1,8 +1,9 @@
import * as Haptics from "expo-haptics";
import { GlassView } from "expo-glass-effect";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { SymbolView } from "../../components/AppSymbol";
import { useCallback, useEffect, useRef } from "react";
-import { ActivityIndicator, Pressable, StyleSheet, useColorScheme, View } from "react-native";
+import { Pressable, StyleSheet, useColorScheme, View } from "react-native";
import Animated, { FadeIn, FadeOut, LinearTransition } from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -158,7 +159,7 @@ function OverlayContent(props: { readonly progress: GitActionProgress }) {
function OverlayIcon(props: { readonly phase: GitActionProgress["phase"] }) {
switch (props.phase) {
case "running":
- return ;
+ return ;
case "success":
return (
diff --git a/apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx b/apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx
index 8330a6b1b54f79363d63006fd48951f8d664a4ac..1e9922b497a6103dbca66cf416acb4c7d1461d47 100644
--- a/apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx
+++ b/apps/mobile/src/features/threads/NewTaskContextPickerScreens.tsx
@@ -1,3 +1,4 @@
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { MaterialListRow } from "../../components/MaterialListRow";
import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell";
import { shouldCheckoutNewTaskBranch } from "./new-task-context-presentation";
@@ -11,15 +12,7 @@ import {
import * as Haptics from "expo-haptics";
import { useNavigation } from "@react-navigation/native";
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
-import {
- ActivityIndicator,
- Alert,
- Platform,
- Pressable,
- ScrollView,
- TextInput,
- View,
-} from "react-native";
+import { Alert, Platform, Pressable, ScrollView, TextInput, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { AndroidScreenHeader } from "../../components/AndroidScreenHeader";
@@ -486,7 +479,7 @@ export function BranchPickerScreen(props: {
: 0,
}}
>
- {props.loading ? : null}
+ {props.loading ? : null}
{props.loading
? "Loading branches…"
@@ -525,7 +518,7 @@ export function BranchPickerScreen(props: {
ListFooterComponent={
props.refreshing ? (
-
+
) : null
}
diff --git a/apps/mobile/src/features/threads/NewTaskRouteScreen.tsx b/apps/mobile/src/features/threads/NewTaskRouteScreen.tsx
index b015b0508bb559b0a9a6b58a6b14ae0c30d26c66..2b4c6258bc70960e8d5d50187b6072539a87a9a5 100644
--- a/apps/mobile/src/features/threads/NewTaskRouteScreen.tsx
+++ b/apps/mobile/src/features/threads/NewTaskRouteScreen.tsx
@@ -1,3 +1,4 @@
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { MaterialListRow } from "../../components/MaterialListRow";
import { ScreenHeader } from "../../components/ScreenHeader";
import {
@@ -15,7 +16,7 @@ import {
} from "@t3tools/client-runtime/state/runtime";
import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell";
import { useEffect, useRef, useState } from "react";
-import { ActivityIndicator, Alert, Platform, Pressable, View } from "react-native";
+import { Alert, Platform, Pressable, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { cn } from "../../lib/cn";
import { MaterialScreenContent } from "../../components/MaterialScreenContent";
@@ -369,7 +370,7 @@ export function NewTaskRouteScreen({ route }: StaticScreenProps
{projectEmptyState.loading ? (
-
+
) : null}
{projectEmptyState.title}
diff --git a/apps/mobile/src/features/threads/ThreadFeed.tsx b/apps/mobile/src/features/threads/ThreadFeed.tsx
index 469a10907c35d79c8806a5008cbd3a602680f0a0..0304227c299753068c57e1e86ee3db59b30fa3ec 100644
--- a/apps/mobile/src/features/threads/ThreadFeed.tsx
+++ b/apps/mobile/src/features/threads/ThreadFeed.tsx
@@ -1,6 +1,7 @@
import { ThreadContextDivider } from "./thread-context-divider";
import { ThreadHandoffRow } from "./thread-handoff-row";
import { SecretRequestCard } from "./SecretRequestCard";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import {
WorktreeWorkingHeader,
WorktreeSetupCard,
@@ -79,7 +80,6 @@ import {
type PartialMarkdownTheme,
} from "react-native-nitro-markdown";
import {
- ActivityIndicator,
Alert,
Image,
Platform,
@@ -378,7 +378,7 @@ function AssistantForkButton(props: {
className="h-7 w-7 items-center justify-center disabled:opacity-40"
>
{busy ? (
-
+
) : (
-
+
);
}
@@ -630,7 +630,7 @@ function MessageAttachmentFile(props: {
>
{opening ? (
-
+
) : (
)}
@@ -3310,7 +3310,7 @@ function ThreadFeedLoadEarlierControl(props: ThreadFeedHistoryControls) {
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"
>
{props.loading ? (
-
+
) : (
)}
diff --git a/apps/mobile/src/features/threads/ThreadMarkdownImage.tsx b/apps/mobile/src/features/threads/ThreadMarkdownImage.tsx
index fe9e6d48b8bd99a19a76a8f56a20f293ffdc9f4d..f37866707cc9c9097e2cb2a4304e3efdc256b301 100644
--- a/apps/mobile/src/features/threads/ThreadMarkdownImage.tsx
+++ b/apps/mobile/src/features/threads/ThreadMarkdownImage.tsx
@@ -1,14 +1,8 @@
import type { AssetResource, EnvironmentId } from "@t3tools/contracts";
import { createContext, useContext, useEffect, useId, useState } from "react";
-import {
- ActivityIndicator,
- Image,
- Pressable,
- StyleSheet,
- View,
- type ViewStyle,
-} from "react-native";
+import { Image, Pressable, StyleSheet, View, type ViewStyle } from "react-native";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { AppText as Text } from "../../components/AppText";
import type { FilePreviewSource } from "../../components/FilePreviewModal";
import { MediaActionsMenu } from "../../components/MediaActionsMenu";
@@ -96,7 +90,7 @@ export function ThreadMarkdownImageView(props: {
{failed ? (
Image unavailable
) : (
-
+
)}
diff --git a/apps/mobile/src/features/threads/floating-working-control.tsx b/apps/mobile/src/features/threads/floating-working-control.tsx
index 8672e383452138512a1390913a02ca49fd755c13..3140f7669578a4e0528c379f2316fac4e848b40a 100644
--- a/apps/mobile/src/features/threads/floating-working-control.tsx
+++ b/apps/mobile/src/features/threads/floating-working-control.tsx
@@ -3,7 +3,6 @@ import { formatDuration } from "@t3tools/shared/orchestrationTiming";
import { GlassContainer, GlassView } from "expo-glass-effect";
import { type ReactNode, useEffect, useRef, useState } from "react";
import {
- ActivityIndicator,
type LayoutChangeEvent,
Pressable,
Text as SystemText,
@@ -22,6 +21,7 @@ import Animated, {
} from "react-native-reanimated";
import { withUniwind } from "uniwind";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { AppText as Text } from "../../components/AppText";
import { SymbolView } from "../../components/AppSymbol";
import { ControlPill } from "../../components/ControlPill";
@@ -364,7 +364,7 @@ function FloatingStatusLabel(props: {
className="gap-2"
onLayout={props.onLayout}
>
-
+
{props.status.label}
@@ -402,7 +402,7 @@ function FloatingStatusLabel(props: {
onPress={props.status.onPress}
>
{props.status.tone === "reconnecting" ? (
-
+
) : (
)}
diff --git a/apps/mobile/src/features/threads/worktree-setup-card.tsx b/apps/mobile/src/features/threads/worktree-setup-card.tsx
index 86b71992710f8920445079ef26a54d6894ef8b2b..b2080b002d91be10c6babcb3c9e09fe69d27e22e 100644
--- a/apps/mobile/src/features/threads/worktree-setup-card.tsx
+++ b/apps/mobile/src/features/threads/worktree-setup-card.tsx
@@ -6,8 +6,9 @@ import {
import { worktreeSetupAgentStarted } from "@t3tools/client-runtime/worktree-setup";
import { formatDuration } from "@t3tools/shared/orchestrationTiming";
import { useEffect, useState } from "react";
-import { ActivityIndicator, AppState, Pressable, ScrollView, View } from "react-native";
+import { AppState, Platform, Pressable, ScrollView, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
+import { AppActivityIndicator } from "../../components/AppActivityIndicator";
import { AppText as Text } from "../../components/AppText";
import { SymbolView, type AppSymbolName } from "../../components/AppSymbol";
import { WorktreeSetupSheet } from "./worktree-setup-sheet";
@@ -97,10 +98,14 @@ export function WorktreeSetupCard(props: WorktreeSetupCardProps) {
}
>
{backgroundSetup ? (
-
) : failed ? (
{stage.status === "running" && animate ? (
-
{props.loading ? (
-
+
) : (