From 52242d688606896beaa49dc3f0c51f459d68f304 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Sun, 5 Apr 2026 02:11:02 +0800 Subject: [PATCH] fix(android): use mobile footer bar in portrait mode on Android, closes #3742 (#3746) On Android tablets/foldables in portrait, screen width can exceed 640px causing the desktop footer bar to show. Now use portrait orientation detection to force the mobile footer bar layout on Android regardless of screen width. Co-authored-by: Claude Opus 4.6 (1M context) --- .../components/footerbar/DesktopFooterBar.tsx | 7 +++++- .../reader/components/footerbar/FooterBar.tsx | 22 +++++++++++++------ .../components/footerbar/MobileFooterBar.tsx | 9 +++++--- .../components/footerbar/NavigationBar.tsx | 7 +++++- .../app/reader/components/footerbar/types.ts | 1 + apps/readest-app/src/styles/globals.css | 7 ++++++ 6 files changed, 41 insertions(+), 12 deletions(-) diff --git a/apps/readest-app/src/app/reader/components/footerbar/DesktopFooterBar.tsx b/apps/readest-app/src/app/reader/components/footerbar/DesktopFooterBar.tsx index f0397d59..aa713287 100644 --- a/apps/readest-app/src/app/reader/components/footerbar/DesktopFooterBar.tsx +++ b/apps/readest-app/src/app/reader/components/footerbar/DesktopFooterBar.tsx @@ -17,6 +17,7 @@ const DesktopFooterBar: React.FC = ({ progressValid, progressFraction, navigationHandlers, + isMobileLayout, onSpeakText, }) => { const _ = useTranslation(); @@ -67,7 +68,11 @@ const DesktopFooterBar: React.FC = ({ return (
= ({ const footerBarRef = useRef(null); useSpatialNavigation(footerBarRef, isVisible); + const isPortrait = window.innerWidth <= window.innerHeight; + const isMobile = appService?.isMobile || window.innerWidth < 640; + const isMobileLayout = isMobile || (!!appService?.isAndroidApp && isPortrait); + const commonProps: FooterBarChildProps = { bookKey, gridInsets, @@ -201,6 +205,7 @@ const FooterBar: React.FC = ({ progressValid, progressFraction, navigationHandlers, + isMobileLayout, onSetActionTab: handleSetActionTab, onSpeakText: handleSpeakText, }; @@ -210,21 +215,24 @@ const FooterBar: React.FC = ({ (bookData?.isFixedLayout && viewSettings?.zoomLevel && viewSettings.zoomLevel > 100); const containerClasses = clsx( - 'footer-bar shadow-xs bottom-0 left-0 z-10 flex w-full flex-col sm:h-[52px]', - 'sm:bg-base-100 border-base-300/50 border-t sm:border-none', + 'footer-bar shadow-xs bottom-0 left-0 z-10 flex w-full flex-col', + isMobileLayout ? '' : 'sm:h-[52px]', + isMobileLayout ? '' : 'sm:bg-base-100 sm:border-none', + 'border-base-300/50 border-t', 'transition-[opacity,transform] duration-300', - window.innerWidth < 640 ? 'fixed' : 'absolute', + isMobileLayout ? 'fixed' : window.innerWidth < 640 ? 'fixed' : 'absolute', appService?.hasRoundedWindow && 'rounded-window-bottom-right', !isSideBarVisible && appService?.hasRoundedWindow && 'rounded-window-bottom-left', isHoveredAnim && 'hover-bar-anim', - needHorizontalScroll ? 'sm:!bottom-3 sm:!h-10 sm:justify-end' : 'sm:justify-center', + !isMobileLayout && + (needHorizontalScroll ? 'sm:!bottom-3 sm:!h-10 sm:justify-end' : 'sm:justify-center'), isVisible ? 'pointer-events-auto translate-y-0 opacity-100' - : 'pointer-events-none translate-y-full opacity-0 sm:translate-y-0', + : isMobileLayout + ? 'pointer-events-none translate-y-full opacity-0' + : 'pointer-events-none translate-y-full opacity-0 sm:translate-y-0', ); - const isMobile = appService?.isMobile || window.innerWidth < 640; - return ( <> {/* Hover trigger area */} diff --git a/apps/readest-app/src/app/reader/components/footerbar/MobileFooterBar.tsx b/apps/readest-app/src/app/reader/components/footerbar/MobileFooterBar.tsx index 30d30b5a..93b37902 100644 --- a/apps/readest-app/src/app/reader/components/footerbar/MobileFooterBar.tsx +++ b/apps/readest-app/src/app/reader/components/footerbar/MobileFooterBar.tsx @@ -1,3 +1,4 @@ +import clsx from 'clsx'; import React from 'react'; import { useResponsiveSize } from '@/hooks/useResponsiveSize'; import { FooterBarChildProps } from './types'; @@ -13,15 +14,16 @@ const MobileFooterBar: React.FC = ({ progressValid, progressFraction, navigationHandlers, + isMobileLayout, onSetActionTab, }) => { - const isMobile = window.innerWidth < 640 || window.innerHeight < 640; + const isMobile = isMobileLayout || window.innerWidth < 640 || window.innerHeight < 640; const sliderHeight = useResponsiveSize(28); const marginIconSize = useResponsiveSize(20); const bottomOffset = isMobile ? `${gridInsets.bottom * 0.33 + 64}px` : '64px'; return ( - <> +
= ({ bookKey={bookKey} actionTab={actionTab} gridInsets={gridInsets} + isMobileLayout={isMobileLayout} onSetActionTab={onSetActionTab!} /> - +
); }; diff --git a/apps/readest-app/src/app/reader/components/footerbar/NavigationBar.tsx b/apps/readest-app/src/app/reader/components/footerbar/NavigationBar.tsx index fde96dcd..f95da81d 100644 --- a/apps/readest-app/src/app/reader/components/footerbar/NavigationBar.tsx +++ b/apps/readest-app/src/app/reader/components/footerbar/NavigationBar.tsx @@ -16,6 +16,7 @@ interface NavigationBarProps { bookKey: string; actionTab: string; gridInsets: Insets; + isMobileLayout: boolean; onSetActionTab: (tab: string) => void; } @@ -23,6 +24,7 @@ export const NavigationBar: React.FC = ({ bookKey, actionTab, gridInsets, + isMobileLayout, onSetActionTab, }) => { const isMobile = window.innerWidth < 640 || window.innerHeight < 640; @@ -36,7 +38,10 @@ export const NavigationBar: React.FC = ({ return (
void; onSpeakText: () => void; } diff --git a/apps/readest-app/src/styles/globals.css b/apps/readest-app/src/styles/globals.css index 0d799a1a..6994453f 100644 --- a/apps/readest-app/src/styles/globals.css +++ b/apps/readest-app/src/styles/globals.css @@ -441,6 +441,13 @@ foliate-fxl { mask: linear-gradient(180deg, black 0%, rgba(0, 0, 0, 0.9) 50%, rgba(0, 0, 0, 0.5) 100%); } +/* Force mobile footer bar layout on Android portrait (overrides sm:hidden breakpoint) */ +@media (min-width: 640px) { + .force-mobile-layout .sm\:hidden { + display: flex; + } +} + .visible-focus-inset-2:focus-visible { outline: none; box-shadow: