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

@@ -10,6 +10,7 @@ import { useBackStep } from '@/nav/Navigator';
import { EmptyState, ErrorState, InlineError, LoadingState, PrimaryButton, Screen, ScreenHeader } from '@/components/ui';
import { friendlyError } from '@/lib/errors';
import { formatJalaliDate, formatNumber, formatToman, toFa } from '@/lib/format';
import { useCompanion } from '@/state/companion';
function durationLabel(days: number): string {
if (days === 30 || days === 31) return 'یک‌ماهه';
@@ -42,6 +43,7 @@ export function SubscriptionScreen({ initialView }: { initialView?: View }) {
const [picked, setPicked] = useState<string | null>(null);
const [buying, setBuying] = useState<Plan | null>(null);
const bazaar = inBazaarApp();
const look = useCompanion();
// از فهرست، «برگشت» اول به دو گزینه برمی‌گردد.
useBackStep(view !== null && !initialView, () => setView(null));
@@ -64,11 +66,11 @@ export function SubscriptionScreen({ initialView }: { initialView?: View }) {
<div className="flex-1 overflow-y-auto">
<ScreenHeader
title={view === 'pack' ? 'بسته‌ی سوال' : view === 'subscription' ? (active ? 'تمدیدِ اشتراک' : 'اشتراکِ تدریسینو') : 'فروشگاه'}
subtitle={view === 'pack' ? 'سوالِ بیشتر از پی، بدونِ تاریخِ انقضا' : 'همه‌ی فصل‌ها، قلبِ نامحدود'}
subtitle={view === 'pack' ? `سوالِ بیشتر از ${look.name}، بدونِ تاریخِ انقضا` : 'همه‌ی فصل‌ها، قلبِ نامحدود'}
color="#ED6956"
exact
back={view !== null && !initialView ? () => setView(null) : true}
mascot={{ char: 'pi', mood: 'excited', size: 60 }}
mascot={{ mood: 'excited', size: 60 }}
/>
<div className="flex flex-col gap-4 px-4 py-5">
@@ -88,7 +90,7 @@ export function SubscriptionScreen({ initialView }: { initialView?: View }) {
icon={<Sparkles size={26} />}
tone="#9B7BFF"
title="بسته‌ی سوال"
text="سوالِ بیشتر از پی، هیچ‌وقت تموم نمی‌شه"
text={`سوالِ بیشتر از ${look.name}، هیچ‌وقت تموم نمی‌شه`}
onClick={() => setView('pack')}
/>
</div>