1+ import { useAndroidFloatingTabs } from "../settings/appearance/AppearancePreferencesProvider" ;
12import { SafeAreaInsetsContext , useSafeAreaInsets } from "react-native-safe-area-context" ;
23import { AdaptiveWorkspaceContext , useAdaptiveWorkspaceLayout } from "./adaptive-workspace-context" ;
34import { threadSelectionDeferral } from "./deferred-navigation" ;
@@ -47,9 +48,13 @@ import {
4748 parseActiveThreadPath ,
4849 useHardwareKeyboardCommand ,
4950} from "../keyboard/hardwareKeyboardCommands" ;
51+ import { useAndroidControlSizing } from "../../components/ui/useAndroidControlSizing" ;
52+ import { useEnabledNavigationTabs } from "../workspace/navigation-tabs" ;
53+ import { FLOATING_TABS , floatingTabsMetrics } from "../workspace/floating-workspace-tabs" ;
5054import { AndroidHomeFabLayout } from "../home/AndroidHomeFab" ;
5155import {
5256 AndroidWorkspaceNavigation ,
57+ FloatingWorkspaceTabs ,
5358 ANDROID_WORKSPACE_RAIL_WIDTH ,
5459} from "../workspace/AndroidWorkspaceNavigation" ;
5560import { AndroidWorkspaceSidebar } from "../workspace/AndroidWorkspaceSidebar" ;
@@ -168,6 +173,7 @@ function AdaptiveWorkspaceLayoutContent(
168173 readonly savedThreadListFilters ?: ThreadListFilters | undefined ;
169174 } ,
170175) {
176+ const floatingTabsEnabled = useAndroidFloatingTabs ( ) ;
171177 const projectGroupingMode = props . projectGroupingMode ;
172178 const nativeWorkspace = use ( NativeWorkspaceModeContext ) ;
173179 const windowDimensions = useWindowDimensions ( ) ;
@@ -244,7 +250,11 @@ function AdaptiveWorkspaceLayoutContent(
244250 const showPrimarySidebar =
245251 ! workspacePage &&
246252 ( primarySidebarPinned || ( preferredSidebarVisible && ! composerKeyboardActive ) ) ;
247- const shouldRenderNavigationRail = Platform . OS === "android" && baseLayout . usesSplitView ;
253+ const shouldRenderNavigationRail =
254+ Platform . OS === "android" && baseLayout . usesSplitView && ! floatingTabsEnabled ;
255+ const enabledTabs = useEnabledNavigationTabs ( ) ;
256+ const { scale, fabSize } = useAndroidControlSizing ( ) ;
257+ const floatingTabs = floatingTabsMetrics ( enabledTabs . length + 1 , scale , fabSize ) ;
248258 const navigationRailWidth =
249259 shouldRenderNavigationRail && ( showPrimarySidebar || workspacePage )
250260 ? ANDROID_WORKSPACE_RAIL_WIDTH
@@ -255,16 +265,31 @@ function AdaptiveWorkspaceLayoutContent(
255265 setHinge ( next ) ;
256266 setWindowHinge ( nextWindow ) ;
257267 } ;
258- const layout = useMemo (
259- ( ) =>
260- alignListPaneToHinge ( {
261- layout : baseLayout ,
262- hinge,
263- viewportWidth,
264- leadingWidth : navigationRailWidth ,
265- } ) ,
266- [ baseLayout , hinge , navigationRailWidth , viewportWidth ] ,
267- ) ;
268+ const layout = useMemo ( ( ) => {
269+ const aligned = alignListPaneToHinge ( {
270+ layout : baseLayout ,
271+ hinge,
272+ viewportWidth,
273+ leadingWidth : navigationRailWidth ,
274+ } ) ;
275+ if (
276+ ! floatingTabsEnabled ||
277+ ! aligned . usesSplitView ||
278+ aligned . listPaneGap !== undefined ||
279+ aligned . listPaneWidth === null ||
280+ aligned . listPaneWidth >= floatingTabs . row ||
281+ viewportWidth - floatingTabs . row < 320
282+ )
283+ return aligned ;
284+ return { ...aligned , listPaneWidth : floatingTabs . row } ;
285+ } , [
286+ baseLayout ,
287+ hinge ,
288+ navigationRailWidth ,
289+ viewportWidth ,
290+ floatingTabs . row ,
291+ floatingTabsEnabled ,
292+ ] ) ;
268293 const width = viewportWidth - navigationRailWidth ;
269294 // The sidebar stays mounted under a workspace page, so its list is ready on the way back.
270295 const shouldRenderPrimarySidebar = layout . usesSplitView ;
@@ -733,7 +758,10 @@ function AdaptiveWorkspaceLayoutContent(
733758 } ) ) ;
734759 const sidebarAnimatedStyle = useAnimatedStyle ( ( ) => ( {
735760 // Fading a UIKit glass ancestor can leave remounted header effects missing.
736- opacity : Platform . OS === "ios" ? 1 : Math . min ( 1 , renderedSidebarWidth . value / 80 ) ,
761+ opacity :
762+ Platform . OS === "ios" || floatingTabsEnabled
763+ ? 1
764+ : Math . min ( 1 , renderedSidebarWidth . value / 80 ) ,
737765 width : renderedSidebarWidth . value ,
738766 } ) ) ;
739767
@@ -829,12 +857,12 @@ function AdaptiveWorkspaceLayoutContent(
829857 { shouldRenderPrimarySidebar && layout . listPaneWidth !== null ? (
830858 < Animated . View
831859 className = "self-stretch overflow-hidden"
832- accessibilityElementsHidden = { ! panes . primarySidebarVisible }
860+ accessibilityElementsHidden = { workspacePage || ! panes . primarySidebarVisible }
833861 collapsable = { false }
834862 importantForAccessibility = {
835- panes . primarySidebarVisible ? "auto" : "no-hide-descendants"
863+ ! workspacePage && panes . primarySidebarVisible ? "auto" : "no-hide-descendants"
836864 }
837- pointerEvents = { panes . primarySidebarVisible ? "auto" : "none" }
865+ pointerEvents = { ! workspacePage && panes . primarySidebarVisible ? "auto" : "none" }
838866 // Under a workspace page the sidebar leaves the row but keeps its size, so its
839867 // lists never lay out at zero size and are ready, unchanged, on the way back. The
840868 // content pane, laid out after it, covers it, in the same mount as the page.
@@ -857,7 +885,12 @@ function AdaptiveWorkspaceLayoutContent(
857885 onRequestVisibility = { revealPrimarySidebar }
858886 selectedPullRequestKey = { props . selectedPullRequestKey ?? null }
859887 >
860- < AndroidHomeFabLayout sidebar onStartNewTask = { handleStartNewTask } >
888+ < AndroidHomeFabLayout
889+ sidebar
890+ disabled = { workspacePage }
891+ paneWidth = { layout . listPaneWidth }
892+ onStartNewTask = { handleStartNewTask }
893+ >
861894 < ThreadNavigationSidebar
862895 width = { layout . listPaneWidth }
863896 visible = { panes . primarySidebarVisible }
@@ -876,6 +909,14 @@ function AdaptiveWorkspaceLayoutContent(
876909 </ AndroidHomeFabLayout >
877910 </ AndroidWorkspaceSidebar >
878911 </ RenderErrorBoundary >
912+ { /* Keep the pill under the content throughout the sidebar's clipping animation. */ }
913+ { floatingTabsEnabled && ! workspacePage ? (
914+ < FloatingWorkspaceTabs
915+ paneWidth = { layout . listPaneWidth }
916+ onNewThread = { handleStartNewTask }
917+ onSelect = { revealPrimarySidebar }
918+ />
919+ ) : null }
879920 </ Animated . View >
880921 </ Animated . View >
881922 ) : null }
@@ -913,14 +954,35 @@ function AdaptiveWorkspaceLayoutContent(
913954 { /* Compact Settings keeps the bottom navigation under all of its pages. */ }
914955 < SafeAreaInsetsContext
915956 value = {
916- compactAndroidSettings ? { ...safeAreaInsets , bottom : 0 } : safeAreaInsets
957+ floatingTabsEnabled &&
958+ ( compactAndroidSettings || ( layout . usesSplitView && workspacePage ) )
959+ ? {
960+ ...safeAreaInsets ,
961+ bottom :
962+ safeAreaInsets . bottom +
963+ FLOATING_TABS . bottom +
964+ FLOATING_TABS . edge +
965+ floatingTabs . height ,
966+ }
967+ : compactAndroidSettings
968+ ? { ...safeAreaInsets , bottom : 0 }
969+ : safeAreaInsets
917970 }
918971 >
919972 < View className = "flex-1" >
920973 < SettingsWorkspaceLayout > { props . children } </ SettingsWorkspaceLayout >
921974 </ View >
922975 </ SafeAreaInsetsContext >
923- { compactAndroidSettings ? (
976+ { floatingTabsEnabled &&
977+ ( compactAndroidSettings || ( layout . usesSplitView && workspacePage ) ) ? (
978+ < FloatingWorkspaceTabs
979+ paneWidth = {
980+ layout . usesSplitView ? ( layout . listPaneWidth ?? undefined ) : undefined
981+ }
982+ fillWidth = { compactAndroidSettings }
983+ onNewThread = { handleStartNewTask }
984+ />
985+ ) : compactAndroidSettings ? (
924986 < AndroidWorkspaceNavigation onNewThread = { handleStartNewTask } />
925987 ) : null }
926988 </ WorkspaceSettledContentWidthContext >
0 commit comments