Commit Graph

81 Commits

Author SHA1 Message Date
d440986b88 Checkout: Zarinpal is the default; Bazaar tile shows the price Bazaar itself has (Poolakey getSkuDetails bridge)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:21:48 +03:30
fffdeac3ed Bazaar: subscriptions (subscribeProduct), dynamic price token, RSA key; Zarinpal and Bazaar side by side at checkout
- Checkout sheet always shows both methods as two tiles; Bazaar tile disabled with the reason
  (not in Bazaar / Android app only / update the app) instead of hidden; default is Bazaar inside an app that can buy.
- Bridge v2: TadrisinoAndroid.bazaarBuy(sku, orderId, productType, dynamicPriceToken); inAndroidApp() split
  from canBuyFromBazaar() so the pay.html workaround still applies to the installed APK.
- Bazaar flow: order -> optional discount code -> POST /payment/bazaar/orders/{id}/start -> Poolakey -> verify;
  consume only packs.
- Android: RSA key, subscribeProduct/getSubscribedProducts for subscriptions, dynamicPriceToken, versionCode 2.
  compileDebugKotlin green; no purchase tested on a phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:51:03 +03:30
3a2a685ed3 FeedbackBar: like/dislike/report under every quiz question and lesson 2026-10-12 12:56:52 +03:30
41f1c993ee plainMath: identifiers like OPENAI_API_KEY untouched, log base text 2026-10-12 12:18:48 +03:30
f2263e6dff MathText: last-line guard that turns stray LaTeX/markdown into plain Unicode 2026-10-12 12:15:31 +03:30
cf2a3251dd Quiz: draw the question figure under the prompt (Question.figure) 2026-10-12 11:58:42 +03:30
18eeb54327 Island label shows up to 3 lines so the part number of split islands stays visible
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 10:54:34 +03:30
f569ece55b Subject bar under the header in Quizzes and Lessons, synced between tabs; bidi fix for Latin math in RTL text
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 05:35:30 +03:30
56dcc93202 android/README: splash/offline are the APK's job; acceptance checklist incl. VPN-on white page 2026-10-11 22:57:57 +03:30
186dc208be GET requests time out after 8s (was 15s): the 'internet is slow' message and retry come sooner
Writes keep 15s so an early cut does not make the user submit twice. Worst-case boot
wait with an unreachable API goes from ~50s to ~27s (3 attempts + backoff). Not tested
on a real slow network.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:54:21 +03:30
b635b15c7d android/README: splash and offline screen join the next-APK list 2026-10-11 22:52:17 +03:30
0d6396e0de Users never see error text: offline/slow wording in app and web; technical details are never shown
friendlyError now shows a server detail only when it is Persian prose (no 'خطای 404', no
DRF English defaults); network errors say 'اینترنت قطع است' (navigator offline) or
'اینترنت کند است' (timeout). The native connecting screen picks the same two wordings from
ConnectivityManager (ACCESS_NETWORK_STATE). Debug build compiles; not run on a device.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:50:58 +03:30
ab02bb43b9 Native splash held while connecting; offline shows the same picture with a connecting line; web splash hidden inside the app
Inside the app the web splash is invisible with the new APK (the system splash is still on
top) and icon-only for old APKs, so there is no second picture to switch to. Offline or a
5xx on the main frame: system splash released onto the same icon (288dp drawable) with
'در حال اتصال به اینترنت…', retried every 5s. A slow-but-online load shows nothing extra.
Debug build compiles; not run on a device.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:48:06 +03:30
446cf36103 Keep the system splash until the first real screen: no blank WebView gap, no second splash
Installed APK sequence was system splash -> white WebView -> web splash -> home. The
activity now holds the system splash (core-splashscreen) until the page calls
TadrisinoAndroid.appReady(), after its first non-boot screen; released after 10s or on a
main-frame load error so a dead site shows the WebView error, not an endless splash.
Older APKs have no appReady and are unaffected. Debug build compiles; not run on a device.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:41:07 +03:30
3520279143 One static splash: app icon + name in index.html from the first frame, no companion, no timers
The first second was a blank page, then a splash with the selected companion, then the
home screen. Now index.html carries the splash (icon from the Android launcher, name,
white to match the native window background) and SplashScreen draws the same markup, so
the hand-over from HTML to React shows no change. Not run on a device.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:33:02 +03:30
16cf5c91f7 Spread retries over 1-2x Retry-After so a crowd turned away together does not return together
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:55:29 +03:30
c7407f32b9 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.
2026-10-11 01:19:34 +03:30
8a61cb25b8 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>
2026-10-10 18:51:19 +03:30
737414f246 Payment from the Android app: reach Zarinpal through pay.html so it carries our Referer
The app hands every outside URL to the phone browser, which sends no Referer, and Zarinpal's StartPay
refuses a request without our domain as Referer («دسترسی از این دامنه مجاز نمی‌باشد»). Checked against
production on a pending order: no Referer → refused; Referer https://math.tadrisino2.ir/ → forwards to
the bank (sep.shaparak.ir).

- pay.html + src/pay.ts: opened in the phone browser, forwards only to our own
  /api/payment/orders/<id>/pay link (anything else is refused), so the Referer is math.tadrisino2.ir.
  The app opens it as http:// (the WebView keeps only https on its own host); ArvanCloud moves it to
  https, and the link rides in the fragment, which never reaches a server.
- MainActivity.kt (next APK): payment pages (our subdomains, zarinpal, shaparak) stay inside the WebView
  and `paysInApp()` tells the frontend to navigate directly. Not built.

Not checked: a full payment on a real phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 18:40:10 +03:30
468f5b2d29 Calmer shop with payment choice, step-by-step phone back, sound/haptics that reach the phone, live streak flame
- Shop: subscription perks shown once (each card repeated them, «۳ سوال در روز» twice), plans as selectable
  rows with one big buy button, prices as «۷۹۸ هزار تومان», a single clear status card for subscribers.
  Checkout inside the Bazaar app offers «کارتِ بانکی» (default) or «کافه‌بازار» for packs with a SKU;
  «به‌زودی در بازار» is gone. The gateway opens in the phone browser; status refreshes on return.
- Back button: every back-able level (open sheet, pushed screen, non-home tab, shop list) owns exactly one
  history entry created right after the tap; back closes the sheet, then the screen, then returns to Home,
  then leaves the app. (A re-pushed trap is skipped by Chrome's history intervention.)
- Sound: AudioContext unlocked on the first touch (it was created after a network wait, which WebView/Safari
  keep muted); softer notes, option tick, finish fanfare, streak whoosh. Haptics through the new
  TadrisinoAndroid.vibrate bridge when the APK has it, navigator.vibrate otherwise.
- Streak: animated flame with lit / at-risk (grey) / frozen (ice) states on Home and the streak screen,
  a companion reminder when the streak is at risk or frozen, and a full-screen «روزِ پیاپی» celebration
  after the first lesson of the day (needs backend 9024c6b: streak.activeToday / lost).
- Lessons: intro/example text with inline «۱. … ۲. …» renders as a numbered list (2,250 of 4,469 texts).
- Quiz: duplicate React key on the answer bubble fixed.
- android/MainActivity.kt: vibrate bridge and mediaPlaybackRequiresUserGesture=false. Not built; needs the
  next APK from the signing owner.

Checked in headless Chrome against the local backend (shop as a subscriber with the Bazaar bridge stubbed,
back sequences, quiz → streak celebration, frozen state). Not checked on a real phone or in the APK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 18:27:24 +03:30
eafee76a93 Smooth screen transitions: new screen waits hidden until its data arrives, no half-built flash
- Navigator stage: the next screen mounts hidden under the current one and is revealed only when its
  first data has arrived (ScreenGate held by useAsync/useGateHold), at most 450 ms; a thin top bar shows
  after 120 ms of waiting. Push slides from the left, back from the right, tabs only fade.
- Session cache (stale-while-revalidate) for screen GETs: revisited screens draw instantly.
- Home map and stats are fetched during the splash, so the first screen arrives complete.
- Entrance pop-ins (mascot, islands, lesson cards) run on first view only, not on every revisit.
- Shop status card shows placeholders instead of a wrong «نداری» while loading.
- Re-tapping the open tab scrolls to top instead of reloading; sheets close with a swipe down.
- Quiz: «۱ از ۴» instead of «۱/۴» (read as a fraction), varied praise, «N تا پشتِ هم درست»,
  growth-mindset copy after a weak round, zero-reward tiles hidden.
- Streak: a concrete next step when the streak is 0 or today is still empty.

Checked in headless Chrome against the local backend (frame captures at 150 ms and 700 ms latency,
rapid tab tapping, full quiz run). Not checked on a real phone or iOS Safari.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:55:47 +03:30
19ca3d7de1 Merge personal-fork Android build commit; keep MH-W90's signing flow from upstream (owner of Android signing)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:41:20 +03:30
f74b6ad279 Companions: each wears its own Greek letter badge (Δ, π, ω); Tedi renamed Omega (ω)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:40:52 +03:30
a8a5351862 Subscription store, paywall on locked units, profile subscription bar, Tadrisino branding
- Store opens with two choices (subscription / question packs); 3- and 6-month cards
  show per-month price and saving.
- Units that need a subscription show a crown badge and open the store; a 402 from
  the quiz goes there too. «ادامه بده» skips locked units.
- Profile: subscription card with an animated days-left bar (planDurationDays).
- BrandMark (SVG) on splash, home header and footer; copyright footer; floating math
  glyphs on splash. CSS transform/opacity only; honours reduced motion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 01:19:09 +03:30
8879b42b1c Fix: first screen's looping animations (mascot bob, island ring, start button) never ran until a tab switch; AnimatePresence initial={false} leaked into children 2026-10-08 18:12:35 +03:30
6930c83e07 Quiz: selecting an option only highlights it blue; the answer is submitted by a «بررسی» button and green/red is shown after, then «ادامه» 2026-10-08 18:04:02 +03:30
d0a2822658 Fine-tune: bigger invisible tap areas on header chips, press feedback on tabs and chips, touch-action manipulation
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:59:12 +03:30
50f6ade9f8 Replace keyboard emojis with the app's own lucide icons (quiz reactions, +XP, welcome hand, party popper, streak flame)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:47:05 +03:30
22fc870176 Smoother tab switch (no dark flash), full welcome sentence, chapter banner sticky and chip centered, floating math glyphs in headers, quiz reaction emojis
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:38:28 +03:30
54b872026d Lessons and packages headers: back to the owner's preferred colors (green, coral), exactly as before
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:31:01 +03:30
54bd865835 Colors keep their role: headers/banners darken to readable contrast (same hue), lessons uses the teal token, packages use indigo; +XP pop, streak milestone card, lazy confetti
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:26:07 +03:30
dc1fb15a55 Lessons tab: chapters closed by default, no quiz-button flash while lessons load, state kept on back, continue card and progress
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 16:00:49 +03:30
09ca1a270a Lessons tab replaces the map tab: chapter > island > lesson list with the island quiz beside it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 15:46:38 +03:30
838049c469 Island sheet: quiz button stays primary, lessons button gets a neon-green outline and clearer wording
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 14:37:01 +03:30
5327fed1dd Android: reproducible signed build (keystore and Bazaar RSA key read from outside the repo), Gradle wrapper, build-apk.sh
First time the project has been built: debug and release both produce an APK.
Release refuses to build without keystore or RSA key.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 14:05:48 +03:30
2fbcf6e6e0 fixes 2026-10-07 19:04:45 +03:30
5b98c054ea Merge branch 'main' of https://gitea.tadrisino2.ir/Tadrisino/MathApp_frontend 2026-10-07 18:30:25 +03:30
c64b3939ba changes 2026-10-07 18:29:03 +03:30
b11016c6ea LoadingState appears only after 300ms: fast API answers no longer flash the loader (packs screen and every other data screen)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 17:05:52 +03:30
ec1bfc721d Page switch: show the loading box only after 300ms, not for the 1-2 frames lazy() always takes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 17:02:18 +03:30
5b211c2786 Lesson screen: render fixed lesson figures under intro, example and steps
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 16:26:13 +03:30
0db6526933 Remove every guest remnant: profile badge, register flow, delete-by-word
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 18:20:10 +03:30
af2e1f57dd Login: no guest option; every user signs in with a phone number
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 17:38:18 +03:30
1025321b5f Android WebView: bridge only on our own https page, external schemes blocked, file access off; payment URL must be http(s)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 09:44:47 +03:30
e850f6dfbd Payment return decided by the server only; encoded path ids; no sign-out on a two-tab refresh race
- ?orderId must be numeric; every id in an API path is encodeURIComponent'd.
- The payment sheet shows success only from GET /payment/orders/{id}; a failed lookup says the
  status is unknown instead of "paid".
- A 401 from /auth/refresh is not a sign-out when another tab already stored a rotated token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 04:52:25 +03:30
647ccf2c88 copy: no companion count in splash/onboarding (said three, there are four)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 03:32:57 +03:30
f8cf86e4e8 UI readability pass and Bazaar in-app purchase bridge
- muted text #9e9ec0 -> #727299 (contrast 2.6 -> 4.5 on white); tab labels 11px
- map: chapter titles wrap to two lines instead of being cut off
- reduced-motion respected (MotionConfig + CSS), focus-visible ring,
  no pull-to-refresh, short vibration on correct/wrong answers
- src/lib/bazaar.ts: bridge to the Android WebView wrapper; checkout uses
  Bazaar IAP only inside that app, Zarinpal everywhere else (unchanged)
- android/: WebView wrapper with Poolakey; written but never built

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 19:13:12 +03:30
adecf25486 http: only a 401 from /auth/refresh signs the user out
A 429, 5xx or network failure during token refresh used to sign the user
out. Behind one school Wi-Fi or mobile CGNAT the backend's per-IP refresh
limit was hit, and phone users were sent back to SMS login. Now those cases
keep the session and surface as a retryable error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 16:05:59 +03:30
33f5f8d009 no-hearts screen: live countdown, filling heart, auto-continue; refunded/canceled invoices
- The wait screen counts down to the next heart second by second, fills
  the first heart as time passes, shows when all five are back, and asks
  the server when the time comes; 'continue' reloads the quiz. Packs and
  'ask Pi meanwhile' (asking costs no hearts) are offered as ways on.
- The server now also refuses to finish a lesson/island without hearts
  and answers (403 no_hearts); the quiz shows the same screen then.
- Invoices know the two admin-set statuses, canceled and refunded.
- Account deletion waits for all six digits of the SMS code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:42:14 +03:30
befaaeb7bb packs: no per-question price on the card
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 12:27:38 +03:30