diff --git a/apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx b/apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx index 6161c203f11de563dee23fffc03f20367597174b..a191a9c7175b0c4c4c468242c14684c26398bd84 100644 --- a/apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx +++ b/apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx @@ -8,15 +8,12 @@ import { type EnvironmentMachineKind, resolveEnvironmentMachineKind, } from "@t3tools/contracts"; -import type { MenuAction } from "@react-native-menu/menu"; -import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader"; -import { SymbolView } from "../../components/AppSymbol"; import { useNavigation } from "@react-navigation/native"; +import { ScreenHeader } from "../../components/ScreenHeader"; +import { SymbolView } from "../../components/AppSymbol"; import { useCallback, useMemo, useRef, type ComponentProps } from "react"; import { - TextInput, ActivityIndicator, - Platform, Pressable, RefreshControl, useWindowDimensions, @@ -26,19 +23,13 @@ import { Gesture, GestureDetector } from "react-native-gesture-handler"; import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable"; import { AppText as Text } from "../../components/AppText"; -import { ControlPillMenu } from "../../components/ControlPill"; import { EmptyState } from "../../components/EmptyState"; import { EnvironmentMachineSymbol } from "../../components/EnvironmentMachineSymbol"; import { ProjectFavicon } from "../../components/ProjectFavicon"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; import { relativeTime } from "../../lib/time"; import { useUniwindTheme } from "../../lib/useUniwindTheme"; import { useServerConfigs } from "../../state/entities"; import { ThreadSwipeable } from "../home/thread-swipe-actions"; -import { - createNativeMailSearchToolbarItem, - NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED, -} from "../layout/native-mail-search-toolbar"; import type { ArchivedThreadGroup, ArchivedThreadSortOrder } from "./archivedThreadList"; import { SettingsScreenContent } from "../settings/components/SettingsScreen"; @@ -47,6 +38,85 @@ export interface ArchivedThreadsHeaderEnvironment { readonly label: string; } +function ArchivedThreadsHeader(props: { + readonly environments: ReadonlyArray; + readonly searchQuery: string; + readonly selectedEnvironmentId: EnvironmentId | null; + readonly sortOrder: ArchivedThreadSortOrder; + readonly onEnvironmentChange: (environmentId: EnvironmentId | null) => void; + readonly onRefresh: () => void; + readonly onSearchQueryChange: (query: string) => void; + readonly onSortOrderChange: (sortOrder: ArchivedThreadSortOrder) => void; +}) { + const navigation = useNavigation(); + const { width } = useWindowDimensions(); + const hasCustomFilter = props.selectedEnvironmentId !== null || props.sortOrder !== "newest"; + return ( + navigation.goBack()} + search={{ + value: props.searchQuery, + onChangeText: props.onSearchQueryChange, + placeholder: "Search archived threads", + compactPlaceholder: "Search", + mode: "inline", + compactToolbar: width < 700, + onRefresh: props.onRefresh, + refreshInToolbar: true, + refreshAccessibilityLabel: "Refresh archived threads", + }} + menus={[ + { + title: "Archived thread options", + icon: hasCustomFilter + ? "line.3.horizontal.decrease.circle.fill" + : "line.3.horizontal.decrease.circle", + items: [ + { + id: "environment", + title: "Environment", + items: [ + { + id: "environment:all", + title: "All environments", + selected: props.selectedEnvironmentId === null, + onPress: () => props.onEnvironmentChange(null), + }, + ...props.environments.map((environment) => ({ + id: `environment:${environment.environmentId}`, + title: environment.label, + selected: props.selectedEnvironmentId === environment.environmentId, + onPress: () => props.onEnvironmentChange(environment.environmentId), + })), + ], + }, + { + id: "sort", + title: "Sort by archived date", + items: [ + { + id: "sort:newest", + title: "Newest first", + selected: props.sortOrder === "newest", + onPress: () => props.onSortOrderChange("newest"), + }, + { + id: "sort:oldest", + title: "Oldest first", + selected: props.sortOrder === "oldest", + onPress: () => props.onSortOrderChange("oldest"), + }, + ], + }, + ], + }, + ]} + /> + ); +} + type ArchivedThreadListItem = | { readonly kind: "project"; @@ -64,309 +134,6 @@ type ArchivedThreadListItem = readonly thread: EnvironmentThreadShell; }; -function ArchivedThreadsHeader(props: { - readonly environments: ReadonlyArray; - readonly searchQuery: string; - readonly selectedEnvironmentId: EnvironmentId | null; - readonly sortOrder: ArchivedThreadSortOrder; - readonly onEnvironmentChange: (environmentId: EnvironmentId | null) => void; - readonly onRefresh: () => void; - readonly onSearchQueryChange: (query: string) => void; - readonly onSortOrderChange: (sortOrder: ArchivedThreadSortOrder) => void; -}) { - const { width } = useWindowDimensions(); - const navigation = useNavigation(); - const insets = useSafeAreaInsets(); - const hasCustomFilter = props.selectedEnvironmentId !== null || props.sortOrder !== "newest"; - const usesNativeChrome = Platform.OS === "ios"; - const usesCompactMailToolbar = - Platform.OS === "ios" && width < 700 && NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED; - const androidFilterActions = useMemo( - () => [ - { - id: "environment", - title: "Environment", - subactions: [ - { - id: "environment:all", - title: "All environments", - state: props.selectedEnvironmentId === null ? ("on" as const) : undefined, - }, - ...props.environments.map((environment) => ({ - id: `environment:${environment.environmentId}`, - title: environment.label, - state: - props.selectedEnvironmentId === environment.environmentId - ? ("on" as const) - : undefined, - })), - ], - }, - { - id: "sort", - title: "Sort by archived date", - subactions: [ - { - id: "sort:newest", - title: "Newest first", - state: props.sortOrder === "newest" ? ("on" as const) : undefined, - }, - { - id: "sort:oldest", - title: "Oldest first", - state: props.sortOrder === "oldest" ? ("on" as const) : undefined, - }, - ], - }, - ], - [props.environments, props.selectedEnvironmentId, props.sortOrder], - ); - const handleAndroidFilterAction = useCallback( - (event: { nativeEvent: { event: string } }) => { - const action = event.nativeEvent.event; - if (action === "environment:all") { - props.onEnvironmentChange(null); - } else if (action.startsWith("environment:")) { - props.onEnvironmentChange(action.slice("environment:".length) as EnvironmentId); - } else if (action === "sort:newest") { - props.onSortOrderChange("newest"); - } else if (action === "sort:oldest") { - props.onSortOrderChange("oldest"); - } - }, - [props.onEnvironmentChange, props.onSortOrderChange], - ); - - if (Platform.OS === "android") { - // Single header row matching the app's Android chrome (AndroidScreenHeader - // palette): back chevron, inline search, filter menu. - return ( - <> - - - - navigation.goBack()} - className="size-11 items-center justify-center" - > - - - - - - - - - - - - - - - ); - } - const archiveFilterMenu = { - title: "Archived thread options", - items: [ - { - type: "submenu" as const, - title: "Environment", - items: [ - { - type: "action" as const, - title: "All environments", - state: props.selectedEnvironmentId === null ? ("on" as const) : ("off" as const), - onPress: () => props.onEnvironmentChange(null), - }, - ...props.environments.map((environment) => ({ - type: "action" as const, - title: environment.label, - state: - props.selectedEnvironmentId === environment.environmentId - ? ("on" as const) - : ("off" as const), - onPress: () => props.onEnvironmentChange(environment.environmentId), - })), - ], - }, - { - type: "submenu" as const, - title: "Sort by archived date", - items: [ - { - type: "action" as const, - title: "Newest first", - state: props.sortOrder === "newest" ? ("on" as const) : ("off" as const), - onPress: () => props.onSortOrderChange("newest"), - }, - { - type: "action" as const, - title: "Oldest first", - state: props.sortOrder === "oldest" ? ("on" as const) : ("off" as const), - onPress: () => props.onSortOrderChange("oldest"), - }, - ], - }, - ], - }; - - return ( - <> - {/* Static header config (glass preset + title) lives in Stack.tsx; only - dynamic toolbar/search wiring is set here. */} - [ - createNativeMailSearchToolbarItem({ - composeButtonId: "archived-refresh", - composeSystemImageName: "arrow.clockwise", - filterMenu: archiveFilterMenu, - filterButtonId: "archived-filter", - filterSystemImageName: hasCustomFilter - ? "line.3.horizontal.decrease.circle.fill" - : "line.3.horizontal.decrease", - onComposePress: props.onRefresh, - onSearchTextChange: props.onSearchQueryChange, - placeholder: "Search", - searchTextChangeId: "archived-search-text", - }), - ] - : undefined, - headerSearchBarOptions: usesCompactMailToolbar - ? undefined - : { - ...(usesNativeChrome - ? { - allowToolbarIntegration: true, - // "integratedButton" is an iOS 26 search-bar placement; - // pre-glass iOS keeps the default pull-down placement. - ...(NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED - ? { placement: "integratedButton" as const } - : null), - } - : { - placement: "stacked" as const, - }), - autoCapitalize: "none", - hideNavigationBar: false, - obscureBackground: false, - placeholder: "Search archived threads", - onChangeText: (event) => { - props.onSearchQueryChange(event.nativeEvent.text); - }, - onCancelButtonPress: () => { - props.onSearchQueryChange(""); - }, - }, - }} - /> - - {usesCompactMailToolbar ? null : ( - - {usesNativeChrome ? ( - - ) : null} - - - Environment - props.onEnvironmentChange(null)} - > - All environments - - {props.environments.map((environment) => ( - props.onEnvironmentChange(environment.environmentId)} - > - {environment.label} - - ))} - - - - Sort by archived date - props.onSortOrderChange("newest")} - > - Newest first - - props.onSortOrderChange("oldest")} - > - Oldest first - - - - - )} - - ); -} - function ProjectGroupLabel(props: { readonly environmentLabel: string | null; readonly environmentMachine: EnvironmentMachineKind;