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
5 changes: 4 additions & 1 deletion apps/mobile/src/components/CompactBrandTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function brandTitleOffset(nativeLeadingItem: boolean): number {
*/
export function CompactBrandTitle(
props: {
readonly allowFontScaling?: boolean;
readonly nativeLeadingItem?: boolean;
} = {},
) {
Expand All @@ -57,6 +58,7 @@ export function CompactBrandTitle(
>
<T3Wordmark color={iconColor} height={15} />
<Text
allowFontScaling={props.allowFontScaling}
style={{
color: mutedColor,
fontFamily: "DMSans-Medium",
Expand All @@ -75,6 +77,7 @@ export function CompactBrandTitle(
}}
>
<Text
allowFontScaling={props.allowFontScaling}
Comment thread
cursor[bot] marked this conversation as resolved.
style={{
color: mutedColor,
fontFamily: "DMSans-Bold",
Expand All @@ -91,7 +94,7 @@ export function CompactBrandTitle(
}

export function renderCompactBrandTitle() {
return <CompactBrandTitle />;
return <CompactBrandTitle allowFontScaling={Platform.OS === "ios"} />;
}

export function renderCompactBrandHeaderItems(): NativeStackHeaderItem[] {
Expand Down
4 changes: 2 additions & 2 deletions apps/mobile/src/features/home/AndroidHomeFab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { SymbolView } from "../../components/AppSymbol";
import { useThemeColor } from "../../lib/useThemeColor";

/**
* Android-only wrapper that overlays a bottom-right new-task FAB on the home
* screen. Other platforms render children unchanged.
* Android-only wrapper that overlays a bottom-right new-task FAB on a thread
* list. Other platforms render children unchanged.
*/
export function AndroidHomeFabLayout(props: {
readonly onStartNewTask: () => void;
Expand Down
25 changes: 16 additions & 9 deletions apps/mobile/src/features/home/HomeRouteScreen.tsx
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as Arr from "effect/Array";
import * as Order from "effect/Order";
import { useNavigation } from "@react-navigation/native";
import { useEffect, useMemo, useState } from "react";
import { Platform } from "react-native";

import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader";
import { useProjects, useThreadShells } from "../../state/entities";
Expand Down Expand Up @@ -104,7 +105,11 @@ export function HomeRouteScreen() {
return (
<>
<NativeStackScreenOptions
options={{ title: "", headerTitle: "", unstable_headerLeftItems: () => [] }}
options={
Platform.OS === "android"
? { headerShown: false }
: { title: "", headerTitle: "", unstable_headerLeftItems: () => [] }
}
/>
<WorkspaceSidebarToolbar
afterSidebarButton={
Expand All @@ -127,15 +132,17 @@ export function HomeRouteScreen() {
onStartNewTask={() => navigation.navigate("NewTaskSheet", { screen: "NewTask" })}
>
<>
{/* Restore the compact title after the split branch blanks the detail
header. The brand slot doubles as the connection status surface:
while an environment reconnects, the lockup fades to a status label
in place (no layout shift in the list below). */}
{/* Restore the header after leaving split view; screen options are
shallow-merged. The brand slot also doubles as the connection
status surface while an environment reconnects. */}
<NativeStackScreenOptions
options={getConnectionAwareBrandHeaderOptions({
onOpenEnvironments: () =>
navigation.navigate("SettingsSheet", { screen: "SettingsEnvironments" }),
})}
options={{
...getConnectionAwareBrandHeaderOptions({
onOpenEnvironments: () =>
navigation.navigate("SettingsSheet", { screen: "SettingsEnvironments" }),
}),
headerShown: true,
}}
/>
<HomeHeader
environments={environments}
Expand Down
33 changes: 21 additions & 12 deletions apps/mobile/src/features/layout/AdaptiveWorkspaceLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
parseActiveThreadPath,
useHardwareKeyboardCommand,
} from "../keyboard/hardwareKeyboardCommands";
import { AndroidHomeFabLayout } from "../home/AndroidHomeFab";
import { HomeListOptionsProvider } from "../home/home-list-options";
import { ThreadNavigationSidebar } from "../threads/ThreadNavigationSidebar";
import { WORKSPACE_PANE_TIMING } from "./workspace-pane-animation";
Expand Down Expand Up @@ -432,6 +433,10 @@ function AdaptiveWorkspaceLayoutContent(
navigation.navigate("SettingsSheet", { screen: "Settings" });
}, [navigation]);

const handleStartNewTask = useCallback(() => {
navigation.navigate("NewTaskSheet", { screen: "NewTask" });
}, [navigation]);

// Minted here (root stack navigation) so the sidebar pane stays free of
// navigation hooks — on iOS it renders inside an independent nav tree.
const handleOpenEnvironmentSettings = useCallback(() => {
Expand Down Expand Up @@ -522,18 +527,22 @@ function AdaptiveWorkspaceLayoutContent(
pointerEvents={panes.primarySidebarVisible ? "auto" : "none"}
style={sidebarAnimatedStyle}
>
<ThreadNavigationSidebar
width={layout.listPaneWidth}
visible={panes.primarySidebarVisible}
onRequestVisibility={revealPrimarySidebar}
selectedThreadKey={selectedThreadKey}
onOpenSettings={handleOpenSettings}
onOpenEnvironmentSettings={handleOpenEnvironmentSettings}
onNewThreadInProject={handleNewThreadInProject}
onSelectThread={handleSelectThread}
onSearchQueryChange={setPrimarySidebarSearchQuery}
searchQuery={primarySidebarSearchQuery}
/>
<View className="flex-1" style={{ width: layout.listPaneWidth }}>
<AndroidHomeFabLayout onStartNewTask={handleStartNewTask}>
<ThreadNavigationSidebar
width={layout.listPaneWidth}
visible={panes.primarySidebarVisible}
onRequestVisibility={revealPrimarySidebar}
selectedThreadKey={selectedThreadKey}
onOpenSettings={handleOpenSettings}
onOpenEnvironmentSettings={handleOpenEnvironmentSettings}
onNewThreadInProject={handleNewThreadInProject}
onSelectThread={handleSelectThread}
onSearchQueryChange={setPrimarySidebarSearchQuery}
searchQuery={primarySidebarSearchQuery}
/>
</AndroidHomeFabLayout>
</View>
</Animated.View>
) : null}
<View className="flex-1 overflow-hidden bg-screen" collapsable={false}>
Expand Down
16 changes: 11 additions & 5 deletions apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { NativeStackScreenOptions } from "../../native/StackHeader";
import { StackActions, useNavigation, usePreventRemove } from "@react-navigation/native";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Alert, InteractionManager, Platform, View, useColorScheme } from "react-native";
import { KeyboardAvoidingView, useKeyboardState } from "react-native-keyboard-controller";
import {
KeyboardAvoidingView,
KeyboardStickyView,
useKeyboardState,
} from "react-native-keyboard-controller";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useThemeColor } from "../../lib/useThemeColor";
import { useFontFamily } from "../../lib/useFontFamily";
Expand Down Expand Up @@ -1053,9 +1057,11 @@ export function NewTaskDraftScreen(props: {
<NativeStackScreenOptions options={{ headerShown: false }} />
<AndroidScreenHeader title="New Thread" onBack={() => navigation.goBack()} />

<KeyboardAvoidingView automaticOffset behavior="padding" className="flex-1">
<View className="flex-1" />

<View className="flex-1" />
<KeyboardStickyView
style={{ position: "absolute", bottom: 0, left: 0, right: 0 }}
offset={{ closed: 0, opened: 0 }}
>
<View
className="px-4 pt-2"
style={{
Expand Down Expand Up @@ -1119,7 +1125,7 @@ export function NewTaskDraftScreen(props: {
</ComposerToolbarRow>
) : null}
</View>
</KeyboardAvoidingView>
</KeyboardStickyView>
</View>
);
}
Expand Down
74 changes: 16 additions & 58 deletions apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import { useAtomValue } from "@effect/atom-react";
import type { EnvironmentId } from "@t3tools/contracts";
import { sortPinnedThreadsByOrderKey } from "@t3tools/client-runtime/state/thread-sort";
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import type { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent } from "react-native";
import type { LayoutChangeEvent } from "react-native";
import { Platform, Pressable, StyleSheet, TextInput, View, useColorScheme } from "react-native";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import type { SwipeableMethods } from "react-native-gesture-handler/ReanimatedSwipeable";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import type { SearchBarCommands } from "react-native-screens";
import Svg, { Defs, LinearGradient, Rect, Stop } from "react-native-svg";

import { AppText as Text } from "../../components/AppText";
import { CompactBrandTitle } from "../../components/CompactBrandTitle";
import { ControlPillMenu } from "../../components/ControlPill";
import { SymbolView } from "../../components/AppSymbol";
import { NATIVE_LIQUID_GLASS_SUPPORTED } from "../../native/native-glass";
Expand Down Expand Up @@ -129,11 +129,6 @@ function SidebarHeaderButtonGroup(props: {
}

const SIDEBAR_STICKY_HEADER_HEIGHT = 106;
const SIDEBAR_STICKY_HEADER_FADE_HEIGHT = 44;
const SIDEBAR_HEADER_WASH_OPACITY = {
dark: [0.22, 0.14, 0.04],
light: [0.46, 0.3, 0.08],
} as const;

interface ThreadNavigationSidebarProps {
readonly width: number;
Expand Down Expand Up @@ -195,11 +190,9 @@ function ThreadNavigationSidebarPane(
const threads = useThreadShells();
const { environments: workspaceEnvironments, state: catalogState } = useWorkspaceState();
const { savedConnectionsById } = useSavedRemoteConnections();
const [headerIsOverContent, setHeaderIsOverContent] = useState(false);
const searchInputRef = useRef<TextInput>(null);
const searchBarRef = useRef<SearchBarCommands>(null);
const openSwipeableRef = useRef<SwipeableMethods | null>(null);
const headerIsOverContentRef = useRef(false);
const sidebarScrollGesture = useMemo(() => Gesture.Native(), []);
const {
archiveThread,
Expand Down Expand Up @@ -750,8 +743,6 @@ function ThreadNavigationSidebarPane(
const borderColor = useThemeColor("--color-border");
const mutedColor = useThemeColor("--color-foreground-muted");
const placeholderColor = useThemeColor("--color-placeholder");
const headerFadeColor = String(backgroundColor);
const headerWashOpacity = SIDEBAR_HEADER_WASH_OPACITY[colorScheme];
const [measuredHeaderHeight, setMeasuredHeaderHeight] = useState<number | null>(null);
// The sticky header (title row, search field, optional connection status)
// is measured so the list inset always matches its real height — no
Expand Down Expand Up @@ -780,19 +771,10 @@ function ThreadNavigationSidebarPane(
},
[props.onSelectThread],
);
const handleScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
const next = event.nativeEvent.contentOffset.y > 6;
if (headerIsOverContentRef.current === next) {
return;
}
headerIsOverContentRef.current = next;
setHeaderIsOverContent(next);
}, []);
const handleScrollBeginDrag = useCallback(() => {
openSwipeableRef.current?.close();
}, []);
const { swipeEnabled, scrollGateHandlers } = useSwipeableScrollGate({
onScroll: handleScroll,
onScrollBeginDrag: handleScrollBeginDrag,
});
// Project shells load after the first rows draw, so the maps they feed have
Expand Down Expand Up @@ -1282,7 +1264,10 @@ function ThreadNavigationSidebarPane(
contentContainerStyle={[
styles.threadListContent,
{
paddingBottom: 16 + insets.bottom,
paddingBottom:
Platform.OS === "android"
? Math.max(insets.bottom, 16) + 88 - insets.bottom
: 16 + insets.bottom,
Comment thread
cursor[bot] marked this conversation as resolved.
paddingTop: topListInset,
},
]}
Expand All @@ -1301,53 +1286,26 @@ function ThreadNavigationSidebarPane(

<View
className="absolute inset-x-0 top-0 z-[4]"
collapsable={false}
onLayout={handleStickyHeaderLayout}
pointerEvents="box-none"
style={{ paddingTop: insets.top }}
pointerEvents="auto"
style={{
paddingTop: insets.top,
backgroundColor,
}}
>
<View
className="absolute inset-x-0 top-0"
pointerEvents="none"
accessibilityElementsHidden
importantForAccessibility="no-hide-descendants"
style={{ height: stickyHeaderHeight + SIDEBAR_STICKY_HEADER_FADE_HEIGHT }}
>
<Svg width="100%" height="100%">
<Defs>
<LinearGradient id="sidebar-header-wash" x1="0%" x2="0%" y1="0%" y2="100%">
<Stop
offset="0%"
stopColor={headerFadeColor}
stopOpacity={headerIsOverContent ? headerWashOpacity[0] : 0}
/>
<Stop
offset="58%"
stopColor={headerFadeColor}
stopOpacity={headerIsOverContent ? headerWashOpacity[1] : 0}
/>
<Stop
offset="88%"
stopColor={headerFadeColor}
stopOpacity={headerIsOverContent ? headerWashOpacity[2] : 0}
/>
<Stop offset="100%" stopColor={headerFadeColor} stopOpacity={0} />
</LinearGradient>
</Defs>
<Rect width="100%" height="100%" fill="url(#sidebar-header-wash)" />
</Svg>
</View>
<View className="h-[50px] flex-row items-end gap-0.5 pr-2 pl-5">
{/* Title slot doubles as the connection status surface: while an
environment reconnects, "Threads" fades to a status label in
environment reconnects, the brand fades to a status label in
place (no layout shift in the list below). */}
<WorkspaceConnectionTitle
grow
onPress={props.onOpenEnvironmentSettings}
size="pageTitle"
brand={
<Text className="flex-1 text-[34px] font-t3-bold text-foreground" numberOfLines={1}>
Threads
</Text>
<View className="h-11 flex-1 justify-center">
<CompactBrandTitle allowFontScaling={false} />
</View>
}
/>
<SidebarHeaderButtonGroup colorScheme={colorScheme}>
Expand Down

This file was deleted.

Loading