diff --git a/apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx b/apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx
index 2cfe50de830a40a1f2d46ece9a23dbc7c7651f84..9e89ea1b24e49602364c447803d7ce2e7c2fe815 100644
--- a/apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx
+++ b/apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx
@@ -1,6 +1,5 @@
-import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader";
+import { NativeStackScreenOptions } from "../../native/StackHeader";
import { StackActions, useNavigation, type StaticScreenProps } from "@react-navigation/native";
-import type { MenuAction } from "@react-native-menu/menu";
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
import { Platform, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -14,10 +13,8 @@ import {
} from "@t3tools/shared/filePreview";
import { mediaFileReference } from "@t3tools/client-runtime/media-reference";
-import { AndroidHeaderIconButton, AndroidScreenHeader } from "../../components/AndroidScreenHeader";
import { MaterialScreenContent } from "../../components/MaterialScreenContent";
import { AudioFilePreview } from "../../components/AudioFilePreview";
-import { ControlPillMenu } from "../../components/ControlPill";
import { EmptyState } from "../../components/EmptyState";
import { FilePreviewModal, type FilePreviewSource } from "../../components/FilePreviewModal";
import { LoadingScreen } from "../../components/LoadingScreen";
@@ -38,14 +35,6 @@ import {
useAdaptiveWorkspacePaneRole,
useRegisterWorkspaceInspector,
} from "../layout/AdaptiveWorkspaceLayout";
-import {
- createNativeMailSearchToolbarItem,
- NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED,
-} from "../layout/native-mail-search-toolbar";
-import {
- AndroidWorkspaceSidebarButton,
- WorkspaceSidebarToolbar,
-} from "../layout/workspace-sidebar-toolbar";
import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider";
import { ThreadRouteScreen } from "../threads/ThreadRouteScreen";
import { FilePreviewLoading, FilePreviewNotice } from "./FilePreviewFeedback";
@@ -55,7 +44,7 @@ import { useFileTreeEntries } from "./useFileTreeEntries";
import { preloadWorkspaceFileContents } from "./preload-workspace-file";
import { SourceFileSurface } from "./SourceFileSurface";
import { ThreadFileNavigatorPane } from "./thread-file-navigator-pane";
-import { MaterialFilesHeader } from "./MaterialFilesHeader";
+import { ScreenHeader } from "../../components/ScreenHeader";
import { WorkspaceFileImagePreview } from "./WorkspaceFileImagePreview";
import { WorkspaceFilePreviewError } from "./WorkspaceFilePreviewError";
import { WorkspaceFileVideoPreview } from "./WorkspaceFileVideoPreview";
@@ -70,6 +59,107 @@ import {
} from "./filePath";
import { useWorkspaceFileAssetUrlState } from "./workspaceFileAssetUrl";
+function FilesBrowserHeader(props: {
+ readonly projectName: string;
+ readonly searchQuery: string;
+ readonly onSearchQueryChange: (query: string) => void;
+ readonly onRefresh: () => void;
+ readonly onBack: () => void;
+}) {
+ return (
+
+ );
+}
+
+function FileHeader(props: {
+ readonly title: string;
+ readonly subtitle: string;
+ readonly iconColor: string;
+ readonly activeMode: string;
+ readonly fileInspectorSupported: boolean;
+ readonly onBack: () => void;
+ readonly onReturnToThread: () => void;
+ readonly actions: ReadonlyArray<{
+ readonly id: string;
+ readonly title: string;
+ readonly icon: string;
+ readonly inline: boolean;
+ readonly onPress: () => unknown;
+ }>;
+}) {
+ const { panes, toggleAuxiliaryPane } = useAdaptiveWorkspaceLayout();
+ const modes = props.actions.filter(({ inline }) => inline);
+ return (
+ 0
+ ? [
+ {
+ id: "modes",
+ inline: true,
+ items: modes.map((action) => ({
+ ...action,
+ selected: action.id === props.activeMode,
+ })),
+ },
+ ]
+ : []),
+ ...props.actions.filter(({ inline }) => !inline),
+ ],
+ },
+ ]}
+ />
+ );
+}
+
type FileViewMode = "preview" | "source";
function firstRouteParam(value: string | string[] | undefined): string | null {
@@ -315,14 +405,10 @@ function FilesToolbarBottomFade() {
export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) {
useAdaptiveWorkspacePaneRole("inspector");
const navigation = useNavigation();
- const { fileInspector, layout, panes, showAuxiliaryPane, togglePrimarySidebar } =
- useAdaptiveWorkspaceLayout();
+ const { fileInspector, layout, showAuxiliaryPane } = useAdaptiveWorkspaceLayout();
const [searchQuery, setSearchQuery] = useState("");
- const isAndroid = Platform.OS === "android";
const { themeAppearance: highlightTheme } = useAppearancePreferences();
- const theme = useUniwindTheme();
- const headerColor = theme["--color-header"];
- const sheetSurfaceColor = theme["--color-sheet-solid"];
+ const headerColor = useUniwindTheme()["--color-header"];
const { cwd, environmentId, projectName, selectedThread, threadId } = useThreadFilesWorkspace(
props.route.params,
);
@@ -430,84 +516,15 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) {
);
}
- const usesCompactMailToolbar =
- Platform.OS === "ios" && !layout.usesSplitView && NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED;
-
const content = (
<>
- {/* Static header config (glass preset and title) lives in Stack.tsx. The
- live sheet color stays dynamic here so the FlatList can remain the
- direct scene child for native scroll-edge sampling. */}
- 0 ? projectName : undefined,
- // No refresh button: the list already supports pull-to-refresh.
- unstable_headerToolbarItems: usesCompactMailToolbar
- ? () => [
- createNativeMailSearchToolbarItem({
- onSearchTextChange: setSearchQuery,
- placeholder: "Search files",
- searchTextChangeId: "files-search-text",
- }),
- ]
- : undefined,
- headerSearchBarOptions: usesCompactMailToolbar
- ? undefined
- : {
- allowToolbarIntegration: true,
- autoCapitalize: "none",
- hideNavigationBar: false,
- placeholder: "Search files",
- onChangeText: (event) => {
- setSearchQuery(event.nativeEvent.text);
- },
- onCancelButtonPress: () => {
- setSearchQuery("");
- },
- },
- }}
+
- {isAndroid ? (
- <>
- {
- }
- searchQuery={searchQuery}
- onSearchQueryChange={setSearchQuery}
- onRefresh={entriesQuery.refresh}
- onBack={handleReturnToThread}
- />
- }
- >
- ) : (
- <>
- {layout.usesSplitView ? (
-
-
-
- ) : null}
- {usesCompactMailToolbar ? null : (
-
-
-
- )}
- >
- )}
(
- () =>
- fileMenuActions.map((action) => ({
- id: action.id,
- title: action.title,
- image: action.icon,
- state: action.id === resolvedActiveMode ? "on" : undefined,
- })),
- [fileMenuActions, resolvedActiveMode],
- );
- const handleAndroidFileMenuAction = useCallback(
- (event: { nativeEvent: { event: string } }) => {
- const action = fileMenuActions.find(({ id }) => id === event.nativeEvent.event);
- void action?.onPress();
- },
- [fileMenuActions],
- );
const handleReturnToThread = useCallback(() => {
if (environmentId !== null && threadId !== null) {
navigation.dispatch(
@@ -878,100 +877,16 @@ export function ThreadFileScreen(props: ThreadFileRouteScreenProps) {
return (
- 0 ? headerSubtitle : undefined,
- }}
+
- {isAndroid ? (
- }
- hideBottomBorder
- onBack={handleBack}
- trailing={
- <>
- {fileInspector.supported ? (
-
- ) : null}
-
-
-
- >
- }
- />
- ) : null}
-
- {fileInspector.supported ? (
-
- ) : null}
-
-
- {fileInspector.supported ? (
-
- ) : null}
-
- {fileMenuActions.some(({ inline }) => inline) ? (
-
- {fileMenuActions
- .filter(({ inline }) => inline)
- .map((action) => (
-
- {action.title}
-
- ))}
-
- ) : null}
- {fileMenuActions
- .filter(({ inline }) => !inline)
- .map((action) => (
-
- {action.title}
-
- ))}
-
-