ux: add splash screen for iOS and Android and reduce flash when opening books (#1926)

This commit is contained in:
Huang Xin
2025-08-30 01:10:09 +08:00
committed by GitHub
parent 267fe58a8c
commit 856ed4d3b3
13 changed files with 226 additions and 43 deletions
Generated
+4 -4
View File
@@ -5337,7 +5337,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.8.2"
version = "2.8.4"
dependencies = [
"anyhow",
"bytes",
@@ -5810,7 +5810,7 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.8.0"
version = "2.8.1"
dependencies = [
"gtk",
"http",
@@ -7249,9 +7249,9 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "wry"
version = "0.53.1"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5698e50a589268aec06d2219f48b143222f7b5ad9aa690118b8dce0a8dcac574"
checksum = "31f0e9642a0d061f6236c54ccae64c2722a7879ad4ec7dff59bd376d446d8e90"
dependencies = [
"base64 0.22.1",
"block2 0.6.1",
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
@@ -0,0 +1,8 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="#FF323130" />
</item>
<item android:gravity="center" android:width="120dp" android:height="120dp">
<bitmap android:src="@drawable/splash_icon" />
</item>
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

@@ -2,9 +2,7 @@
<!-- Base application theme. -->
<style name="Theme.readest" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:windowSplashScreenBackground">@android:color/transparent</item>
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="31">@null</item>
<item name="android:windowBackground">@drawable/splash_background</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowTranslucentStatus">false</item>
+2 -1
View File
@@ -242,7 +242,8 @@ pub fn run() {
};
let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
.background_throttling(BackgroundThrottlingPolicy::Disabled);
.background_throttling(BackgroundThrottlingPolicy::Disabled)
.background_color(tauri::window::Color(50, 49, 48, 255));
#[cfg(desktop)]
let win_builder = win_builder.inner_size(800.0, 600.0).resizable(true);
@@ -9,7 +9,6 @@ import { useTranslation } from '@/hooks/useTranslation';
import { useLongPress } from '@/hooks/useLongPress';
import { Menu, MenuItem } from '@tauri-apps/api/menu';
import { revealItemInDir } from '@tauri-apps/plugin-opener';
import { getLocalBookFilename } from '@/utils/book';
import { getOSPlatform } from '@/utils/misc';
import { throttle } from '@/utils/throttle';
import { LibraryCoverFitType, LibraryViewModeType } from '@/types/settings';
@@ -192,7 +191,7 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
const showBookInFinderMenuItem = await MenuItem.new({
text: _(fileRevealLabel),
action: async () => {
const folder = `${settings.localBooksDir}/${getLocalBookFilename(book)}`;
const folder = `${settings.localBooksDir}/${book.hash}`;
revealItemInDir(folder);
},
});
+5 -7
View File
@@ -619,9 +619,11 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
};
if (!appService || !insets || checkOpenWithBooks || checkLastOpenBooks) {
return <div className='bg-base-200 inset-0 flex h-[100vh] items-center justify-center'></div>;
return <div className='bg-base-200 h-[100vh]' />;
}
const showBookshelf = libraryLoaded || libraryBooks.length > 0;
return (
<div
ref={pageRef}
@@ -647,7 +649,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
<Spinner loading />
</div>
)}
{libraryLoaded &&
{showBookshelf &&
(libraryBooks.some((book) => !book.deletedAt) ? (
<OverlayScrollbarsComponent
defer
@@ -728,11 +730,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
const LibraryPage = () => {
return (
<Suspense
fallback={
<div className='bg-base-200 inset-0 flex h-[100vh] items-center justify-center'></div>
}
>
<Suspense fallback={<div className='bg-base-200 h-[100vh]' />}>
<LibraryPageWithSearchParams />
</Suspense>
);
@@ -45,6 +45,7 @@ import { lockScreenOrientation } from '@/utils/bridge';
import { useTextTranslation } from '../hooks/useTextTranslation';
import { manageSyntaxHighlighting } from '@/utils/highlightjs';
import { getViewInsets } from '@/utils/insets';
import Spinner from '@/components/Spinner';
import ConfirmSyncDialog from './ConfirmSyncDialog';
declare global {
@@ -74,6 +75,8 @@ const FoliateViewer: React.FC<{
const isViewCreated = useRef(false);
const doubleClickDisabled = useRef(!!viewSettings?.disableDoubleClick);
const [toastMessage, setToastMessage] = useState('');
const [loading, setLoading] = useState(false);
const docLoaded = useRef(false);
useEffect(() => {
const timer = setTimeout(() => setToastMessage(''), 2000);
@@ -127,6 +130,8 @@ const FoliateViewer: React.FC<{
};
const docLoadHandler = (event: Event) => {
setLoading(false);
docLoaded.current = true;
const detail = (event as CustomEvent).detail;
console.log('doc index loaded:', detail.index);
if (detail.doc) {
@@ -228,6 +233,8 @@ const FoliateViewer: React.FC<{
if (isViewCreated.current) return;
isViewCreated.current = true;
setTimeout(() => setLoading(true), 200);
const openBook = async () => {
console.log('Opening book', bookKey);
await import('foliate-js/view.js');
@@ -383,6 +390,7 @@ const FoliateViewer: React.FC<{
{...mouseHandlers}
{...touchHandlers}
/>
{!docLoaded.current && loading && <Spinner loading={true} />}
{syncState === 'conflict' && conflictDetails && (
<ConfirmSyncDialog
details={conflictDetails}
@@ -107,25 +107,25 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hoveredBookKey]);
return (
libraryLoaded &&
settings.globalReadSettings && (
<div
className={clsx(
`reader-page text-base-content select-none overflow-hidden`,
appService?.isLinuxApp && 'window-border',
appService?.hasRoundedWindow && 'rounded-window',
)}
>
<Suspense>
<ReaderContent ids={ids} settings={settings} />
<AboutWindow />
<UpdaterWindow />
<KOSyncSettingsWindow />
<Toast />
</Suspense>
</div>
)
return libraryLoaded && settings.globalReadSettings ? (
<div
className={clsx(
`reader-page bg-base-100 text-base-content select-none overflow-hidden`,
appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh',
appService?.isLinuxApp && 'window-border',
appService?.hasRoundedWindow && 'rounded-window',
)}
>
<Suspense fallback={<div className='h-[100vh]'></div>}>
<ReaderContent ids={ids} settings={settings} />
<AboutWindow />
<UpdaterWindow />
<KOSyncSettingsWindow />
<Toast />
</Suspense>
</div>
) : (
<div className='bg-base-100 h-[100vh]'></div>
);
};
@@ -179,7 +179,7 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
const bookData = getBookData(bookKeys[0]!);
const viewSettings = getViewSettings(bookKeys[0]!);
if (!bookData || !bookData.book || !bookData.bookDoc || !viewSettings) {
setTimeout(() => setLoading(true), 300);
setTimeout(() => setLoading(true), 200);
return (
loading && (
<div className={clsx('hero hero-content', appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh')}>
+6 -5
View File
@@ -3,11 +3,12 @@ export function formatReadingProgress(
total: number | undefined,
template: string,
): string {
if (current === undefined || total === undefined || total <= 0 || current < 0) {
if (current !== undefined && total !== undefined && total > 0 && current >= 0) {
return template
.replace('{current}', String(current + 1))
.replace('{total}', String(total))
.replace('{percent}', (((current + 1) / total) * 100).toFixed(1));
} else {
return '';
}
return template
.replace('{current}', String(current + 1))
.replace('{total}', String(total))
.replace('{percent}', (((current + 1) / total) * 100).toFixed(1));
}