镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
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
2 changes: 1 addition & 1 deletion apps/mobile/src/components/ui/AppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { NativeStackScreenOptions, useScreenOptionsWriter } from "../../native/S
import { useNativeWorkspaceColumnsSupported } from "../../native/NativeWorkspaceColumns";
import { useDeviceType } from "../../native/native-layout-metrics";
import { isDuoDevice } from "../../lib/device-type";
import { useAdaptiveWorkspaceLayout } from "../../features/layout/AdaptiveWorkspaceLayout";
import { useAdaptiveWorkspaceLayout } from "../../features/layout/adaptive-workspace-context";
import { useSidebarToggle } from "../../features/layout/useSidebarToggle";
import { InspectorCloseContext } from "../../features/layout/inspector-bar-store";
import {
Expand Down
32 changes: 26 additions & 6 deletions apps/mobile/src/features/layout/AdaptiveWorkspaceLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,11 @@ import {
workspaceTabColumns,
workspaceTabNavigationState,
} from "./workspace/workspace-tab-navigation";
import type { WorkspaceTab } from "./workspace/workspace-tabs-state";
import { workspaceUsesColumns, type WorkspaceTab } from "./workspace/workspace-tabs-state";
import { workspaceRoutesOf } from "./workspace-overlay-routes";
import { SettingsWorkspaceLayout } from "../settings/SettingsWorkspaceLayout";
import { WorkspaceThreadSidebar } from "./workspace/WorkspaceThreadSidebar";
import { WorkspaceSettingsSidebar } from "../settings/WorkspaceSettingsSidebar";

export { useAdaptiveWorkspaceLayout, useWorkspacePaneState };

Expand Down Expand Up @@ -263,11 +264,11 @@ function AdaptiveWorkspaceLayoutContent(
const { width: viewportWidth, height } = nativeMetrics ?? windowDimensions;
const pathname = props.pathname;
const workspace = useWorkspaceTabs();
// Settings and Usage occupy the workspace rather than a list/detail pair.
// Android workspace pages fill the viewport; legacy iPad pages retain their sidebar navigation.
//
// Android 工作区页面铺满窗口;旧版 iPad 保留侧栏导航,避免全屏内容与局部导航错位。
const androidSettingsOpen = useAndroidWorkspaceRoot()?.settingsOpen ?? false;
const safeAreaInsets = useSafeAreaInsets();
const workspacePage =
Platform.OS === "android" && (androidSettingsOpen || workspace?.state.selectedTab === "usage");
const navigation = useNavigation();
const [primarySidebarPreferredVisible, setPrimarySidebarPreferredVisible] = useState(true);
const [fileInspectorPreferredVisible, setFileInspectorPreferredVisible] = useState(true);
Expand All @@ -290,6 +291,18 @@ function AdaptiveWorkspaceLayoutContent(
() => deriveLayout({ width: viewportWidth, height, nativeMetrics, nativeWorkspace }),
[height, viewportWidth, nativeMetrics, nativeWorkspace],
);
const workspacePage =
(Platform.OS === "android" &&
(androidSettingsOpen || workspace?.state.selectedTab === "usage")) ||
(Platform.OS === "ios" &&
!nativeWorkspace &&
workspace !== null &&
!workspaceUsesColumns(
workspace.state.selectedTab,
workspace.settingsOpen,
nativeWorkspace,
baseLayout.usesSplitView,
));
// The overlay sidebar closes when a page opens from it, and stays open on a list or on the
// page a tab switch opened (`keepOpenFor`, set by that switch).
const [overlaySidebar, setOverlaySidebar] = useState({
Expand Down Expand Up @@ -1010,8 +1023,15 @@ function AdaptiveWorkspaceLayoutContent(
/>
)}
>
{/* Without a sidebar route (iPadOS before 26) the columns draw the thread list. */}
{sidebar ?? <WorkspaceThreadSidebar />}
{/* The legacy sidebar owns its list while the root navigator owns detail pages.

兼容侧栏承载目录,详情仍由根导航管理,切换设置时不再隐藏整列。 */}
{sidebar ??
(Platform.OS === "ios" && workspace?.settingsOpen ? (
<WorkspaceSettingsSidebar width={layout.listPaneWidth} />
) : (
<WorkspaceThreadSidebar />
))}
</RenderErrorBoundary>
</PaneContext>
{/* Keep the pill under the content throughout the sidebar's clipping animation. */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,12 @@ function WorkspaceStackView(props: ViewProps & { readonly nativeColumns: boolean
const descriptors = props.descriptors;
const baseRoute = { key: `${props.state.key}:workspace`, name: "Workspace" };
const visibleRoute = projection.detail.at(-1) ?? primaryRoute;
const workspacePage = !workspaceUsesColumns(workspace.state.selectedTab, workspace.settingsOpen);
const workspacePage = !workspaceUsesColumns(
workspace.state.selectedTab,
workspace.settingsOpen,
props.nativeColumns,
layout.usesSplitView,
);
const baseDescriptor: Descriptor = {
...primaryDescriptor,
navigation: props.descriptors[visibleRoute.key]!.navigation,
Expand Down Expand Up @@ -649,8 +654,13 @@ function WorkspaceStackNavigator({
);
return (
<NativeWorkspaceModeContext value={usesNativeWorkspaceColumns}>
<NavigationContent>
<WorkspaceTabsProvider settingsOpen={settingsOpen}>
{/* The fallback sidebar is rendered by NavigationContent's layout, outside its children.
Share tab state above that layout so sidebar actions and detail tabs use one owner.

兼容侧栏由 NavigationContent 的布局绘制,位于其 children 外部;
将标签状态提升到布局之上,使侧栏入口与详情导航共用同一份状态。 */}
<WorkspaceTabsProvider settingsOpen={settingsOpen}>
<NavigationContent>
<WorkspaceStackView
nativeColumns={usesNativeWorkspaceColumns}
{...rest}
Expand All @@ -659,8 +669,8 @@ function WorkspaceStackNavigator({
descriptors={descriptors}
navigation={navigation}
/>
</WorkspaceTabsProvider>
</NavigationContent>
</NavigationContent>
</WorkspaceTabsProvider>
</NativeWorkspaceModeContext>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
NAVIGATION_TAB_ICONS,
useEnabledNavigationTabs,
} from "./navigation-tabs";
import { useLayoutEffect, useRef, useState, type ReactNode } from "react";
import { use, useLayoutEffect, useRef, useState, type ReactNode } from "react";
import { View, useWindowDimensions, type NativeSyntheticEvent } from "react-native";
import { Tabs, type TabSelectedEvent, type TabSelectionRejectedEvent } from "react-native-screens";

Expand All @@ -16,6 +16,7 @@ import {
} from "./workspace-tabs-state";
import {
NativeWorkspaceTabActivityContext,
NativeWorkspaceModeContext,
WorkspaceSlotOptionsContext,
} from "../../../native/v5-workspace-context";
import {
Expand Down Expand Up @@ -83,6 +84,7 @@ export function WorkspaceNativeTabs(props: {
const selectTab = useSelectWorkspaceTab();
const [provenance, setProvenance] = useState(0);
const systemTabBar = useSystemWorkspaceTabBar();
const nativeColumns = use(NativeWorkspaceModeContext);
const deviceType = useDeviceType();
const compactTabs = useCompactWorkspaceTabs();
const { layout } = useAdaptiveWorkspaceLayout();
Expand All @@ -91,7 +93,12 @@ export function WorkspaceNativeTabs(props: {
const hiddenWithSidebar =
deviceType === "pad" &&
layout.usesSplitView &&
workspaceUsesColumns(workspace.state.selectedTab, workspace.settingsOpen) &&
workspaceUsesColumns(
workspace.state.selectedTab,
workspace.settingsOpen,
nativeColumns,
layout.usesSplitView,
) &&
!panes.primarySidebarVisible;
const floatingTabs = useFloatingWorkspaceTabs();
const { width } = useWindowDimensions();
Expand Down Expand Up @@ -161,9 +168,17 @@ export function WorkspaceNativeTabs(props: {
{(deviceType === "pad" ||
compactTabs ||
(layout.variant === "medium" &&
!workspaceUsesColumns(selectedTab, workspace.settingsOpen))) &&
!workspaceUsesColumns(selectedTab, workspace.settingsOpen, nativeColumns))) &&
!props.hidden &&
!props.tabsInSidebarColumn &&
// The legacy split sidebar owns its own tabs; this host sits in the detail column.
//
// 兼容分栏的侧栏自行绘制导航,此容器位于右侧详情,不能重复绘制。
!(
!nativeColumns &&
layout.usesSplitView &&
workspaceUsesColumns(selectedTab, workspace.settingsOpen, nativeColumns)
) &&
!hiddenWithSidebar ? (
<View
pointerEvents="box-none"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { StyleSheet, View } from "react-native";

import { StandaloneStack } from "../nested-stacks";
import { pageRoute } from "../route-options";
import { useDeviceType } from "../../../native/native-layout-metrics";
import { useWorkspaceTabs } from "./WorkspaceTabsContext";
import { WorkspaceSidebarTabs } from "./WorkspaceSidebarTabs";
import { getCompactBrandHeaderOptions } from "./brand-header-options";

const SIDEBAR_COLUMN_OPTIONS = {
Expand All @@ -15,6 +18,8 @@ export function WorkspaceSidebarColumn(props: {
readonly width: number;
readonly children: ReactNode;
}) {
const deviceType = useDeviceType();
const workspace = useWorkspaceTabs();
return (
<View
testID="thread-navigation-sidebar"
Expand All @@ -24,6 +29,10 @@ export function WorkspaceSidebarColumn(props: {
<StandaloneStack surface="sidebar" options={SIDEBAR_COLUMN_OPTIONS}>
{props.children}
</StandaloneStack>
{/* Keep legacy iPad navigation in its sidebar; the detail host previously covered the composer.

旧版 iPad 的导航跟随侧栏,避免此前挂在详情容器上遮挡输入框。 */}
{deviceType === "pad" && workspace !== null ? <WorkspaceSidebarTabs /> : null}
</View>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ describe("workspace tab selections", () => {
describe("opening a workspace tab", () => {
const columns = { columns: true, check: available };

it("opens the remembered Settings detail beside its list on legacy iPads", () => {
const next = workspaceTabNavigationState(
{ routes: [home, thread] },
"settings",
{ settings: "SettingsAppearance" },
{ columns: workspaceTabColumns("ios", "settings", true), check: available },
);
expect(names(next)).toEqual(["Home", "Settings", "SettingsAppearance"]);
expect(workspaceTabColumns("ios", "threads", true)).toBe(true);
expect(workspaceTabColumns("ios", "settings", false)).toBe(false);
});

it("opens Android Settings beside its default page only in a wide window", () => {
const open = (wide: boolean) =>
workspaceTabNavigationState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export function workspaceTabColumns(
tab: WorkspaceTab | "settings",
columns: boolean,
) {
// Both iPad column implementations host Settings lists, so restore the detail beside either one.
//
// 两种 iPad 分栏都承载设置目录,恢复右侧设置页,避免兼容布局只显示全屏目录。
return columns && (platform === "ios" || (platform === "android" && tab === "settings"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ describe("Duo list clearance", () => {
});

describe("workspace root navigation", () => {
it("keeps every legacy iPad tab in columns while retaining native Usage layout", () => {
expect(workspaceUsesColumns("threads", true, false)).toBe(true);
expect(workspaceUsesColumns("threads", true, true)).toBe(true);
expect(workspaceUsesColumns("usage", true, false)).toBe(true);
expect(workspaceUsesColumns("usage", true, true)).toBe(true);
expect(workspaceUsesColumns("threads", false, false)).toBe(true);
expect(workspaceUsesColumns("usage", false, false)).toBe(true);
expect(workspaceUsesColumns("usage", false, false, false)).toBe(false);
expect(workspaceUsesColumns("threads", true, false, false)).toBe(false);
});
it("pairs Settings, chats and pull requests with a detail column, and keeps Usage one page", () => {
expect(workspaceUsesColumns("usage")).toBe(false);
expect(workspaceUsesColumns("usage", true)).toBe(true);
Expand Down
19 changes: 16 additions & 3 deletions apps/mobile/src/features/layout/workspace/workspace-tabs-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,22 @@ export type WorkspaceTab = NavigationTab;
export type WorkspaceSearchTab = WorkspaceTab | "settings";
export type WorkspaceSearchMode = "resting" | "active" | "expanded";

/** Settings, Chats and pull requests pair a list with a detail column; Usage is one page. */
export function workspaceUsesColumns(tab: WorkspaceTab, settingsOpen = false) {
return settingsOpen || tab === "threads" || tab === "pullRequests";
/** Native Usage is one page; the legacy workspace keeps its sidebar for every tab.
*
* 原生分栏的用量为单页,兼容工作区的每个标签均保留侧栏。
*/
export function workspaceUsesColumns(
tab: WorkspaceTab,
settingsOpen = false,
nativeColumns = true,
splitView = true,
) {
// Legacy iPad tabs retain their sidebar; full-page Settings and Usage stranded its navigation.
//
// 旧版 iPad 各标签保留侧栏,避免设置和用量铺满页面后导航仍滞留在左下角。
return settingsOpen
? nativeColumns || splitView
: (!nativeColumns && splitView) || tab === "threads" || tab === "pullRequests";
}

export interface WorkspaceTabState {
Expand Down
7 changes: 4 additions & 3 deletions apps/mobile/src/features/settings/SettingsRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ScreenScrollView as ScrollView } from "../../components/ui/ScreenScroll
import { EmptyState } from "../../components/ui/EmptyState";
import { useAuth, useUser } from "@clerk/expo";
import { useNavigation, useNavigationState } from "@react-navigation/native";
import { use, type ComponentProps } from "react";
import { use, type ComponentProps, type ReactNode } from "react";
import { Platform, View } from "react-native";
import { useNativeColumnLayoutMetrics } from "../../native/native-layout-metrics";
import { deriveProjectGroupLabel } from "@t3tools/client-runtime/state/project-grouping";
Expand Down Expand Up @@ -65,13 +65,14 @@ function useSettingsListPlacement() {
return { sidebar, selected };
}

function SettingsListContent() {
export function SettingsListContent() {
return hasCloudPublicConfig() ? <ConfiguredSettingsRouteScreen /> : <LocalSettingsRouteScreen />;
}

export function SettingsListScreen(props: {
readonly sidebar: boolean;
readonly selected: string | null;
readonly children?: ReactNode;
}) {
const workspace = useWorkspaceTabs();
const environmentMenu = useSettingsEnvironmentMenu();
Expand All @@ -93,7 +94,7 @@ export function SettingsListScreen(props: {
: undefined
}
>
<SettingsListContent />
{props.children ?? <SettingsListContent />}
</SettingsScreen>
</View>
</SettingsSidebarContext>
Expand Down
Loading
Loading