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
14 changes: 11 additions & 3 deletions apps/mobile/src/features/archive/ArchivedThreadsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
import { relativeTime } from "../../lib/time";
import { useThemeColor } from "../../lib/useThemeColor";
import { ThreadSwipeable } from "../home/thread-swipe-actions";
import { createNativeMailSearchToolbarItem } from "../layout/native-mail-search-toolbar";
import {
createNativeMailSearchToolbarItem,
NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED,
} from "../layout/native-mail-search-toolbar";
import type { ArchivedThreadGroup, ArchivedThreadSortOrder } from "./archivedThreadList";

export interface ArchivedThreadsHeaderEnvironment {
Expand Down Expand Up @@ -70,7 +73,8 @@ function ArchivedThreadsHeader(props: {
const searchIconColor = useThemeColor("--color-icon");
const searchTextColor = useThemeColor("--color-foreground");
const usesNativeChrome = Platform.OS === "ios";
const usesCompactMailToolbar = Platform.OS === "ios" && width < 700;
const usesCompactMailToolbar =
Platform.OS === "ios" && width < 700 && NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED;
const androidFilterActions = useMemo<MenuAction[]>(
() => [
{
Expand Down Expand Up @@ -272,7 +276,11 @@ function ArchivedThreadsHeader(props: {
...(usesNativeChrome
? {
allowToolbarIntegration: true,
placement: "integratedButton" as const,
// "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,
Expand Down
8 changes: 6 additions & 2 deletions apps/mobile/src/features/files/ThreadFilesRouteScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ import {
useAdaptiveWorkspacePaneRole,
useRegisterWorkspaceInspector,
} from "../layout/AdaptiveWorkspaceLayout";
import { createNativeMailSearchToolbarItem } from "../layout/native-mail-search-toolbar";
import {
createNativeMailSearchToolbarItem,
NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED,
} from "../layout/native-mail-search-toolbar";
import { WorkspaceSidebarToolbar } from "../layout/workspace-sidebar-toolbar";
import { ReviewHighlighterProvider } from "../review/ReviewHighlighterProvider";
import { ThreadRouteScreen } from "../threads/ThreadRouteScreen";
Expand Down Expand Up @@ -354,7 +357,8 @@ export function ThreadFilesTreeScreen(props: ThreadFilesRouteScreenProps) {
);
}

const usesCompactMailToolbar = Platform.OS === "ios" && !layout.usesSplitView;
const usesCompactMailToolbar =
Platform.OS === "ios" && !layout.usesSplitView && NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED;

return (
<>
Expand Down
97 changes: 47 additions & 50 deletions apps/mobile/src/features/home/HomeHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import { useThemeColor } from "../../lib/useThemeColor";
import { useThreadListV2Enabled } from "../threads/use-thread-list-v2-enabled";
import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands";
import { withNativeGlassHeaderItem } from "../layout/native-glass-header-items";
import { createNativeMailSearchToolbarItem } from "../layout/native-mail-search-toolbar";
import {
createNativeMailSearchToolbarItem,
NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED,
} from "../layout/native-mail-search-toolbar";
import type { HomeProjectSortOrder } from "./homeThreadList";
import {
buildHomeListFilterMenu,
Expand Down Expand Up @@ -320,9 +323,11 @@ function IosHomeHeader(props: HomeHeaderProps) {
}),
]
: undefined,
unstable_headerToolbarItems:
Platform.OS === "ios"
? () => [
// The keys below are set per-branch (not `undefined`) so a later
// reapply cannot clobber options owned by NativeHeaderToolbar.
...(NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED
? {
unstable_headerToolbarItems: () => [
createNativeMailSearchToolbarItem({
composeButtonId: "home-new-task",
composeSystemImageName: "square.and.pencil",
Expand All @@ -336,14 +341,14 @@ function IosHomeHeader(props: HomeHeaderProps) {
placeholder: "Search",
searchTextChangeId: "home-search-text",
}),
]
: undefined,
headerSearchBarOptions:
Platform.OS === "ios"
? undefined
: {
],
}
: {
// Pre-Liquid-Glass iOS: standard pull-down search in the nav
// bar; create + sort live in the plain bottom toolbar below.
headerSearchBarOptions: {
ref: searchBarRef,
allowToolbarIntegration: true,
autoCapitalize: "none" as const,
hideNavigationBar: false,
placeholder: "Search",
onCancelButtonPress: () => {
Expand All @@ -353,21 +358,11 @@ function IosHomeHeader(props: HomeHeaderProps) {
props.onSearchQueryChange(event.nativeEvent.text);
},
},
}),
}}
/>

{Platform.OS === "ios" ? null : (
<NativeHeaderToolbar placement="right">
<NativeHeaderToolbar.Button
accessibilityLabel="Open settings"
icon="gearshape"
onPress={props.onOpenSettings}
separateBackground
/>
</NativeHeaderToolbar>
)}

{Platform.OS === "ios" ? null : (
{NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED ? null : (
<NativeHeaderToolbar placement="bottom">
<NativeHeaderToolbar.Menu
accessibilityLabel="Filter and sort threads"
Expand Down Expand Up @@ -421,35 +416,37 @@ function IosHomeHeader(props: HomeHeaderProps) {
</NativeHeaderToolbar.Menu>
) : null}

<NativeHeaderToolbar.Menu title="Sort projects">
<NativeHeaderToolbar.Label>Sort projects</NativeHeaderToolbar.Label>
{PROJECT_SORT_OPTIONS.map((option) => (
<NativeHeaderToolbar.MenuAction
key={option.value}
isOn={props.projectSortOrder === option.value}
onPress={() => props.onProjectSortOrderChange(option.value)}
>
<NativeHeaderToolbar.Label>{option.label}</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
))}
</NativeHeaderToolbar.Menu>
{threadListV2Enabled ? null : (
<NativeHeaderToolbar.Menu title="Sort projects">
<NativeHeaderToolbar.Label>Sort projects</NativeHeaderToolbar.Label>
{PROJECT_SORT_OPTIONS.map((option) => (
<NativeHeaderToolbar.MenuAction
key={option.value}
isOn={props.projectSortOrder === option.value}
onPress={() => props.onProjectSortOrderChange(option.value)}
>
<NativeHeaderToolbar.Label>{option.label}</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
))}
</NativeHeaderToolbar.Menu>
)}

<NativeHeaderToolbar.Menu title="Sort threads">
<NativeHeaderToolbar.Label>Sort threads</NativeHeaderToolbar.Label>
{THREAD_SORT_OPTIONS.map((option) => (
<NativeHeaderToolbar.MenuAction
key={option.value}
isOn={props.threadSortOrder === option.value}
onPress={() => props.onThreadSortOrderChange(option.value)}
>
<NativeHeaderToolbar.Label>{option.label}</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
))}
</NativeHeaderToolbar.Menu>
{threadListV2Enabled ? null : (
<NativeHeaderToolbar.Menu title="Sort threads">
<NativeHeaderToolbar.Label>Sort threads</NativeHeaderToolbar.Label>
{THREAD_SORT_OPTIONS.map((option) => (
<NativeHeaderToolbar.MenuAction
key={option.value}
isOn={props.threadSortOrder === option.value}
onPress={() => props.onThreadSortOrderChange(option.value)}
>
<NativeHeaderToolbar.Label>{option.label}</NativeHeaderToolbar.Label>
</NativeHeaderToolbar.MenuAction>
))}
</NativeHeaderToolbar.Menu>
)}
</NativeHeaderToolbar.Menu>
<NativeHeaderToolbar.Spacer width={8} sharesBackground={false} />
<NativeHeaderToolbar.SearchBarSlot />
<NativeHeaderToolbar.Spacer width={8} sharesBackground={false} />
<NativeHeaderToolbar.Spacer flexible />
<NativeHeaderToolbar.Button
accessibilityLabel="New task"
icon="square.and.pencil"
Expand Down
18 changes: 13 additions & 5 deletions apps/mobile/src/features/home/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ interface HomeScreenProps {
/* ─── Layout constants ───────────────────────────────────────────────── */

const ESTIMATED_THREAD_ROW_HEIGHT = 72;
const PRE_LIQUID_GLASS_BOTTOM_TOOLBAR_HEIGHT = 44;
/**
* Top spacing between the list and the Android custom header. The Android
* header (AndroidHomeHeader) is rendered in-flow above this screen and
Expand Down Expand Up @@ -196,6 +197,10 @@ export function HomeScreen(props: HomeScreenProps) {
const listRef = useRef<LegendListRef | null>(null);
const insets = useSafeAreaInsets();
const accentColor = useThemeColor("--color-icon-muted");
const iosBottomToolbarClearance =
Platform.OS === "ios" && !NATIVE_LIQUID_GLASS_SUPPORTED
? PRE_LIQUID_GLASS_BOTTOM_TOOLBAR_HEIGHT
: 0;
const searchEnvironmentIds = useMemo(
() =>
props.selectedEnvironmentId === null
Expand Down Expand Up @@ -877,7 +882,7 @@ export function HomeScreen(props: HomeScreenProps) {
<View
className="flex-1 items-center justify-center bg-screen px-8"
style={{
paddingBottom: Math.max(insets.bottom, 24),
paddingBottom: Math.max(insets.bottom, 24) + iosBottomToolbarClearance,
Comment thread
cursor[bot] marked this conversation as resolved.
paddingTop: NATIVE_LIQUID_GLASS_SUPPORTED ? insets.top + 72 : 0,
}}
>
Expand Down Expand Up @@ -1019,7 +1024,7 @@ export function HomeScreen(props: HomeScreenProps) {
contentContainerStyle={{
paddingBottom:
Platform.OS === "ios"
? Math.max(insets.bottom, 24) + 96
? Math.max(insets.bottom, 24) + 96 + iosBottomToolbarClearance
: Math.max(insets.bottom, 16) + 88,
}}
/>
Expand Down Expand Up @@ -1060,16 +1065,19 @@ export function HomeScreen(props: HomeScreenProps) {
scrollEventThrottle={16}
contentContainerStyle={{
// Android reserves room for the floating new-task FAB
// (56 button + 16 gap + bottom inset).
// (56 button + 16 gap + bottom inset). Pre-glass iOS shows a
Comment thread
cursor[bot] marked this conversation as resolved.
// standard 44pt bottom toolbar that overlays the list and is not
// reflected in insets while contentInsetAdjustmentBehavior is
// "never".
paddingBottom:
Platform.OS === "ios"
? Math.max(insets.bottom, 24) + 24
? Math.max(insets.bottom, 24) + 24 + iosBottomToolbarClearance
: Math.max(insets.bottom, 16) + 88,
}}
scrollIndicatorInsets={
Platform.OS === "ios"
? {
bottom: Math.max(insets.bottom, 16) + 24,
bottom: Math.max(insets.bottom, 16) + 24 + iosBottomToolbarClearance,
top: 0,
}
: undefined
Expand Down
11 changes: 11 additions & 0 deletions apps/mobile/src/features/layout/native-mail-search-toolbar.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import type { HeaderBarButtonMailSearchToolbarItem } from "react-native-screens";

import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";

/**
* The patched mail-style toolbar is built natively from iOS 26 Liquid Glass
* UIKit (`UIGlassEffect`) with no earlier fallback: pre-26 the native side
* silently drops the item and hides the navigation toolbar entirely. Screens
* that send it must fall back to standard search/toolbar primitives when this
* is false.
*/
export const NATIVE_MAIL_SEARCH_TOOLBAR_SUPPORTED = NATIVE_LIQUID_GLASS_SUPPORTED;

type NativeMailSearchToolbarInput = Omit<
HeaderBarButtonMailSearchToolbarItem,
"type" | "useFallbackSearchField"
Expand Down
9 changes: 8 additions & 1 deletion apps/mobile/src/native/StackHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,8 @@ function convertToolbarChild(child: ReactNode): NativeStackHeaderItem | null {
return {
type: "spacing",
spacing: typeof child.props.width === "number" ? child.props.width : 8,
};
flexible: Boolean(child.props.flexible),
} as NativeStackHeaderItem;
}

return null;
Expand All @@ -351,6 +352,11 @@ function collectToolbarItems(children: ReactNode): NativeStackHeaderItem[] {
Children.forEach(children, (child) => {
const item = convertToolbarChild(child);
if (item) {
if (item.type === "spacing") {
// Native inserts spacing items at `index`, treating a missing index
// as 0 — which would move the spacer in front of earlier siblings.
(item as { index?: number }).index = items.length;
}
items.push(item);
}
});
Expand Down Expand Up @@ -440,6 +446,7 @@ function NativeHeaderToolbarLabel(_props: { readonly children?: ReactNode }) {
NativeHeaderToolbarLabel.displayName = "NativeHeaderToolbarLabel";

function NativeHeaderToolbarSpacer(_props: {
readonly flexible?: boolean;
readonly sharesBackground?: boolean;
readonly width?: number;
}) {
Expand Down
Loading