diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 7ca5436..eabdfba 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index b0d3af6..2731b54 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 69a23e8..29aba9a 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index dde8db3..174d600 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 99a5c06..807cde2 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/web/index.html b/web/index.html index 517c162..8a92972 100644 --- a/web/index.html +++ b/web/index.html @@ -28,6 +28,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + reader
+
diff --git a/web/public/manifest.webmanifest b/web/public/manifest.webmanifest index 6541dea..dadb80d 100644 --- a/web/public/manifest.webmanifest +++ b/web/public/manifest.webmanifest @@ -6,7 +6,10 @@ "lang": "zh-CN", "start_url": "./", "scope": "./", + "display_override": ["window-controls-overlay", "standalone"], "display": "standalone", + "orientation": "any", + "launch_handler": { "client_mode": "navigate-existing" }, "background_color": "#fbfaf7", "theme_color": "#fbfaf7", "icons": [ diff --git a/web/public/startup/startup-1125x2436.png b/web/public/startup/startup-1125x2436.png new file mode 100644 index 0000000..e6751d6 Binary files /dev/null and b/web/public/startup/startup-1125x2436.png differ diff --git a/web/public/startup/startup-1136x640.png b/web/public/startup/startup-1136x640.png new file mode 100644 index 0000000..7bb1764 Binary files /dev/null and b/web/public/startup/startup-1136x640.png differ diff --git a/web/public/startup/startup-1170x2532.png b/web/public/startup/startup-1170x2532.png new file mode 100644 index 0000000..28120fc Binary files /dev/null and b/web/public/startup/startup-1170x2532.png differ diff --git a/web/public/startup/startup-1179x2556.png b/web/public/startup/startup-1179x2556.png new file mode 100644 index 0000000..2f652e9 Binary files /dev/null and b/web/public/startup/startup-1179x2556.png differ diff --git a/web/public/startup/startup-1206x2622.png b/web/public/startup/startup-1206x2622.png new file mode 100644 index 0000000..5ae9324 Binary files /dev/null and b/web/public/startup/startup-1206x2622.png differ diff --git a/web/public/startup/startup-1242x2208.png b/web/public/startup/startup-1242x2208.png new file mode 100644 index 0000000..5df8e96 Binary files /dev/null and b/web/public/startup/startup-1242x2208.png differ diff --git a/web/public/startup/startup-1242x2688.png b/web/public/startup/startup-1242x2688.png new file mode 100644 index 0000000..f9d2cb7 Binary files /dev/null and b/web/public/startup/startup-1242x2688.png differ diff --git a/web/public/startup/startup-1284x2778.png b/web/public/startup/startup-1284x2778.png new file mode 100644 index 0000000..c56944d Binary files /dev/null and b/web/public/startup/startup-1284x2778.png differ diff --git a/web/public/startup/startup-1290x2796.png b/web/public/startup/startup-1290x2796.png new file mode 100644 index 0000000..29ccc7d Binary files /dev/null and b/web/public/startup/startup-1290x2796.png differ diff --git a/web/public/startup/startup-1320x2868.png b/web/public/startup/startup-1320x2868.png new file mode 100644 index 0000000..2763b05 Binary files /dev/null and b/web/public/startup/startup-1320x2868.png differ diff --git a/web/public/startup/startup-1334x750.png b/web/public/startup/startup-1334x750.png new file mode 100644 index 0000000..4842458 Binary files /dev/null and b/web/public/startup/startup-1334x750.png differ diff --git a/web/public/startup/startup-1536x2048.png b/web/public/startup/startup-1536x2048.png new file mode 100644 index 0000000..ece5074 Binary files /dev/null and b/web/public/startup/startup-1536x2048.png differ diff --git a/web/public/startup/startup-1620x2160.png b/web/public/startup/startup-1620x2160.png new file mode 100644 index 0000000..3902d36 Binary files /dev/null and b/web/public/startup/startup-1620x2160.png differ diff --git a/web/public/startup/startup-1640x2360.png b/web/public/startup/startup-1640x2360.png new file mode 100644 index 0000000..6362808 Binary files /dev/null and b/web/public/startup/startup-1640x2360.png differ diff --git a/web/public/startup/startup-1668x2388.png b/web/public/startup/startup-1668x2388.png new file mode 100644 index 0000000..b836535 Binary files /dev/null and b/web/public/startup/startup-1668x2388.png differ diff --git a/web/public/startup/startup-1792x828.png b/web/public/startup/startup-1792x828.png new file mode 100644 index 0000000..81cfc3e Binary files /dev/null and b/web/public/startup/startup-1792x828.png differ diff --git a/web/public/startup/startup-2048x1536.png b/web/public/startup/startup-2048x1536.png new file mode 100644 index 0000000..4ab8bc6 Binary files /dev/null and b/web/public/startup/startup-2048x1536.png differ diff --git a/web/public/startup/startup-2048x2732.png b/web/public/startup/startup-2048x2732.png new file mode 100644 index 0000000..e448b4f Binary files /dev/null and b/web/public/startup/startup-2048x2732.png differ diff --git a/web/public/startup/startup-2160x1620.png b/web/public/startup/startup-2160x1620.png new file mode 100644 index 0000000..128ffcf Binary files /dev/null and b/web/public/startup/startup-2160x1620.png differ diff --git a/web/public/startup/startup-2208x1242.png b/web/public/startup/startup-2208x1242.png new file mode 100644 index 0000000..f5b96e5 Binary files /dev/null and b/web/public/startup/startup-2208x1242.png differ diff --git a/web/public/startup/startup-2360x1640.png b/web/public/startup/startup-2360x1640.png new file mode 100644 index 0000000..92e960d Binary files /dev/null and b/web/public/startup/startup-2360x1640.png differ diff --git a/web/public/startup/startup-2388x1668.png b/web/public/startup/startup-2388x1668.png new file mode 100644 index 0000000..57344ff Binary files /dev/null and b/web/public/startup/startup-2388x1668.png differ diff --git a/web/public/startup/startup-2436x1125.png b/web/public/startup/startup-2436x1125.png new file mode 100644 index 0000000..d335ba1 Binary files /dev/null and b/web/public/startup/startup-2436x1125.png differ diff --git a/web/public/startup/startup-2532x1170.png b/web/public/startup/startup-2532x1170.png new file mode 100644 index 0000000..b43d87f Binary files /dev/null and b/web/public/startup/startup-2532x1170.png differ diff --git a/web/public/startup/startup-2556x1179.png b/web/public/startup/startup-2556x1179.png new file mode 100644 index 0000000..8953068 Binary files /dev/null and b/web/public/startup/startup-2556x1179.png differ diff --git a/web/public/startup/startup-2622x1206.png b/web/public/startup/startup-2622x1206.png new file mode 100644 index 0000000..11e8c4c Binary files /dev/null and b/web/public/startup/startup-2622x1206.png differ diff --git a/web/public/startup/startup-2688x1242.png b/web/public/startup/startup-2688x1242.png new file mode 100644 index 0000000..1cdbb6d Binary files /dev/null and b/web/public/startup/startup-2688x1242.png differ diff --git a/web/public/startup/startup-2732x2048.png b/web/public/startup/startup-2732x2048.png new file mode 100644 index 0000000..43aeb80 Binary files /dev/null and b/web/public/startup/startup-2732x2048.png differ diff --git a/web/public/startup/startup-2778x1284.png b/web/public/startup/startup-2778x1284.png new file mode 100644 index 0000000..e04c21f Binary files /dev/null and b/web/public/startup/startup-2778x1284.png differ diff --git a/web/public/startup/startup-2796x1290.png b/web/public/startup/startup-2796x1290.png new file mode 100644 index 0000000..f13650c Binary files /dev/null and b/web/public/startup/startup-2796x1290.png differ diff --git a/web/public/startup/startup-2868x1320.png b/web/public/startup/startup-2868x1320.png new file mode 100644 index 0000000..b1ee87b Binary files /dev/null and b/web/public/startup/startup-2868x1320.png differ diff --git a/web/public/startup/startup-640x1136.png b/web/public/startup/startup-640x1136.png new file mode 100644 index 0000000..eda9fe1 Binary files /dev/null and b/web/public/startup/startup-640x1136.png differ diff --git a/web/public/startup/startup-750x1334.png b/web/public/startup/startup-750x1334.png new file mode 100644 index 0000000..2519204 Binary files /dev/null and b/web/public/startup/startup-750x1334.png differ diff --git a/web/public/startup/startup-828x1792.png b/web/public/startup/startup-828x1792.png new file mode 100644 index 0000000..0a9733d Binary files /dev/null and b/web/public/startup/startup-828x1792.png differ diff --git a/web/src/main.ts b/web/src/main.ts index d8434c6..4e3887b 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -42,4 +42,4 @@ window.__readerFlush = () => { void app.start().catch((err: unknown) => { root.textContent = `客户端启动失败:${err instanceof Error ? err.message : String(err)}`; -}); +}).finally(() => document.getElementById('app-startup')?.remove()); diff --git a/web/test/pwa.test.ts b/web/test/pwa.test.ts index 536ddb3..6186485 100644 --- a/web/test/pwa.test.ts +++ b/web/test/pwa.test.ts @@ -1,6 +1,8 @@ // @vitest-environment jsdom import { afterEach, beforeEach, expect, it, vi } from 'vitest'; import { registerPwa, watchPwaUpdates } from '../src/core/pwa.ts'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; let registration: ServiceWorkerRegistration; let serviceWorker: ServiceWorkerContainer; @@ -126,6 +128,16 @@ it('keeps ordinary HTTP and unsupported browsers usable', () => { expect(registerPwa).not.toThrow(); }); +it('ships standalone launch metadata and a first-paint startup shell', () => { + const manifest = JSON.parse(readFileSync(resolve(process.cwd(), 'public/manifest.webmanifest'), 'utf8')); + expect(manifest.display).toBe('standalone'); + expect(manifest.display_override).toContain('standalone'); + expect(manifest.launch_handler.client_mode).toBe('navigate-existing'); + const html = readFileSync(resolve(process.cwd(), 'index.html'), 'utf8'); + expect(html).toContain('id="app-startup"'); + expect(html).toContain('apple-touch-startup-image'); +}); + it('reports failed registration without an unhandled rejection', async () => { const error = new Error('storage unavailable'); register.mockRejectedValueOnce(error); diff --git a/web/tools/pwa/branding.mjs b/web/tools/pwa/branding.mjs new file mode 100644 index 0000000..b9a310d --- /dev/null +++ b/web/tools/pwa/branding.mjs @@ -0,0 +1,41 @@ +// One PWA icon supplies APK launcher sizes and iOS startup images. +// Run: node web/tools/pwa/branding.mjs +import { chromium } from 'playwright'; +import { readFile, mkdir, writeFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +const root = new URL('../../', import.meta.url); +const icon = await readFile(new URL('public/icons/icon-512.png', root)); +const browser = await chromium.launch({ headless: true }); +try { + const page = await browser.newPage(); + await page.setContent(''); + async function render(relative, width, height, size) { + const bytes = await page.evaluate(async ({ source, width, height, size }) => { + const img = new Image(); img.src = source; await img.decode(); + const canvas = document.querySelector('canvas'); canvas.width = width; canvas.height = height; + const ctx = canvas.getContext('2d'); ctx.fillStyle = '#fbfaf7'; ctx.fillRect(0, 0, width, height); + ctx.drawImage(img, (width - size) / 2, (height - size) / 2, size, size); + return canvas.toDataURL('image/png').split(',')[1]; + }, { source: `data:image/png;base64,${icon.toString('base64')}`, width, height, size }); + const dest = new URL(relative, root); await mkdir(new URL('./', dest), { recursive: true }); + await writeFile(dest, Buffer.from(bytes, 'base64')); + } + for (const [density, size] of Object.entries({ mdpi: 48, hdpi: 72, xhdpi: 96, xxhdpi: 144, xxxhdpi: 192 })) { + await render(`../android/app/src/main/res/mipmap-${density}/ic_launcher.png`, size, size, size); + } + const devices = [[320,568,2],[375,667,2],[414,736,3],[375,812,3],[390,844,3],[393,852,3],[402,874,3],[414,896,2],[414,896,3],[428,926,3],[430,932,3],[440,956,3],[768,1024,2],[810,1080,2],[820,1180,2],[834,1194,2],[1024,1366,2]]; + const links = []; + for (const [w,h,dpr] of devices) { + for (const orientation of ['portrait', 'landscape']) { + const width = (orientation === 'portrait' ? w : h) * dpr; + const height = (orientation === 'portrait' ? h : w) * dpr; + const name = `startup-${width}x${height}.png`; + await render(`public/startup/${name}`, width, height, 96 * dpr); + links.push(` `); + } + } + const htmlPath = new URL('index.html', root); + const html = await readFile(htmlPath, 'utf8'); + await writeFile(htmlPath, html.replace(/[\s\S]*?/, `\n${links.join('\n')}\n `)); + console.log('Generated Android launcher icons and iOS startup images.'); +} finally { await browser.close(); }