PWA app theme color in the header and safe area avoiding home indicator (#220)

This commit is contained in:
Huang Xin
2025-01-23 00:22:09 +01:00
committed by GitHub
parent 4d5297fdb5
commit c678d8fa44
13 changed files with 87 additions and 17 deletions
@@ -8,7 +8,7 @@ import { useReaderStore } from '@/store/readerStore';
import { useSidebarStore } from '@/store/sidebarStore';
import { useTranslation } from '@/hooks/useTranslation';
import { eventDispatcher } from '@/utils/event';
import { isTauriAppPlatform } from '@/services/environment';
import { isPWA, isTauriAppPlatform } from '@/services/environment';
import Button from '@/components/Button';
interface FooterBarProps {
@@ -62,6 +62,7 @@ const FooterBar: React.FC<FooterBarProps> = ({ bookKey, pageinfo, isHoveredAnim
className={clsx(
'footer-bar absolute bottom-0 z-10 flex h-12 w-full items-center gap-x-4 px-4',
'shadow-xs bg-base-100 transition-opacity duration-300',
isPWA() ? 'pb-[env(safe-area-inset-bottom)]' : '',
isTauriAppPlatform() && 'rounded-window-bottom-right',
!isSideBarVisible && isTauriAppPlatform() && 'rounded-window-bottom-left',
isHoveredAnim && 'hover-bar-anim',
@@ -5,6 +5,7 @@ import * as React from 'react';
import { useEffect, Suspense, useRef } from 'react';
import { useEnv } from '@/context/EnvContext';
import { useTheme } from '@/hooks/useTheme';
import { useLibraryStore } from '@/store/libraryStore';
import { useSettingsStore } from '@/store/settingsStore';
import { isTauriAppPlatform } from '@/services/environment';
@@ -18,7 +19,10 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => {
const { library, setLibrary } = useLibraryStore();
const isInitiating = useRef(false);
const { updateAppTheme } = useTheme();
useEffect(() => {
updateAppTheme('base-100');
if (isInitiating.current) return;
isInitiating.current = true;
const initLibrary = async () => {
@@ -7,6 +7,7 @@ import { useReaderStore } from '@/store/readerStore';
import { useSidebarStore } from '@/store/sidebarStore';
import { useNotebookStore } from '@/store/notebookStore';
import { useTranslation } from '@/hooks/useTranslation';
import { useTheme } from '@/hooks/useTheme';
import { useEnv } from '@/context/EnvContext';
import { TextSelection } from '@/utils/sel';
import { BookNote } from '@/types/book';
@@ -23,6 +24,7 @@ const MAX_NOTEBOOK_WIDTH = 0.45;
const Notebook: React.FC = ({}) => {
const _ = useTranslation();
const { updateAppTheme } = useTheme();
const { envConfig } = useEnv();
const { settings } = useSettingsStore();
const { sideBarBookKey } = useSidebarStore();
@@ -41,6 +43,15 @@ const Notebook: React.FC = ({}) => {
}
};
useEffect(() => {
if (isNotebookVisible) {
updateAppTheme('base-200');
} else {
updateAppTheme('base-100');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isNotebookVisible]);
useEffect(() => {
setNotebookWidth(settings.globalReadSettings.notebookWidth);
setNotebookPin(settings.globalReadSettings.isNotebookPinned);
@@ -22,7 +22,7 @@ const BookCard = ({ book }: { book: Book }) => {
alt={_('Book Cover')}
width={56}
height={80}
className='mr-4 aspect-auto max-h-20 w-[15%] max-w-14 rounded-sm object-cover shadow-md'
className='mr-4 aspect-auto max-h-16 w-[15%] max-w-12 rounded-sm object-cover shadow-md'
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
@@ -53,7 +53,7 @@ const SidebarContent: React.FC<{
const handleTabChange = (tab: string) => {
setFade(true);
const timeout = setTimeout(() => {
setFade(false)
setFade(false);
setTargetTab(tab);
setConfig(sideBarBookKey!, config);
clearTimeout(timeout);
@@ -76,7 +76,7 @@ const SidebarContent: React.FC<{
ref={scrollContainerRef}
className={clsx(
'scroll-container overflow-y-auto transition-opacity duration-300 ease-in-out',
{ 'opacity-0': fade, 'opacity-100': !fade }
{ 'opacity-0': fade, 'opacity-100': !fade },
)}
>
{targetTab === 'toc' && bookDoc.toc && (
@@ -8,6 +8,7 @@ import { useSidebarStore } from '@/store/sidebarStore';
import { BookSearchResult } from '@/types/book';
import { eventDispatcher } from '@/utils/event';
import { isTauriAppPlatform } from '@/services/environment';
import { useTheme } from '@/hooks/useTheme';
import SidebarHeader from './Header';
import SidebarContent from './Content';
import BookCard from './BookCard';
@@ -23,6 +24,7 @@ const MAX_SIDEBAR_WIDTH = 0.45;
const SideBar: React.FC<{
onGoToLibrary: () => void;
}> = ({ onGoToLibrary }) => {
const { updateAppTheme } = useTheme();
const { settings } = useSettingsStore();
const { sideBarBookKey } = useSidebarStore();
const { getBookData } = useBookDataStore();
@@ -57,6 +59,15 @@ const SideBar: React.FC<{
}
};
useEffect(() => {
if (isSideBarVisible) {
updateAppTheme('base-200');
} else {
updateAppTheme('base-100');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSideBarVisible]);
useEffect(() => {
eventDispatcher.on('search', onSearchEvent);
eventDispatcher.on('navigate', onNavigateEvent);
@@ -3,6 +3,7 @@ import React from 'react';
import { MdToc, MdEditNote, MdBookmarkBorder } from 'react-icons/md';
import { useTranslation } from '@/hooks/useTranslation';
import { isPWA } from '@/services/environment';
const TabNavigation: React.FC<{
activeTab: string;
@@ -13,7 +14,12 @@ const TabNavigation: React.FC<{
const tabs = ['toc', 'annotations', 'bookmarks'];
return (
<div className='bottom-tab border-base-300/50 relative flex w-full border-t'>
<div
className={clsx(
'bottom-tab border-base-300/50 relative flex w-full border-t',
isPWA() ? 'bottom-[calc(env(safe-area-inset-bottom)_/_2)]' : '',
)}
>
<div
className={clsx(
'bg-base-300 absolute bottom-1.5 left-1 -z-10 h-[calc(100%-12px)] w-[calc(33.3%-8px)] rounded-lg',
@@ -1,12 +1,15 @@
import clsx from 'clsx';
import React, { useState, useRef, useEffect, useCallback } from 'react';
import { useBookDataStore } from '@/store/bookDataStore';
import { useReaderStore } from '@/store/readerStore';
import { useTranslation } from '@/hooks/useTranslation';
import { useResponsiveSize } from '@/hooks/useResponsiveSize';
import { TTSController } from '@/services/tts/TTSController';
import { getPopupPosition, Position } from '@/utils/sel';
import { eventDispatcher } from '@/utils/event';
import { parseSSMLLang } from '@/utils/ssml';
import { throttle } from '@/utils/ui';
import { isPWA } from '@/services/environment';
import Popup from '@/components/Popup';
import TTSPanel from './TTSPanel';
import TTSIcon from './TTSIcon';
@@ -28,6 +31,10 @@ const TTSControl = () => {
const [panelPosition, setPanelPosition] = useState<Position>();
const [trianglePosition, setTrianglePosition] = useState<Position>();
const popupWidth = useResponsiveSize(POPUP_WIDTH);
const popupHeight = useResponsiveSize(POPUP_HEIGHT);
const popupPadding = useResponsiveSize(POPUP_PADDING);
const iconRef = useRef<HTMLDivElement>(null);
const ttsControllerRef = useRef<TTSController | null>(null);
@@ -212,9 +219,9 @@ const TTSControl = () => {
const popupPos = getPopupPosition(
trianglePos,
windowRect,
POPUP_WIDTH,
POPUP_HEIGHT,
POPUP_PADDING,
popupWidth,
popupHeight,
popupPadding,
);
setPanelPosition(popupPos);
@@ -241,14 +248,20 @@ const TTSControl = () => {
/>
)}
{showIndicator && (
<div ref={iconRef} className='absolute bottom-12 right-6 h-12 w-12'>
<div
ref={iconRef}
className={clsx(
'absolute right-6 h-12 w-12',
isPWA() ? 'bottom-[calc(env(safe-area-inset-bottom)+48px)]' : 'bottom-12',
)}
>
<TTSIcon isPlaying={isPlaying} onClick={togglePopup} />
</div>
)}
{showPanel && panelPosition && trianglePosition && (
<Popup
width={POPUP_WIDTH}
height={POPUP_HEIGHT}
width={popupWidth}
height={popupHeight}
position={panelPosition}
trianglePosition={trianglePosition}
className='bg-base-200 absolute flex shadow-lg'