diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/android/src/main/java/NativeBridgePlugin.kt b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/android/src/main/java/NativeBridgePlugin.kt index fb6b047e..8a6a73bc 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/android/src/main/java/NativeBridgePlugin.kt +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/android/src/main/java/NativeBridgePlugin.kt @@ -227,4 +227,22 @@ class NativeBridgePlugin(private val activity: Activity): Plugin(activity) { } invoke.resolve(ret) } + + @Command + fun get_status_bar_height(invoke: Invoke) { + val ret = JSObject() + try { + val resourceId = activity.resources.getIdentifier("status_bar_height", "dimen", "android") + val height = if (resourceId > 0) { + activity.resources.getDimensionPixelSize(resourceId) + } else { + 0 + } + ret.put("height", height) + } catch (e: Exception) { + ret.put("height", -1) + ret.put("error", e.message) + } + invoke.resolve(ret) + } } diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs index 043c05f1..3505d5bf 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/build.rs @@ -5,6 +5,7 @@ const COMMANDS: &[&str] = &[ "use_background_audio", "install_package", "set_system_ui_visibility", + "get_status_bar_height", ]; fn main() { diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/get_status_bar_height.toml b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/get_status_bar_height.toml new file mode 100644 index 00000000..d0aae5bf --- /dev/null +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/commands/get_status_bar_height.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-get-status-bar-height" +description = "Enables the get_status_bar_height command without any pre-configured scope." +commands.allow = ["get_status_bar_height"] + +[[permission]] +identifier = "deny-get-status-bar-height" +description = "Denies the get_status_bar_height command without any pre-configured scope." +commands.deny = ["get_status_bar_height"] diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md index 1a0202e8..ccec6e4b 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/autogenerated/reference.md @@ -10,6 +10,7 @@ Default permissions for the plugin - `allow-use-background-audio` - `allow-install-package` - `allow-set-system-ui-visibility` +- `allow-get-status-bar-height` ## Permission Table @@ -101,6 +102,32 @@ Denies the copy_uri_to_path command without any pre-configured scope. +`native-bridge:allow-get-status-bar-height` + + + + +Enables the get_status_bar_height command without any pre-configured scope. + + + + + + + +`native-bridge:deny-get-status-bar-height` + + + + +Denies the get_status_bar_height command without any pre-configured scope. + + + + + + + `native-bridge:allow-install-package` diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml index 8d5aa057..ba4e6b8b 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/default.toml @@ -7,4 +7,5 @@ permissions = [ "allow-use-background-audio", "allow-install-package", "allow-set-system-ui-visibility", + "allow-get-status-bar-height", ] diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json index 06e5927c..03101ed0 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/permissions/schemas/schema.json @@ -330,6 +330,18 @@ "const": "deny-copy-uri-to-path", "markdownDescription": "Denies the copy_uri_to_path command without any pre-configured scope." }, + { + "description": "Enables the get_status_bar_height command without any pre-configured scope.", + "type": "string", + "const": "allow-get-status-bar-height", + "markdownDescription": "Enables the get_status_bar_height command without any pre-configured scope." + }, + { + "description": "Denies the get_status_bar_height command without any pre-configured scope.", + "type": "string", + "const": "deny-get-status-bar-height", + "markdownDescription": "Denies the get_status_bar_height command without any pre-configured scope." + }, { "description": "Enables the install_package command without any pre-configured scope.", "type": "string", @@ -367,10 +379,10 @@ "markdownDescription": "Denies the use_background_audio command without any pre-configured scope." }, { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`", + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`", "type": "string", "const": "default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`" + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-auth-with-safari`\n- `allow-auth-with-custom-tab`\n- `allow-copy-uri-to-path`\n- `allow-use-background-audio`\n- `allow-install-package`\n- `allow-set-system-ui-visibility`\n- `allow-get-status-bar-height`" } ] } diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs index 1c1aecad..04e24d3b 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/commands.rs @@ -51,3 +51,10 @@ pub(crate) async fn set_system_ui_visibility( ) -> Result { app.native_bridge().set_system_ui_visibility(payload) } + +#[command] +pub(crate) async fn get_status_bar_height( + app: AppHandle, +) -> Result { + app.native_bridge().get_status_bar_height() +} diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs index 5ddd7619..2db2b046 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/desktop.rs @@ -43,4 +43,8 @@ impl NativeBridge { ) -> crate::Result { Err(crate::Error::UnsupportedPlatformError) } + + pub fn get_status_bar_height(&self) -> crate::Result { + Err(crate::Error::UnsupportedPlatformError) + } } diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs index 8bb00a1d..5f0d5b2e 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/lib.rs @@ -43,6 +43,7 @@ pub fn init() -> TauriPlugin { commands::use_background_audio, commands::install_package, commands::set_system_ui_visibility, + commands::get_status_bar_height, ]) .setup(|app, api| { #[cfg(mobile)] diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs index 94a4a32f..924655e9 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/mobile.rs @@ -77,3 +77,11 @@ impl NativeBridge { .map_err(Into::into) } } + +impl NativeBridge { + pub fn get_status_bar_height(&self) -> crate::Result { + self.0 + .run_mobile_plugin("get_status_bar_height", ()) + .map_err(Into::into) + } +} diff --git a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs index 283717e3..5451eb9f 100644 --- a/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs +++ b/apps/readest-app/src-tauri/plugins/tauri-plugin-native-bridge/src/models.rs @@ -58,3 +58,10 @@ pub struct SetSystemUIVisibilityResponse { pub success: bool, pub error: Option, } + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetStatusBarHeightResponse { + pub height: u32, + pub error: Option, +} diff --git a/apps/readest-app/src/app/library/components/LibraryHeader.tsx b/apps/readest-app/src/app/library/components/LibraryHeader.tsx index 852ad2e6..f19f5f8b 100644 --- a/apps/readest-app/src/app/library/components/LibraryHeader.tsx +++ b/apps/readest-app/src/app/library/components/LibraryHeader.tsx @@ -9,6 +9,7 @@ import { MdOutlineMenu, MdArrowBackIosNew } from 'react-icons/md'; import { IoMdCloseCircle } from 'react-icons/io'; import { useEnv } from '@/context/EnvContext'; +import { useThemeStore } from '@/store/themeStore'; import { useTranslation } from '@/hooks/useTranslation'; import { useResponsiveSize } from '@/hooks/useResponsiveSize'; import { useTrafficLightStore } from '@/store/trafficLightStore'; @@ -36,6 +37,7 @@ const LibraryHeader: React.FC = ({ const router = useRouter(); const searchParams = useSearchParams(); const { appService } = useEnv(); + const { statusBarHeight } = useThemeStore(); const { isTrafficLightVisible, initializeTrafficLightStore, @@ -96,7 +98,9 @@ const LibraryHeader: React.FC = ({ isTrafficLightVisible ? 'pl-16' : 'pl-0 sm:pl-2', )} style={{ - marginTop: appService?.hasSafeAreaInset ? 'max(env(safe-area-inset-top), 24px)' : '', + marginTop: appService?.hasSafeAreaInset + ? `max(env(safe-area-inset-top), ${statusBarHeight}px)` + : '', }} >
diff --git a/apps/readest-app/src/app/library/page.tsx b/apps/readest-app/src/app/library/page.tsx index 040f9d85..ef22d700 100644 --- a/apps/readest-app/src/app/library/page.tsx +++ b/apps/readest-app/src/app/library/page.tsx @@ -28,6 +28,7 @@ import { usePullToRefresh } from '@/hooks/usePullToRefresh'; import { useTheme } from '@/hooks/useTheme'; import { useDemoBooks } from './hooks/useDemoBooks'; import { useBooksSync } from './hooks/useBooksSync'; +import { useThemeStore } from '@/store/themeStore'; import { useScreenWakeLock } from '@/hooks/useScreenWakeLock'; import { useOpenWithBooks } from '@/hooks/useOpenWithBooks'; import { @@ -65,6 +66,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP const _ = useTranslation(); useTheme({ systemUIVisible: true, appThemeColor: 'base-200' }); const { settings, setSettings, saveSettings } = useSettingsStore(); + const { statusBarHeight } = useThemeStore(); const [loading, setLoading] = useState(false); const isInitiating = useRef(false); const [libraryLoaded, setLibraryLoaded] = useState(false); @@ -542,7 +544,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP )} style={{ marginTop: appService?.hasSafeAreaInset - ? 'max(env(safe-area-inset-top), 24px)' + ? `max(env(safe-area-inset-top), ${statusBarHeight}px)` : '48px', }} > diff --git a/apps/readest-app/src/app/reader/components/HeaderBar.tsx b/apps/readest-app/src/app/reader/components/HeaderBar.tsx index ee588c5d..8b19f8dc 100644 --- a/apps/readest-app/src/app/reader/components/HeaderBar.tsx +++ b/apps/readest-app/src/app/reader/components/HeaderBar.tsx @@ -3,6 +3,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { PiDotsThreeVerticalBold } from 'react-icons/pi'; import { useEnv } from '@/context/EnvContext'; +import { useThemeStore } from '@/store/themeStore'; import { useReaderStore } from '@/store/readerStore'; import { useSidebarStore } from '@/store/sidebarStore'; import { useTrafficLightStore } from '@/store/trafficLightStore'; @@ -42,7 +43,8 @@ const HeaderBar: React.FC = ({ cleanupTrafficLightListeners, } = useTrafficLightStore(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const { bookKeys, hoveredBookKey, systemUIVisible, setHoveredBookKey } = useReaderStore(); + const { bookKeys, hoveredBookKey, setHoveredBookKey } = useReaderStore(); + const { systemUIVisible, statusBarHeight } = useThemeStore(); const { isSideBarVisible } = useSidebarStore(); const iconSize16 = useResponsiveSize(16); @@ -90,7 +92,7 @@ const HeaderBar: React.FC = ({ isVisible ? 'visible' : 'hidden', )} style={{ - height: systemUIVisible ? 'max(env(safe-area-inset-top), 24px)' : '', + height: systemUIVisible ? `max(env(safe-area-inset-top), ${statusBarHeight}px)` : '', }} />
= ({ )} style={{ marginTop: systemUIVisible - ? 'max(env(safe-area-inset-top), 24px)' + ? `max(env(safe-area-inset-top), ${statusBarHeight}px)` : 'env(safe-area-inset-top)', }} onMouseLeave={() => !appService?.isMobile && setHoveredBookKey('')} diff --git a/apps/readest-app/src/app/reader/components/Reader.tsx b/apps/readest-app/src/app/reader/components/Reader.tsx index 2cb23066..116da7e2 100644 --- a/apps/readest-app/src/app/reader/components/Reader.tsx +++ b/apps/readest-app/src/app/reader/components/Reader.tsx @@ -21,8 +21,8 @@ import ReaderContent from './ReaderContent'; const Reader: React.FC<{ ids?: string }> = ({ ids }) => { const { envConfig, appService } = useEnv(); const { settings, setSettings } = useSettingsStore(); - const { isDarkMode } = useThemeStore(); - const { hoveredBookKey, showSystemUI, dismissSystemUI } = useReaderStore(); + const { isDarkMode, showSystemUI, dismissSystemUI } = useThemeStore(); + const { hoveredBookKey } = useReaderStore(); const { isSideBarVisible } = useSidebarStore(); const { setLibrary } = useLibraryStore(); const [libraryLoaded, setLibraryLoaded] = useState(false); diff --git a/apps/readest-app/src/hooks/useTheme.ts b/apps/readest-app/src/hooks/useTheme.ts index ad32b118..ccdfacd4 100644 --- a/apps/readest-app/src/hooks/useTheme.ts +++ b/apps/readest-app/src/hooks/useTheme.ts @@ -3,7 +3,7 @@ import { useEnv } from '@/context/EnvContext'; import { useThemeStore } from '@/store/themeStore'; import { useSettingsStore } from '@/store/settingsStore'; import { applyCustomTheme, Palette } from '@/styles/themes'; -import { setSystemUIVisibility } from '@/utils/bridge'; +import { getStatusBarHeight, setSystemUIVisibility } from '@/utils/bridge'; type UseThemeProps = { systemUIVisible?: boolean; @@ -16,15 +16,22 @@ export const useTheme = ({ }: UseThemeProps = {}) => { const { appService } = useEnv(); const { settings } = useSettingsStore(); - const { themeColor, isDarkMode, updateAppTheme } = useThemeStore(); + const { themeColor, isDarkMode, updateAppTheme, setStatusBarHeight } = useThemeStore(); useEffect(() => { updateAppTheme(appThemeColor); if (appService?.isMobile) { setSystemUIVisibility({ visible: systemUIVisible, darkMode: isDarkMode }); } + if (appService?.isAndroidApp) { + getStatusBarHeight().then((res) => { + if (res.height && res.height > 0) { + setStatusBarHeight(res.height / window.devicePixelRatio); + } + }); + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isDarkMode]); + }, [appService, isDarkMode]); useEffect(() => { const customThemes = settings.globalReadSettings?.customThemes ?? []; diff --git a/apps/readest-app/src/store/readerStore.ts b/apps/readest-app/src/store/readerStore.ts index 0f74629d..04609290 100644 --- a/apps/readest-app/src/store/readerStore.ts +++ b/apps/readest-app/src/store/readerStore.ts @@ -30,13 +30,9 @@ interface ReaderStore { viewStates: { [key: string]: ViewState }; bookKeys: string[]; hoveredBookKey: string | null; - systemUIVisible: boolean; setBookKeys: (keys: string[]) => void; setHoveredBookKey: (key: string | null) => void; setBookmarkRibbonVisibility: (key: string, visible: boolean) => void; - showSystemUI: () => void; - dismissSystemUI: () => void; - setProgress: ( key: string, location: string, @@ -67,12 +63,8 @@ export const useReaderStore = create((set, get) => ({ viewStates: {}, bookKeys: [], hoveredBookKey: null, - systemUIVisible: false, setBookKeys: (keys: string[]) => set({ bookKeys: keys }), setHoveredBookKey: (key: string | null) => set({ hoveredBookKey: key }), - showSystemUI: () => set({ systemUIVisible: true }), - dismissSystemUI: () => set({ systemUIVisible: false }), - getView: (key: string | null) => (key && get().viewStates[key]?.view) || null, setView: (key: string, view) => set((state) => ({ diff --git a/apps/readest-app/src/store/themeStore.ts b/apps/readest-app/src/store/themeStore.ts index e1322445..9030b29c 100644 --- a/apps/readest-app/src/store/themeStore.ts +++ b/apps/readest-app/src/store/themeStore.ts @@ -10,6 +10,11 @@ interface ThemeState { systemIsDarkMode: boolean; themeCode: ThemeCode; isDarkMode: boolean; + systemUIVisible: boolean; + statusBarHeight: number; + setStatusBarHeight: (height: number) => void; + showSystemUI: () => void; + dismissSystemUI: () => void; getIsDarkMode: () => boolean; setThemeMode: (mode: ThemeMode) => void; setThemeColor: (color: string) => void; @@ -62,6 +67,11 @@ export const useThemeStore = create((set, get) => { systemIsDarkMode, isDarkMode, themeCode, + systemUIVisible: false, + statusBarHeight: 24, + showSystemUI: () => set({ systemUIVisible: true }), + dismissSystemUI: () => set({ systemUIVisible: false }), + setStatusBarHeight: (height: number) => set({ statusBarHeight: height }), getIsDarkMode: () => get().isDarkMode, setThemeMode: (mode) => { if (typeof window !== 'undefined' && localStorage) { diff --git a/apps/readest-app/src/utils/bridge.ts b/apps/readest-app/src/utils/bridge.ts index 99055d80..03bd6178 100644 --- a/apps/readest-app/src/utils/bridge.ts +++ b/apps/readest-app/src/utils/bridge.ts @@ -33,6 +33,11 @@ export interface SetSystemUIVisibilityResponse { error?: string; } +export interface GetStatusBarHeightResponse { + height: number; + error?: string; +} + export async function copyURIToPath(request: CopyURIRequest): Promise { const result = await invoke('plugin:native-bridge|copy_uri_to_path', { payload: request, @@ -67,3 +72,10 @@ export async function setSystemUIVisibility( ); return result; } + +export async function getStatusBarHeight(): Promise { + const result = await invoke( + 'plugin:native-bridge|get_status_bar_height', + ); + return result; +}