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) <noreply@anthropic.com>
This commit is contained in:
Huang Xin
2026-04-05 02:11:02 +08:00
committed by GitHub
parent f361698e05
commit 52242d6886
6 changed files with 41 additions and 12 deletions
@@ -17,6 +17,7 @@ const DesktopFooterBar: React.FC<FooterBarChildProps> = ({
progressValid,
progressFraction,
navigationHandlers,
isMobileLayout,
onSpeakText,
}) => {
const _ = useTranslation();
@@ -67,7 +68,11 @@ const DesktopFooterBar: React.FC<FooterBarChildProps> = ({
return (
<div
className='hidden h-8 w-full items-center gap-x-4 overflow-x-auto px-4 sm:flex'
className={
isMobileLayout
? 'hidden'
: 'hidden h-8 w-full items-center gap-x-4 overflow-x-auto px-4 sm:flex'
}
style={{
bottom: isMobile ? `${gridInsets.bottom * 0.33}px` : '0px',
scrollbarWidth: 'none',
@@ -194,6 +194,10 @@ const FooterBar: React.FC<FooterBarProps> = ({
const footerBarRef = useRef<HTMLDivElement>(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<FooterBarProps> = ({
progressValid,
progressFraction,
navigationHandlers,
isMobileLayout,
onSetActionTab: handleSetActionTab,
onSpeakText: handleSpeakText,
};
@@ -210,21 +215,24 @@ const FooterBar: React.FC<FooterBarProps> = ({
(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 */}
@@ -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<FooterBarChildProps> = ({
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 (
<>
<div className={clsx(isMobileLayout && 'force-mobile-layout')}>
<ColorPanel actionTab={actionTab} bottomOffset={bottomOffset} />
<NavigationPanel
bookKey={bookKey}
@@ -42,9 +44,10 @@ const MobileFooterBar: React.FC<FooterBarChildProps> = ({
bookKey={bookKey}
actionTab={actionTab}
gridInsets={gridInsets}
isMobileLayout={isMobileLayout}
onSetActionTab={onSetActionTab!}
/>
</>
</div>
);
};
@@ -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<NavigationBarProps> = ({
bookKey,
actionTab,
gridInsets,
isMobileLayout,
onSetActionTab,
}) => {
const isMobile = window.innerWidth < 640 || window.innerHeight < 640;
@@ -36,7 +38,10 @@ export const NavigationBar: React.FC<NavigationBarProps> = ({
return (
<div
className={clsx('bg-base-200 z-30 mt-auto flex w-full justify-between px-8 py-4 sm:hidden')}
className={clsx(
'bg-base-200 z-30 mt-auto flex w-full justify-between px-8 py-4',
!isMobileLayout && 'sm:hidden',
)}
style={{
paddingBottom: appService?.isAndroidApp
? `calc(env(safe-area-inset-bottom) + 16px)`
@@ -27,6 +27,7 @@ export interface FooterBarChildProps {
progressValid: boolean;
gridInsets: Insets;
actionTab: string;
isMobileLayout: boolean;
onSetActionTab: (tab: string) => void;
onSpeakText: () => void;
}
+7
View File
@@ -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: