Skip to content
Open
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
114 changes: 114 additions & 0 deletions apps/mobile/src/features/threads/ThreadHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { StackActions, useNavigation } from "@react-navigation/native";
import { useMemo } from "react";
import { ScreenHeader } from "../../components/ScreenHeader";
import { ScreenHeaderButton } from "../../components/ScreenHeaderButton";
import type { ScreenHeaderAction } from "../../components/ScreenHeader.types";
import { useAdaptiveWorkspaceLayout } from "../layout/AdaptiveWorkspaceLayout";
import type { ThreadInspectorMode } from "./thread-inspector-content-stack";
import { useThreadHeaderOptions } from "./useThreadHeaderOptions";

export function ThreadHeader(
props: Parameters<typeof useThreadHeaderOptions>[0] & {
readonly hasThreadCwd: boolean;
readonly hasWorkspaceRoot: boolean;
readonly fileInspectorSupported: boolean;
readonly inspectorMode: ThreadInspectorMode | null;
readonly onToggleInspector: () => void;
readonly onOpenGitInspector: () => void;
readonly onOpenFilesInspector: () => void;
},
) {
const navigation = useNavigation();
const { layout, panes, toggleAuxiliaryPane } = useAdaptiveWorkspaceLayout();
const { onOpenTerminal, onMergeBack } = props.gitControls;
const native = useThreadHeaderOptions(props);
const androidHeaderActions = useMemo<ReadonlyArray<ScreenHeaderAction>>(() => {
const actions: ScreenHeaderAction[] = [];
if (props.onReturnToThread) {
actions.push({
accessibilityLabel: "Return to chat",
icon: "chevron.left",
onPress: props.onReturnToThread,
});
}
if (props.hasThreadCwd) {
const filesVisible = props.inspectorMode === "files" && panes.auxiliaryPaneVisible;
actions.push({
accessibilityLabel: filesVisible ? "Close files" : "Open files",
selected: filesVisible,
icon: "folder",
onPress: filesVisible ? toggleAuxiliaryPane : props.onOpenFilesInspector,
});
}
if (props.hasWorkspaceRoot) {
actions.push({
accessibilityLabel: "Open terminal",
icon: "terminal",
onPress: () => onOpenTerminal(null),
});
}
actions.push({
accessibilityLabel: "Open git controls",
icon: "point.topleft.down.curvedto.point.bottomright.up",
onPress: props.onOpenGitInspector,
});
if (onMergeBack) {
actions.push({
accessibilityLabel: "Merge back to source",
icon: "arrow.triangle.merge",
onPress: onMergeBack,
});
}
return actions;
}, [
props.inspectorMode,
panes.auxiliaryPaneVisible,
props.onOpenFilesInspector,
onOpenTerminal,
onMergeBack,
props.onOpenGitInspector,
toggleAuxiliaryPane,
props.onReturnToThread,
props.hasThreadCwd,
props.hasWorkspaceRoot,
]);

return (
<>
<ScreenHeader
title={props.title}
subtitle={props.subtitle}
sidebar={native.sidebar}
options={native.options}
optionsVersion={native.optionsVersion}
trailing={
props.fileInspectorSupported && props.hasThreadCwd ? (
<ScreenHeaderButton
accessibilityLabel={
props.inspectorMode !== null && panes.auxiliaryPaneVisible
? "Hide inspector"
: "Show inspector"
}
icon="sidebar.right"
selected={props.inspectorMode !== null && panes.auxiliaryPaneVisible}
onPress={props.onToggleInspector}
/>
) : null
}
onBack={
layout.usesSplitView
? undefined
: () => {
// A deep link or cold start has no previous route; Home is the way out.
// Read the history at press time: it changes without re-rendering this screen.
if (navigation.canGoBack()) navigation.goBack();
else navigation.dispatch(StackActions.replace("Home"));
}
}
actions={androidHeaderActions}
hideBottomBorder
/>
{native.fallback}
</>
);
}
111 changes: 1 addition & 110 deletions apps/mobile/src/features/threads/ThreadRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ import { makeTurnCommandMetadata } from "../../lib/commandMetadata";
import { buildProjectThreadStartTurnInput } from "../../lib/projectThreadStartTurn";
import { useWorktreeSetup } from "./use-worktree-setup";
import { worktreeSetupAgentStarted } from "@t3tools/client-runtime/worktree-setup";
import { ScreenHeader } from "../../components/ScreenHeader";
import { ScreenHeaderButton } from "../../components/ScreenHeaderButton";
import type { ScreenHeaderAction } from "../../components/ScreenHeader.types";
import { useThreadHeaderOptions } from "./useThreadHeaderOptions";
import {
StackActions,
useFocusEffect,
Expand Down Expand Up @@ -83,112 +79,7 @@ import {
type ThreadInspectorMode,
} from "./thread-inspector-content-stack";
import { threadRouteIsHydrating } from "./thread-route-hydration";

function ThreadHeader(
props: Parameters<typeof useThreadHeaderOptions>[0] & {
readonly hasThreadCwd: boolean;
readonly hasWorkspaceRoot: boolean;
readonly fileInspectorSupported: boolean;
readonly inspectorMode: ThreadInspectorMode | null;
readonly onToggleInspector: () => void;
readonly onOpenGitInspector: () => void;
readonly onOpenFilesInspector: () => void;
},
) {
const navigation = useNavigation();
const { layout, panes, toggleAuxiliaryPane } = useAdaptiveWorkspaceLayout();
const { onOpenTerminal, onMergeBack } = props.gitControls;
const native = useThreadHeaderOptions(props);
const androidHeaderActions = useMemo<ReadonlyArray<ScreenHeaderAction>>(() => {
const actions: ScreenHeaderAction[] = [];
if (props.onReturnToThread) {
actions.push({
accessibilityLabel: "Return to chat",
icon: "chevron.left",
onPress: props.onReturnToThread,
});
}
if (props.hasThreadCwd) {
const filesVisible = props.inspectorMode === "files" && panes.auxiliaryPaneVisible;
actions.push({
accessibilityLabel: filesVisible ? "Close files" : "Open files",
selected: filesVisible,
icon: "folder",
onPress: filesVisible ? toggleAuxiliaryPane : props.onOpenFilesInspector,
});
}
if (props.hasWorkspaceRoot) {
actions.push({
accessibilityLabel: "Open terminal",
icon: "terminal",
onPress: () => onOpenTerminal(null),
});
}
actions.push({
accessibilityLabel: "Open git controls",
icon: "point.topleft.down.curvedto.point.bottomright.up",
onPress: props.onOpenGitInspector,
});
if (onMergeBack) {
actions.push({
accessibilityLabel: "Merge back to source",
icon: "arrow.triangle.merge",
onPress: onMergeBack,
});
}
return actions;
}, [
props.inspectorMode,
panes.auxiliaryPaneVisible,
props.onOpenFilesInspector,
onOpenTerminal,
onMergeBack,
props.onOpenGitInspector,
toggleAuxiliaryPane,
props.onReturnToThread,
props.hasThreadCwd,
props.hasWorkspaceRoot,
]);

return (
<>
<ScreenHeader
title={props.title}
subtitle={props.subtitle}
sidebar={native.sidebar}
options={native.options}
optionsVersion={props.gitControls.projectScripts}
trailing={
props.fileInspectorSupported && props.hasThreadCwd ? (
<ScreenHeaderButton
accessibilityLabel={
props.inspectorMode !== null && panes.auxiliaryPaneVisible
? "Hide inspector"
: "Show inspector"
}
icon="sidebar.right"
selected={props.inspectorMode !== null && panes.auxiliaryPaneVisible}
onPress={props.onToggleInspector}
/>
) : null
}
onBack={
layout.usesSplitView
? undefined
: () => {
// A deep link or cold start has no previous route; Home is the way out.
// Read the history at press time: it changes without re-rendering this screen.
if (navigation.canGoBack()) navigation.goBack();
else navigation.dispatch(StackActions.replace("Home"));
}
}
actions={androidHeaderActions}
hideBottomBorder
/>
{native.fallback}
</>
);
}
import { ThreadHeader } from "./ThreadHeader";

interface ThreadInspectorSelection {
readonly routeThreadIdentity: string | null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export function useThreadHeaderOptions(
): ReturnType<typeof useIosThreadHeaderOptions> {
return {
options: { contentStyle: { backgroundColor: props.headerColor } },
// Android renders header actions as children, not native item factories.
optionsVersion: [],
sidebar: true,
fallback: null,
};
Expand Down
Loading
Loading