Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 5.2k
fix(mobile): restore Android tablet thread controls, clean up header#5385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
b93a2f98cc173f8679acd3a7e119eadfa10c2fb580df8270cFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
macroscopeapp[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,3 @@ | ||
| import { isLiquidGlassSupported, LiquidGlassView } from "@callstack/liquid-glass"; | ||
| import type { | ||
| EnvironmentProject, | ||
| EnvironmentThreadShell, | ||
| @@ -14,16 +13,16 @@ import { AsyncResult } from "effect/unstable/reactivity"; | ||
| import type { EnvironmentId } from "@t3tools/contracts"; | ||
| import { sortPinnedThreadsByOrderKey } from "@t3tools/client-runtime/state/thread-sort"; | ||
| import type { ChangeRequestSettleSource } from "@t3tools/client-runtime/state/thread-settled"; | ||
| import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"; | ||
| import type { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent } from "react-native"; | ||
| import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||
| import type { LayoutChangeEvent } from "react-native"; | ||
| import { Platform, Pressable, StyleSheet, TextInput, View } 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"; | ||
| @@ -39,7 +38,6 @@ import { usePendingNewTasks } from "../../state/use-pending-new-tasks"; | ||
| import { useWorkspaceState } from "../../state/workspace"; | ||
| import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry"; | ||
| import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands"; | ||
| import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; | ||
| import { | ||
| hasCustomHomeListOptions, | ||
| PROJECT_SORT_OPTIONS, | ||
| @@ -96,48 +94,7 @@ type SidebarListItem = | ||
| | ThreadListV2ListItem | ||
| | { readonly type: "v2-show-more"; readonly key: string; readonly hiddenCount: number }; | ||
| /** | ||
| * Shared capsule behind the sidebar header buttons — a native liquid-glass | ||
| * surface on iOS 26+, a tinted pill everywhere else. | ||
| */ | ||
| function SidebarHeaderButtonGroup(props: { | ||
| readonly children: ReactNode; | ||
| readonly colorScheme: "light" | "dark"; | ||
| }) { | ||
| const fallbackBackground = useThemeColor("--color-glass-surface"); | ||
| const fallbackBorder = useThemeColor("--color-header-border"); | ||
| if (isLiquidGlassSupported) { | ||
| return ( | ||
| <LiquidGlassView | ||
| colorScheme={props.colorScheme} | ||
| effect="regular" | ||
| interactive | ||
| style={styles.headerButtonGroup} | ||
| > | ||
| {props.children} | ||
| </LiquidGlassView> | ||
| ); | ||
| } | ||
| return ( | ||
| <View | ||
| style={[ | ||
| styles.headerButtonGroup, | ||
| { backgroundColor: fallbackBackground, borderColor: fallbackBorder }, | ||
| { borderWidth: StyleSheet.hairlineWidth }, | ||
| ]} | ||
| > | ||
| {props.children} | ||
| </View> | ||
| ); | ||
| } | ||
| 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; | ||
| @@ -194,16 +151,13 @@ function ThreadNavigationSidebarPane( | ||
| props: ThreadNavigationSidebarProps & { readonly nativeChrome: boolean }, | ||
| ) { | ||
| const insets = useSafeAreaInsets(); | ||
| const { themeAppearance: colorScheme } = useAppearancePreferences(); | ||
| const projects = useProjects(); | ||
| 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, | ||
| @@ -776,8 +730,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 | ||
| @@ -806,19 +758,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 | ||
| @@ -1314,7 +1257,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, | ||
cursor[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| paddingTop: topListInset, | ||
| }, | ||
| ]} | ||
| @@ -1333,65 +1279,34 @@ 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}> | ||
| <View className="flex-row items-center gap-2.5"> | ||
| <ControlPillMenu actions={listMenuActions} onPressAction={handleListMenuAction}> | ||
| <SidebarFilterButton | ||
| grouped | ||
| accessibilityLabel="Filter and sort threads" | ||
| icon={filterIcon} | ||
| /> | ||
| <SidebarFilterButton accessibilityLabel="Filter and sort threads" icon={filterIcon} /> | ||
| </ControlPillMenu> | ||
| <SidebarHeaderActions grouped onOpenSettings={props.onOpenSettings} /> | ||
| </SidebarHeaderButtonGroup> | ||
| <SidebarHeaderActions onOpenSettings={props.onOpenSettings} /> | ||
| </View> | ||
| </View> | ||
| <View className="mx-4 mt-[9px] h-[38px] flex-row items-center gap-1.5 rounded-xl bg-sidebar-search pr-2.5 pl-[11px]"> | ||
| @@ -1416,12 +1331,6 @@ function ThreadNavigationSidebarPane( | ||
| } | ||
| const styles = StyleSheet.create({ | ||
| headerButtonGroup: { | ||
| alignItems: "center", | ||
| borderRadius: 22, | ||
| flexDirection: "row", | ||
| overflow: "hidden", | ||
| }, | ||
| threadList: { | ||
| flex: 1, | ||
| }, | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.