Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/mobile/src/components/ScreenHeader.android.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export function ScreenHeader(props: ScreenHeaderProps) {
icon: "magnifyingglass",
onPress: () => setSearchOpen(true),
},
...(search.onRefresh
...(search.refreshInToolbar && search.onRefresh
? [
{
accessibilityLabel: search.refreshAccessibilityLabel ?? "Refresh",
Expand Down
13 changes: 10 additions & 3 deletions apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { SymbolView } from "../../components/AppSymbol";
import { useCallback, useMemo, useRef, type ComponentProps } from "react";
import {
ActivityIndicator,
Platform,
Pressable,
RefreshControl,
useWindowDimensions,
Expand Down Expand Up @@ -63,9 +64,6 @@ function ArchivedThreadsHeader(props: {
compactPlaceholder: "Search",
mode: "inline",
compactToolbar: width < 700,
onRefresh: props.onRefresh,
refreshInToolbar: true,
refreshAccessibilityLabel: "Refresh archived threads",
}}
menus={[
{
Expand Down Expand Up @@ -110,6 +108,15 @@ function ArchivedThreadsHeader(props: {
},
],
},
...(Platform.OS === "android"
? [
{
id: "refresh",
title: "Refresh archived threads",
onPress: props.onRefresh,
},
]
: []),
],
},
]}
Expand Down
29 changes: 3 additions & 26 deletions apps/mobile/src/features/connection/CloudEnvironmentRows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,14 @@ interface CloudEnvironmentRowsProps {
readonly showcaseAvailableEnvironments?: ReadonlyArray<RelayEnvironmentView>;
readonly showcaseSignedIn?: boolean;
/**
* Hide the "T3 Connect" section title + refresh button for hosts that
* provide their own chrome (the onboarding sheet's native header and
* pull-to-refresh).
* Hide the "T3 Connect" section title when the host provides its own header.
*/
readonly showHeader?: boolean;
}

/**
* "T3 Connect" section: every environment published to the signed-in account,
* with connect switches, availability status, refresh, and loading/error
* with connect switches, availability status, and loading/error
* states. Shared between the Settings environments screen and the T3 Connect
* onboarding sheet.
*
Expand Down Expand Up @@ -120,29 +118,8 @@ function CloudEnvironmentRowsContent(
return (
<View collapsable={false} className={cn("gap-3", showHeader && "mt-5")}>
{showHeader ? (
<View className="flex-row items-center justify-between px-1">
<View className="px-1">
<Text className="text-sm font-t3-bold uppercase text-foreground-muted">T3 Connect</Text>
{discoveryAvailable ? (
<Pressable
accessibilityRole="button"
disabled={controller.relayDiscovery.isRefreshing}
onPress={() => {
void controller.refreshRelayEnvironments();
}}
className="h-9 w-9 items-center justify-center rounded-full bg-subtle active:opacity-70 disabled:opacity-50"
>
{controller.relayDiscovery.isRefreshing ? (
<ActivityIndicator colorClassName={"accent-icon"} size="small" />
) : (
<SymbolView
name="arrow.clockwise"
size={14}
tintColorClassName={"accent-icon"}
type="monochrome"
/>
)}
</Pressable>
) : null}
</View>
) : null}

Expand Down
32 changes: 21 additions & 11 deletions apps/mobile/src/features/files/FileTreeBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,22 +234,14 @@ export function FileTreeBrowser(props: {
],
);

if (props.error && props.entries.length === 0) {
return (
<View className="flex-1 bg-sheet px-4 py-5">
<Text className="text-sm font-t3-bold text-foreground">Files unavailable</Text>
<Text className="mt-1 text-xs leading-normal text-foreground-muted">{props.error}</Text>
</View>
);
}

// SPIKE: render the FlatList as the screen's DIRECT content (no wrapping View), and
// mirror the Home ScrollView exactly — `contentInsetAdjustmentBehavior: "automatic"`
// with NO manual contentInset. iOS only applies the nav-bar top inset + scroll-edge
// blur to a scroll view in the screen's primary position; a scroll view buried in
// flex-1 Views is ignored, which is why the tree rendered under the header with no blur.
return (
<FlatList
alwaysBounceVertical
className="flex-1"
data={visibleNodes}
keyExtractor={(item) => item.node.path}
Expand All @@ -270,7 +262,7 @@ export function FileTreeBrowser(props: {
renderItem={renderItem}
ListHeaderComponent={
<>
{props.error ? (
{props.error && props.entries.length > 0 ? (
<Text accessibilityRole="alert" className="mx-4 my-2 text-xs text-foreground-muted">
{props.error}
</Text>
Expand All @@ -284,7 +276,25 @@ export function FileTreeBrowser(props: {
}
ListEmptyComponent={
<View className="px-4 py-5">
{props.isPending ? (
{props.error && props.entries.length === 0 ? (
<>
<Text className="text-sm font-t3-bold text-foreground">Files unavailable</Text>
<Text
accessibilityRole="alert"
className="mt-1 text-xs leading-normal text-foreground-muted"
>
{props.error}
</Text>
<Pressable
accessibilityRole="button"
onPress={props.onRefresh}
disabled={props.isPending}
className="mt-3 min-h-11 self-start justify-center rounded-full bg-subtle px-4 active:opacity-70 disabled:opacity-50"
>
<Text className="text-sm font-t3-medium text-foreground">Try again</Text>
</Pressable>
</>
) : props.isPending ? (
<ActivityIndicator size="small" />
) : (
<>
Expand Down
23 changes: 18 additions & 5 deletions apps/mobile/src/features/files/MaterialFilesHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BackHandler, Keyboard, type TextInput, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";

import { AndroidHeaderIconButton, AndroidScreenHeader } from "../../components/AndroidScreenHeader";
import { AndroidAnchoredMenu } from "../../components/AndroidAnchoredMenu";
import { MaterialSearchField } from "../../components/MaterialSearchField";

/** Keep Files search in the same header row on compact and expanded layouts. */
Expand Down Expand Up @@ -55,12 +56,24 @@ export function MaterialFilesHeader(props: {
icon: "magnifyingglass",
onPress: () => setSearchOpen(true),
},
{
accessibilityLabel: "Refresh files",
icon: "arrow.clockwise",
onPress: props.onRefresh,
},
]}
trailing={
<AndroidAnchoredMenu
title="File options"
actions={[{ id: "refresh", title: "Refresh files" }]}
onPressAction={({ nativeEvent }) => {
if (nativeEvent.event === "refresh") props.onRefresh();
}}
>
{(open) => (
<AndroidHeaderIconButton
accessibilityLabel="File options"
icon="ellipsis"
onPress={open}
/>
)}
</AndroidAnchoredMenu>
}
/>
</View>
{searching ? (
Expand Down
13 changes: 11 additions & 2 deletions apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,20 @@ function FilesBrowserHeader(props: {
value: props.searchQuery,
onChangeText: props.onSearchQueryChange,
placeholder: "Search files",
onRefresh: props.onRefresh,
refreshAccessibilityLabel: "Refresh files",
closeAccessibilityLabel: "Close file search",
clearAccessibilityLabel: "Clear file search",
}}
menus={
Platform.OS === "android"
? [
{
title: "File options",
icon: "ellipsis",
items: [{ id: "refresh", title: "Refresh files", onPress: props.onRefresh }],
},
]
: undefined
}
/>
);
}
Expand Down
12 changes: 1 addition & 11 deletions apps/mobile/src/features/files/thread-file-navigator-pane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,6 @@ export function ThreadFileNavigatorPane(props: {
const nativeHeaderRightBarButtonItems = useMemo(
() =>
[
{
accessibilityLabel: "Refresh files",
icon: { name: "arrow.clockwise", type: "sfSymbol" as const },
identifier: "thread-file-navigator-refresh",
onPress: entriesQuery.refresh,
sharesBackground: false,
tintColor: foregroundColor,
type: "button" as const,
width: 44,
},
{
accessibilityLabel: "Close files",
icon: { name: "xmark", type: "sfSymbol" as const },
Expand All @@ -76,7 +66,7 @@ export function ThreadFileNavigatorPane(props: {
width: 44,
},
] as ComponentProps<typeof ScreenStackHeaderConfig>["headerRightBarButtonItems"],
[entriesQuery.refresh, foregroundColor, toggleAuxiliaryPane],
[foregroundColor, toggleAuxiliaryPane],
);

const fileTree = (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
import { useNavigation } from "@react-navigation/native";
import { useAtomValue } from "@effect/atom-react";
import { managedRelaySessionAtom } from "@t3tools/client-runtime/relay";
import type { EnvironmentId } from "@t3tools/contracts";
import { useCallback, useState } from "react";
import { useCallback, useRef, useState } from "react";
import { Platform, RefreshControl } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";

import { SettingsScreen } from "./components/SettingsScreen";
import { AndroidAnchoredMenu } from "../../components/AndroidAnchoredMenu";
import { AndroidHeaderIconButton } from "../../components/AndroidScreenHeader";
import { CloudEnvironmentRows } from "../connection/CloudEnvironmentRows";
import { LocalEnvironmentList } from "../connection/LocalEnvironmentList";
import { GitHubRoutingSettings } from "../connection/GitHubRoutingSettings";
import { splitEnvironmentSections } from "../connection/environmentSections";
import { useUniwindTheme } from "../../lib/useUniwindTheme";
import { useRemoteConnections } from "../../state/use-remote-environment-registry";
import { relayEnvironmentDiscovery } from "../../state/relay";
import { useAtomCommand } from "../../state/use-atom-command";
import {
applyShowcaseLocalEnvironmentDisplayUrls,
resolveShowcaseEnvironmentUpdateDisplayUrl,
Expand Down Expand Up @@ -42,6 +49,24 @@ export function SettingsEnvironmentsRouteScreen() {
: environmentSections.connectedCloudEnvironments;
const [expandedId, setExpandedId] = useState<EnvironmentId | null>(null);
const headerIconColor = useUniwindTheme()["--color-icon"];
const relaySession = useAtomValue(managedRelaySessionAtom);
const refreshRelayEnvironments = useAtomCommand(
relayEnvironmentDiscovery.refresh,
"relay environment refresh",
);
const [isRefreshingCloud, setIsRefreshingCloud] = useState(false);
const cloudRefreshPendingRef = useRef(false);
async function refreshCloudEnvironments() {
if (!relaySession || cloudRefreshPendingRef.current) return;
cloudRefreshPendingRef.current = true;
setIsRefreshingCloud(true);
try {
await refreshRelayEnvironments();
} finally {
cloudRefreshPendingRef.current = false;
setIsRefreshingCloud(false);
}
}

const handleToggle = useCallback((environmentId: EnvironmentId) => {
setExpandedId((prev) => (prev === environmentId ? null : environmentId));
Expand Down Expand Up @@ -76,6 +101,31 @@ export function SettingsEnvironmentsRouteScreen() {
return (
<SettingsScreen
title="Environments"
trailing={
Platform.OS === "android" && relaySession ? (
<AndroidAnchoredMenu
title="Environment options"
actions={[
{
id: "refresh",
title: "Refresh cloud environments",
attributes: { disabled: isRefreshingCloud },
},
]}
onPressAction={({ nativeEvent }) => {
if (nativeEvent.event === "refresh") void refreshCloudEnvironments();
}}
>
{(open) => (
<AndroidHeaderIconButton
accessibilityLabel="Environment options"
icon="ellipsis"
onPress={open}
/>
)}
</AndroidAnchoredMenu>
) : undefined
}
actions={[
{
accessibilityLabel: "Add environment",
Expand All @@ -90,13 +140,22 @@ export function SettingsEnvironmentsRouteScreen() {
]}
>
<ScrollView
alwaysBounceVertical
contentInsetAdjustmentBehavior="automatic"
showsVerticalScrollIndicator={false}
className="flex-1"
contentContainerClassName="px-5 pt-4"
contentContainerStyle={{
paddingBottom: Math.max(insets.bottom, 18) + 18,
}}
refreshControl={
relaySession ? (
<RefreshControl
refreshing={isRefreshingCloud}
onRefresh={() => void refreshCloudEnvironments()}
/>
) : undefined
}
>
<LocalEnvironmentList
environments={localEnvironments}
Expand Down
Loading
Loading