Explorar o código

Neo: show tab bar labels by default (#5806)

* Neo: show tab bar labels by default

Turn on `native.tab_labels`, so the native NativeTabs bar and the web mobile
footer show each tab's title under its icon. Forks that want icon-only tabs
set it back to false in customization/settings.js.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Neo: reserve the labelled Android tab bar height in overlays

With tab labels on, the Android NativeTabs bar is 80dp, but the post
comment composer (page-layout/post.js) and the tab-slide page layer still
reserved a fixed 56, so both sat 24dp low and the composer went under the
bar. They now use getNativeTabBarHeight (tab-menu.js), which picks
androidTabBarHeight(labelsHidden) on Android and keeps 56 on iOS. The
messenger and getNativeTabBarOverlayInset use the same helper instead of
their own copies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Andrey Yasko hai 1 semana
pai
achega
92d69e53cf

+ 3 - 8
neo/packages/app/components/elements/messenger/parts/common.js

@@ -2,7 +2,7 @@ import { Text } from 'app/design/typography'
 import { View, Row } from 'app/design/view'
 import { fetcher } from 'app/lib/fetcher';
 import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
-import { androidTabBarHeight, appSetting, isNativeTabsEnabled } from 'app/lib/util'
+import { appSetting, isNativeTabsEnabled } from 'app/lib/util'
 import { Platform } from 'react-native'
 import useFetchForm from 'app/lib/hooks/use-fetch-form'
 import { useCurrentUser } from 'app/context/user';
@@ -20,7 +20,7 @@ import { nativeTabPageHref } from 'app/lib/navigation/tab-history';
 import { useTranslation } from 'react-i18next'
 import ChatPanels from 'app/components/elements/chat/parts/chat-panels'
 import { useIsDesktop } from 'app/context/measure';
-import { areTabBarLabelsHidden, getTabList } from 'app/components/nav/tabs/tab-menu';
+import { getNativeTabBarHeight } from 'app/components/nav/tabs/tab-menu';
 import { useFooterHeight, useSetPageBottomBlur } from 'app/context/jotai/layout';
 import { PageHeaderOptions } from 'app/ui/molecules/header/options';
 import ConvosList from 'app/components/elements/chat/parts/convos-list';
@@ -41,9 +41,6 @@ const isWeb = Platform.OS === 'web';
 
 // Keep in sync with tabBarStyle.height in app/components/nav/tabs/native-tabs.js
 const TAB_BAR_HEIGHT = Platform.OS === 'ios' ? 52 : 56
-// Keep in sync with TAB_BAR_RESERVE in app/components/nav/tab-slide.js (and
-// post.js) — the iOS NativeTabs bar floats over the screen.
-const NATIVE_TAB_BAR_RESERVE = 56;
 
 /**
  * Messenger root: owns conversation selection, panel visibility, URL/history
@@ -93,9 +90,7 @@ export default function Messenger({ defaultConvoId, selectedMenu, convos, fetchC
     // as the bottom inset (as in post.js). JS tabs end the screen above the bar.
     const tabBarOverlayInset = isWeb || !isNativeTabsEnabled()
         ? 0
-        : (Platform.OS === 'android'
-            ? androidTabBarHeight(areTabBarLabelsHidden(getTabList(currentUser) || []))
-            : NATIVE_TAB_BAR_RESERVE) + (getWindowSafeAreaInsets().bottom || 0);
+        : getNativeTabBarHeight(currentUser) + (getWindowSafeAreaInsets().bottom || 0);
     // KeyboardStickyView lifts by (keyboard − opened), so opened is the
     // composer's resting distance from the window bottom. JS tabs: the bar
     // sits below the screen, plus Android's root SafeAreaView insets.bottom

+ 6 - 3
neo/packages/app/components/nav/tab-slide.js

@@ -1,17 +1,18 @@
 'use client';
 
 import { useLayoutEffect, useRef, useState } from 'react';
-import { Animated, Easing, Platform, StyleSheet, View, useWindowDimensions } from 'react-native';
+import { Animated, Easing, StyleSheet, View, useWindowDimensions } from 'react-native';
 import { useIsFocused, getWindowSafeAreaInsets } from 'app/lib/hooks/router';
 import { isNativeTabsEnabled } from 'app/lib/util';
 import { nativeDriver } from 'app/lib/platform/animation';
 import { TabChromeProvider } from 'app/context/tab-chrome';
 import { TabRouteOverrideContext } from 'app/context/tab-route-override';
 import { usePathname } from 'expo-router';
+import { useCurrentUser } from 'app/context/user';
+import { getNativeTabBarHeight } from 'app/components/nav/tabs/tab-menu';
 
 const SLIDE_MS = 320;
 const SLIDE_EASING = Easing.bezier(0.32, 0.72, 0, 1);
-const TAB_BAR_RESERVE = Platform.OS === 'ios' ? 56 : 56;
 
 const slots = new Map();
 const translateByKey = new Map();
@@ -136,7 +137,9 @@ export function TabSlideHost({ sessionKey, backgroundColor }) {
     const { width } = useWindowDimensions();
     const realPathname = usePathname();
     const sessionRef = useRef(sessionKey);
-    const bottomReserve = TAB_BAR_RESERVE + (getWindowSafeAreaInsets().bottom || 0);
+    const { currentUser } = useCurrentUser();
+    // Leave the tab bar's strip free (taller on Android with labels).
+    const bottomReserve = getNativeTabBarHeight(currentUser) + (getWindowSafeAreaInsets().bottom || 0);
 
     useLayoutEffect(() => {
         if (sessionRef.current === sessionKey) return;

+ 13 - 6
neo/packages/app/components/nav/tabs/tab-menu.js

@@ -52,10 +52,20 @@ export function hasNativeTabsMoreMenu(currentUser) {
     return showsTabs && splitTabBarItems(getTabList(currentUser) || []).overflow.length > 0;
 }
 
-// Keep in sync with TAB_BAR_RESERVE in app/components/nav/tab-slide.js (and
-// post.js, messenger/parts/common.js) — the iOS NativeTabs bar floats over the screen.
+// The iOS NativeTabs bar floats over the screen; this much of it is reserved.
 const NATIVE_TAB_BAR_RESERVE = 56;
 
+/**
+ * Height of the NativeTabs bar a screen runs under, without the system inset.
+ * Android's Material bar is 56dp icons-only and 80dp with labels
+ * (`androidTabBarHeight`); iOS reserves a fixed 56 for its floating bar.
+ */
+export function getNativeTabBarHeight(currentUser) {
+    return Platform.OS === 'android'
+        ? androidTabBarHeight(areTabBarLabelsHidden(getTabList(currentUser) || []))
+        : NATIVE_TAB_BAR_RESERVE;
+}
+
 /**
  * Bottom space a NativeTabs screen leaves so its last content scrolls clear of
  * the tab bar, which is drawn over the page (iOS floats it, Android paints it
@@ -67,10 +77,7 @@ export function getNativeTabBarOverlayInset(currentUser) {
     if (Platform.OS === 'web' || !isNativeTabsEnabled()) return 0;
     const showsTabs = !!((currentUser && currentUser.confirmed) || appSetting('native', 'show_tabs_non_logged'));
     if (!showsTabs) return 0;
-    const bar = Platform.OS === 'android'
-        ? androidTabBarHeight(areTabBarLabelsHidden(getTabList(currentUser) || []))
-        : NATIVE_TAB_BAR_RESERVE;
-    return bar + (getWindowSafeAreaInsets().bottom || 0);
+    return getNativeTabBarHeight(currentUser) + (getWindowSafeAreaInsets().bottom || 0);
 }
 
 /** Map a menu / transpile_urls index onto the Expo route (`tab0`…`tab4`). */

+ 6 - 5
neo/packages/app/components/page-layout/post.js

@@ -22,9 +22,8 @@ import { PageHeaderOptions } from 'app/ui/molecules/header/options';
 import { useSafeAreaInsets, getWindowSafeAreaInsets } from 'app/lib/hooks/router'
 import { useStickyHeaderOffset, stickySidebarStyle } from 'app/lib/hooks/use-sticky-header-offset'
 import { Loading } from 'app/customization/loading'
-// Keep in sync with TAB_BAR_RESERVE in app/components/nav/tab-slide.js —
-// with expo native tabs the screen extends behind the system tab bar.
-const NATIVE_TAB_BAR_RESERVE = 56;
+import { useCurrentUser } from 'app/context/user'
+import { getNativeTabBarHeight } from 'app/components/nav/tabs/tab-menu'
 // Keep in sync with TAB_BAR_HEIGHT in app/components/nav/tabs/native-tabs.js —
 // the JS tab bar is in-flow, so the screen (and the composer's coordinate
 // space) ends this far above the window bottom.
@@ -115,6 +114,7 @@ export default function PageLayout({ data, blocks, isModal = false, url, pageCla
     const isWeb = Platform.OS == 'web';
     const windowHeight = useWindowHeight();
     const insets = useSafeAreaInsets();
+    const { currentUser } = useCurrentUser();
     const isDesktop = useIsDesktop();
     const [replyId, setReplyId] = useState(false);
     const [scrollToEnd, setScrollToEnd] = useState(false);
@@ -127,13 +127,14 @@ export default function PageLayout({ data, blocks, isModal = false, url, pageCla
     const { marginBottom: listBottomInset } = useStickyComposerListInset(formHeight);
     // Resting gap between the composer and the screen bottom (native page only):
     // with native tabs the page paints behind the system tab bar, so the composer
-    // must clear it; with JS tabs the screen already ends above the footer.
+    // must clear it (taller on Android with labels); with JS tabs the screen
+    // already ends above the footer.
     // Window insets, not `insets`: the NativeTabs nested SafeAreaProvider can
     // report bottom = keyboard height, which would bounce the composer.
     const composerBottomGap = isWeb
         ? 0
         : isNativeTabsEnabled()
-            ? NATIVE_TAB_BAR_RESERVE + (getWindowSafeAreaInsets().bottom || 0)
+            ? getNativeTabBarHeight(currentUser) + (getWindowSafeAreaInsets().bottom || 0)
             : COMPOSER_FOOTER_GAP;
     // KeyboardStickyView translates by (keyboardHeight − opened), so landing the
     // composer flush on the keyboard requires opened == its resting distance

+ 1 - 1
neo/packages/app/settings/configs.js

@@ -26,7 +26,7 @@ export const settingsConfigs = {
         expo_native_tabs: true,
         // Text under tab bar icons (web mobile footer + native JS / NativeTabs).
         // false = icon-only. Per-tab empty `title` still hides that label.
-        tab_labels: false,
+        tab_labels: true,
     },
     urls: {
         embeds: '/oembed.php?html=1&a=get_link&l=',