The selected companion everywhere, each with a small trait; next-APK notes

- state/companion.ts: one store for the chosen companion (also in localStorage so splash/login show it).
  BrandMark (the orange «π» tile) now shows the companion's symbol (Σ Δ π ω) in its colour; screen-header
  mascots, loaders and empty/error states default to it; the assistant is «از {name} بپرس» with its face,
  colour and wording; the shop, lesson and quiz «ask» buttons name it; the active tab takes its tint.
- Traits, all client-side and light: Σ sigma keeps a running tally of correct answers in the quiz header;
  Δ delta shows a flame «×N» chip and an extra buzz for answers in a row; π pi gives a short solving tip
  after a wrong answer; ω omega throws gold confetti at the end of every island whatever the score.
  Each companion's «correct» sound is pitched differently; confetti uses its colours. Characters screen
  lists each trait.
- android/README.md: what MainActivity.kt has that the installed APK lacks (vibrate bridge, media gesture,
  in-app gateway + paysInApp), what to check on a phone after building, signing owner, Bazaar policy note.

Checked in headless Chrome with Sigma, Delta and Omega selected (home, assistant, shop, characters, quiz).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 18:51:19 +03:30
parent 737414f246
commit 8a61cb25b8
21 changed files with 256 additions and 69 deletions

View File

@@ -1,6 +1,7 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
import { api, type Character, type Grade, type HomeResponse, type UserProfile, type UserSummary } from '@/api';
import { seyarFromSlug, type SeyarChar } from '@/mascots/seyar';
import { companionStore, useCompanion } from '@/state/companion';
/**
* داده‌هایی که چند صفحه با هم لازم دارند: پروفایل، آمار، نقشه، همراهِ انتخابی
@@ -48,7 +49,9 @@ export function AppDataProvider({
const [summary, setSummary] = useState<UserSummary | null>(prefetch?.summary ?? null);
const [home, setHome] = useState<HomeResponse | null>(prefetch?.home ?? null);
const [homeError, setHomeError] = useState<unknown>(null);
const [companion, setCompanionChar] = useState<SeyarChar>('sigma');
// همراه در companionStore است تا بیرون از AppData (اسپلش، نشان، لودرها) هم خوانده شود.
const companion = useCompanion().char;
const setCompanionChar = companionStore.set;
const [unreadCount, setUnreadCount] = useState(0);
const refreshHome = useCallback(async () => {
@@ -84,10 +87,10 @@ export function AppDataProvider({
api.characters
.list()
.then((list) => {
const selected = seyarFromSlug(list.find((c) => c.selected)?.slug);
if (selected) setCompanionChar(selected);
// بی‌انتخاب یعنی پیش‌فرضِ سرور (سیگما)؛ همراهِ کاربرِ قبلی روی این دستگاه نباید بماند.
setCompanionChar(seyarFromSlug(list.find((c) => c.selected)?.slug) ?? 'sigma');
})
.catch(() => {});
.catch((err: unknown) => console.warn('companion lookup failed; keeping the stored one', err));
// فقط بارِ اول؛ prefetch بعد از mount عوض نمی‌شود.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [refreshHome, refreshSummary, refreshUnread]);