From 4094fd86ffed4fab148046ed1aa9fe332fefd9b1 Mon Sep 17 00:00:00 2001 From: Huang Xin Date: Mon, 5 May 2025 23:42:57 +0800 Subject: [PATCH] fix: padding drag handler on Android, closes #1036 (#1039) * refactor: load default icon when avatar image fails to load * fix: padding drag handler on Android, closes #1036 --- .../app/library/components/SettingsMenu.tsx | 11 +--- apps/readest-app/src/app/user/page.tsx | 16 +++--- apps/readest-app/src/components/Dialog.tsx | 43 +++++++++------- .../readest-app/src/components/UserAvatar.tsx | 50 +++++++++++++++++++ apps/readest-app/src/hooks/useDrag.ts | 1 - apps/readest-app/src/hooks/useTheme.ts | 14 +++++- 6 files changed, 98 insertions(+), 37 deletions(-) create mode 100644 apps/readest-app/src/components/UserAvatar.tsx diff --git a/apps/readest-app/src/app/library/components/SettingsMenu.tsx b/apps/readest-app/src/app/library/components/SettingsMenu.tsx index 6c2179ea..b9c7828f 100644 --- a/apps/readest-app/src/app/library/components/SettingsMenu.tsx +++ b/apps/readest-app/src/app/library/components/SettingsMenu.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react'; -import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { PiUserCircle } from 'react-icons/pi'; import { PiUserCircleCheck } from 'react-icons/pi'; @@ -17,6 +16,7 @@ import { getStoragePlanData } from '@/utils/access'; import { navigateToLogin, navigateToProfile } from '@/utils/nav'; import { tauriHandleSetAlwaysOnTop, tauriHandleToggleFullScreen } from '@/utils/window'; import { QuotaType } from '@/types/user'; +import UserAvatar from '@/components/UserAvatar'; import MenuItem from '@/components/MenuItem'; import Quota from '@/components/Quota'; @@ -146,14 +146,7 @@ const SettingsMenu: React.FC = ({ setIsDropdownOpen }) => { labelClass='!max-w-40' Icon={ avatarUrl ? ( - {_('User + ) : ( PiUserCircleCheck ) diff --git a/apps/readest-app/src/app/user/page.tsx b/apps/readest-app/src/app/user/page.tsx index b2a1d38c..08a4ca9c 100644 --- a/apps/readest-app/src/app/user/page.tsx +++ b/apps/readest-app/src/app/user/page.tsx @@ -2,7 +2,6 @@ import clsx from 'clsx'; import React, { useState, useEffect, useRef } from 'react'; -import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { IoArrowBack } from 'react-icons/io5'; import { PiUserCircle } from 'react-icons/pi'; @@ -20,6 +19,7 @@ import { eventDispatcher } from '@/utils/event'; import { Toast } from '@/components/Toast'; import WindowButtons from '@/components/WindowButtons'; import Quota from '@/components/Quota'; +import UserAvatar from '@/components/UserAvatar'; const ProfilePage = () => { const _ = useTranslation(); @@ -191,14 +191,12 @@ const ProfilePage = () => {
{avatarUrl ? ( - {_('User ) : ( diff --git a/apps/readest-app/src/components/Dialog.tsx b/apps/readest-app/src/components/Dialog.tsx index b5379059..81709cb7 100644 --- a/apps/readest-app/src/components/Dialog.tsx +++ b/apps/readest-app/src/components/Dialog.tsx @@ -3,6 +3,7 @@ import React, { ReactNode, useEffect, useState } from 'react'; import { MdArrowBackIosNew, MdArrowForwardIos } from 'react-icons/md'; import { useEnv } from '@/context/EnvContext'; import { useDrag } from '@/hooks/useDrag'; +import { useThemeStore } from '@/store/themeStore'; import { useDeviceControlStore } from '@/store/deviceStore'; import { useResponsiveSize } from '@/hooks/useResponsiveSize'; import { impactFeedback } from '@tauri-apps/plugin-haptics'; @@ -40,6 +41,7 @@ const Dialog: React.FC = ({ onClose, }) => { const { appService } = useEnv(); + const { systemUIVisible, statusBarHeight } = useThemeStore(); const { acquireBackKeyInterception, releaseBackKeyInterception } = useDeviceControlStore(); const [isFullHeightInMobile, setIsFullHeightInMobile] = React.useState(!snapHeight); const [isRtl] = useState(() => getDirFromUILanguage() === 'rtl'); @@ -105,41 +107,39 @@ const Dialog: React.FC = ({ data.velocity > VELOCITY_THRESHOLD || (data.velocity >= 0 && data.clientY >= window.innerHeight * snapLower) ) { + // dialog is dismissed const transitionDuration = 0.15 / Math.max(data.velocity, 0.5); modal.style.height = '100%'; modal.style.transition = `transform ${transitionDuration}s ease-out`; modal.style.transform = 'translateY(100%)'; overlay.style.transition = `opacity ${transitionDuration}s ease-out`; overlay.style.opacity = '0'; + onClose(); setTimeout(() => { - onClose(); modal.style.transform = 'translateY(0%)'; }, 300); - if (appService?.hasHaptics) { - impactFeedback('medium'); - } } else if ( snapHeight && data.clientY > window.innerHeight * snapUpper && data.clientY < window.innerHeight * snapLower ) { + // dialog is snapped + overlay.style.transition = `opacity 0.3s ease-out`; + overlay.style.opacity = `${1 - snapHeight}`; + modal.style.height = `${snapHeight * 100}%`; + modal.style.bottom = '0'; modal.style.transition = `transform 0.3s ease-out`; - modal.style.transform = `translateY(${(1 - snapHeight) * window.innerHeight}px)`; - setTimeout(() => { - modal.style.height = `${snapHeight * 100}%`; - }, 100); - if (appService?.hasHaptics) { - impactFeedback('medium'); - } + modal.style.transform = ''; } else { + // dialog is opened without snap setIsFullHeightInMobile(true); modal.style.height = '100%'; modal.style.transition = `transform 0.3s ease-out`; modal.style.transform = `translateY(0%)`; overlay.style.opacity = '0'; - if (appService?.hasHaptics) { - impactFeedback('medium'); - } + } + if (appService?.hasHaptics) { + impactFeedback('medium'); } }; @@ -161,7 +161,7 @@ const Dialog: React.FC = ({ />
= ({ snapHeight ? { height: `${snapHeight * 100}%`, - transform: `translateY(${(1 - snapHeight) * window.innerHeight}px)`, + bottom: 0, } : {} } > {window.innerWidth < 640 && (
diff --git a/apps/readest-app/src/components/UserAvatar.tsx b/apps/readest-app/src/components/UserAvatar.tsx new file mode 100644 index 00000000..9ec19e27 --- /dev/null +++ b/apps/readest-app/src/components/UserAvatar.tsx @@ -0,0 +1,50 @@ +import clsx from 'clsx'; +import Image from 'next/image'; +import { IconType } from 'react-icons'; + +interface UserAvatarProps { + url: string; + size: number; + DefaultIcon: IconType; + className?: string; + borderClassName?: string; +} + +const UserAvatar: React.FC = ({ + url, + size, + className, + borderClassName, + DefaultIcon, +}) => { + return ( +
+ {url ? ( +
+ User Avatar { + (e.target as HTMLImageElement).style.display = 'none'; + (e.target as HTMLImageElement).nextElementSibling?.classList.remove('invisible'); + }} + /> +
+ +
+
+ ) : ( + + )} +
+ ); +}; + +export default UserAvatar; diff --git a/apps/readest-app/src/hooks/useDrag.ts b/apps/readest-app/src/hooks/useDrag.ts index 2c31f125..da68ab32 100644 --- a/apps/readest-app/src/hooks/useDrag.ts +++ b/apps/readest-app/src/hooks/useDrag.ts @@ -20,7 +20,6 @@ export const useDrag = ( const handleDragStart = useCallback( (e: React.MouseEvent | React.TouchEvent) => { - e.preventDefault(); isDragging.current = true; if ('touches' in e) { diff --git a/apps/readest-app/src/hooks/useTheme.ts b/apps/readest-app/src/hooks/useTheme.ts index ccdfacd4..bb17e4eb 100644 --- a/apps/readest-app/src/hooks/useTheme.ts +++ b/apps/readest-app/src/hooks/useTheme.ts @@ -16,11 +16,23 @@ export const useTheme = ({ }: UseThemeProps = {}) => { const { appService } = useEnv(); const { settings } = useSettingsStore(); - const { themeColor, isDarkMode, updateAppTheme, setStatusBarHeight } = useThemeStore(); + const { + themeColor, + isDarkMode, + showSystemUI, + dismissSystemUI, + updateAppTheme, + setStatusBarHeight, + } = useThemeStore(); useEffect(() => { updateAppTheme(appThemeColor); if (appService?.isMobile) { + if (systemUIVisible) { + showSystemUI(); + } else { + dismissSystemUI(); + } setSystemUIVisibility({ visible: systemUIVisible, darkMode: isDarkMode }); } if (appService?.isAndroidApp) {