From c7407f32b99d33e784bce517f3f9b17aba29dd54 Mon Sep 17 00:00:00 2001 From: AliReza Date: Sun, 11 Oct 2026 01:19:34 +0330 Subject: [PATCH] Splash only appears when the server is slow: no flash of the welcome screen on a fast start The splash used to stay a fixed 700ms plus a fade, so a fast response still showed it briefly and jumped away. Now only the matching background shows for the first 350ms; the splash content appears only if the response has not arrived by then, and once shown it stays at least 700ms. Not run on a device. --- src/app/App.tsx | 33 ++++++++++++++++++++++++++------- src/screens/SplashScreen.tsx | 8 ++++++-- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index 58667d0..8faaf74 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { AnimatePresence, MotionConfig, motion } from 'motion/react'; import { api, ApiError, http, type Grade, type UserProfile } from '@/api'; import type { Prefetch } from '@/state/AppData'; @@ -23,7 +23,13 @@ type Flow = | { kind: 'setup'; profile: UserProfile; grades: Grade[] } | { kind: 'main'; profile: UserProfile; grades: Grade[]; prefetch?: Prefetch }; -/** کمترین زمانِ نمایشِ اسپلش، تا صفحه چشمک نزند. */ +/** + * اسپلش فقط وقتی نشان داده می‌شود که سرور دیرتر از SPLASH_DELAY_MS جواب بدهد؛ اگر زودتر رسید، کاربر + * اسپلش را نمی‌بیند و مستقیم به صفحه‌ی بعدی می‌رود (به‌جای چند صد میلی‌ثانیه چشمک). + * اگر یک بار نشان داده شد، دست‌کم SPLASH_MIN_MS می‌ماند تا خودش چشمک نزند. + * تا آن لحظه فقط پس‌زمینه‌ی هم‌رنگِ اسپلش دیده می‌شود، نه صفحه‌ی سفید. + */ +const SPLASH_DELAY_MS = 350; const SPLASH_MIN_MS = 700; /** @@ -32,6 +38,8 @@ const SPLASH_MIN_MS = 700; */ export function App() { const [flow, setFlow] = useState({ kind: 'boot' }); + const [splashVisible, setSplashVisible] = useState(false); + const splashShownAt = useRef(null); /** پروفایل و پایه‌ها را می‌گیرد و تصمیم می‌گیرد کاربر کجا برود. */ const enter = useCallback(async () => { @@ -45,7 +53,12 @@ export function App() { const boot = useCallback(async () => { setFlow({ kind: 'boot' }); - const started = Date.now(); + splashShownAt.current = null; + setSplashVisible(false); + const timer = setTimeout(() => { + splashShownAt.current = Date.now(); + setSplashVisible(true); + }, SPLASH_DELAY_MS); let next: Flow | null = null; if (api.auth.isSignedIn()) { try { @@ -59,8 +72,13 @@ export function App() { } else { next = store.get(KEYS.onboarded) ? { kind: 'login' } : { kind: 'onboarding' }; } - const wait = SPLASH_MIN_MS - (Date.now() - started); - if (wait > 0) await new Promise((r) => setTimeout(r, wait)); + clearTimeout(timer); + // اسپلش دیده شده؟ فقط در آن صورت تا کمینه‌ی زمانش نگه دار. + const shownAt = splashShownAt.current; + if (shownAt !== null) { + const wait = SPLASH_MIN_MS - (Date.now() - shownAt); + if (wait > 0) await new Promise((r) => setTimeout(r, wait)); + } if (next) setFlow(next); }, [enter]); @@ -99,9 +117,10 @@ export function App() { initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - transition={{ duration: 0.2 }} + // اگر اسپلش هرگز دیده نشد، محو‌شدن هم لازم نیست (وگرنه همان چشمک از راهِ دیگر برمی‌گردد). + transition={{ duration: splashVisible || flow.kind !== 'boot' ? 0.2 : 0 }} > - {flow.kind === 'boot' && } + {flow.kind === 'boot' && } {flow.kind === 'bootError' && (
void boot()} /> diff --git a/src/screens/SplashScreen.tsx b/src/screens/SplashScreen.tsx index f5a48b9..65b578f 100644 --- a/src/screens/SplashScreen.tsx +++ b/src/screens/SplashScreen.tsx @@ -8,8 +8,12 @@ const GLYPHS = [ { c: '÷', x: 64, d: 3.8 }, { c: '∑', x: 80, d: 0.6 }, { c: '×', x: 90, d: 4.6 }, ]; -/** تا وقتی نشستِ قبلی بررسی می‌شود نمایش داده می‌شود؛ عمداً تأخیرِ ساختگی ندارد. */ -export function SplashScreen() { +/** + * تا وقتی نشستِ قبلی بررسی می‌شود نمایش داده می‌شود. `showContent=false` فقط پس‌زمینه است؛ App + * محتوا را فقط وقتی روشن می‌کند که پاسخِ سرور دیر کرده باشد. + */ +export function SplashScreen({ showContent }: { showContent: boolean }) { + if (!showContent) return
; return (