Commit Graph

137 Commits

Author SHA1 Message Date
airkjw 412cc08167 fix(l10n): localize line-bomb row/column chooser labels
The line-bomb axis chooser hardcoded Korean 가로/세로; route them through
new boosterLineRow/boosterLineCol keys (EN: Row/Column).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 19:27:41 +09:00
airkjw 1a028b9852 feat(ui): 7-day daily-reward popup on home
Presentational DailyRewardSheet (7 cells, today highlighted, reward icons
from the calendar table, claim + watch-ad-2x buttons). HomeScreen becomes a
ConsumerStatefulWidget that shows it once per mount via a post-frame guard;
the 2x path grants the doubled reward only if the rewarded ad was earned,
else the base reward. Guards the throwing saveRepositoryProvider default so
a repo-less mount is a no-op.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 19:26:43 +09:00
airkjw b8bfa00196 feat(ui): rewarded-ad grant for an empty booster
Tapping an empty booster opens the get-one dialog; confirming watches a
rewarded ad and, on earn, grants +1 of that booster and logs booster_granted
(source: ad). Covered by a widget test using an uninitialized AdService whose
showRewarded() resolves true.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 12:41:27 +09:00
airkjw 1ba30028b5 feat(ui): booster bar targeting in the game screen
Mount BoosterBar below the tray (only while playing), guarded so legacy
GameScreen tests without a SaveRepository keep passing. Tapping a booster
arms targeting: shuffle applies immediately; hammer/line-bomb arm a board
tap (hammer clears a cell, line-bomb opens a row/column chooser). An empty
booster opens a get-one dialog (ad grant lands in Task 15).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 12:40:13 +09:00
airkjw a04bb3b847 feat(ui): presentational booster bar 2026-06-18 12:28:25 +09:00
airkjw 0517fabdbb feat(l10n): booster + daily-reward strings (EN/KO)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 12:26:31 +09:00
airkjw d0a2be15ba feat(analytics): booster + daily-reward events
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 12:24:02 +09:00
airkjw fa4247cd9b feat(state): DailyRewardNotifier with injectable clock 2026-06-18 12:21:02 +09:00
airkjw ba4d4a662b feat(state): coordinate booster use with inventory in the session 2026-06-18 12:17:30 +09:00
airkjw 638a177fbb feat(state): BoosterInventoryNotifier 2026-06-18 12:14:34 +09:00
airkjw c185bd0886 feat(daily): pure 7-day login-calendar logic + reward table 2026-06-18 12:11:15 +09:00
airkjw 544a2b8be4 feat(save): persist daily-reward claim state 2026-06-18 12:09:06 +09:00
airkjw 221ea8346e feat(save): persist booster inventory 2026-06-18 12:08:17 +09:00
airkjw 6592b44387 feat(model): BoosterType enum 2026-06-18 12:07:19 +09:00
airkjw e7cd079a5d feat(engine): line-bomb booster clears a row or column 2026-06-18 12:05:14 +09:00
airkjw bbf8cf3f08 feat(engine): shuffle booster re-deals the tray 2026-06-18 12:04:00 +09:00
airkjw 5aee503c09 feat(engine): hammer booster removes one cell, no scoring 2026-06-18 12:02:55 +09:00
airkjw 4cda34f0b7 docs(plan): boosters & daily reward implementation plan
17 bite-sized TDD tasks: engine booster ops -> inventory/daily persistence
-> pure daily-calendar -> notifiers -> analytics/l10n -> booster bar +
targeting -> rewarded-ad grants -> daily popup -> integration. Verification
is flutter test + analyze only (builds are owner-commanded).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 11:57:51 +09:00
airkjw 9f1e0d2cd5 docs(spec): boosters & daily reward design
Brainstormed design for a lightweight booster economy (hammer/shuffle/
line-bomb) earned via a 7-day login calendar and rewarded ads, used in
a stage via a booster bar. Boosters mutate the grid only — no move cost,
no score/combo, no objective credit — so stage balance is preserved
while they can rescue a dead board. Approved by owner; next: impl plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 11:50:54 +09:00
airkjw 02021b540e feat(crashlytics): report release crashes to Firebase Crashlytics
Routes Flutter framework errors (FlutterError.onError) and uncaught
async/platform errors (PlatformDispatcher.onError) to Crashlytics, but
only in release builds — debug keeps its red error screens and console
traces, and collection is disabled via setCrashlyticsCollectionEnabled
(kReleaseMode) so development crashes never reach the dashboard. Adds
the Crashlytics Gradle plugin alongside the existing google-services
FlutterFire config. iOS dSYM upload run script still to be added in
Xcode (symbolication only; crash capture works without it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 11:17:27 +09:00
airkjw cec4c3e427 feat(review): request a store review after a 3-star win, once
Adds an in-app review prompt gated by ReviewPromptPolicy: only after a
3-star stage win, once the player has cleared >=5 stages, at most once
ever (persisted reviewRequested flag). ReviewService swallows all
failures and only burns the one-shot when the store actually shows the
sheet, so an unavailable store retries on a later win. StoreReviewer
wraps in_app_review behind a Reviewer seam so unit tests skip platform
channels. 13 new tests; full suite 194 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 11:13:55 +09:00
airkjw 395e4a189b ios: fix ITMS-91064 — set NSPrivacyTracking=false, drop empty domains
Apple rejected build 2: NSPrivacyTracking was true with an empty
NSPrivacyTrackingDomains. Listing AdMob's ad-serving domains would block
ads before ATT consent (same domains serve non-personalized ads), so we
set NSPrivacyTracking=false and remove the domains key — mirroring
Google's own AdMob SDK manifest (DeviceID data type stays tracking=true,
no top-level tracking/domains). Bump to build 3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 13:06:16 +09:00
airkjw 4df30c3f40 test(store): add 13-inch iPad screenshots (universal app requires them)
App ships universal (iPhone+iPad), so the App Store requires iPad
screenshots. Render the 3 store screens at 2048x2732 (iPad 12.9"/13").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 12:20:33 +09:00
airkjw c78dea71e0 docs(store): remove IAP references (ad-only launch)
Drop the In-App Purchases section from the privacy policy and the
'Remove Ads' bullet from the store description, since the app now ships
ad-supported only. Renumber policy sections; bump policy date to Jun 15.
Redeployed the policy to block-seasons.web.app.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 12:10:36 +09:00
airkjw 8b5bbd9531 feat: ship ad-supported only — gate Remove Ads IAP behind kIapEnabled=false
The developer is an individual without a Korean business registration, so
the App Store / Play paid-apps (merchant) agreements can't be completed.
Hide the Remove Ads + Restore tiles and skip IAP init; ads always show.
AdMob revenue is independent of those agreements. Reversible: flip
kIapEnabled to re-enable once a merchant agreement exists. Bump to build 2;
drop the now-unused IAP review-screenshot generator.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 11:55:48 +09:00
airkjw e9d7f7cef6 test(store): match IAP review screenshot price to $1.99 (actual price) 2026-06-15 10:48:51 +09:00
airkjw 3e136dc288 test(store): generate IAP review screenshot for App Store (remove_ads)
Headless render of the Settings screen showing the Remove Ads purchase
point + price + Restore Purchases, for the App Store IAP review-screenshot
requirement. Forces a desktop target platform so the real IapService can
be constructed without the store plugin making async channel calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:46:54 +09:00
airkjw ea01da9b62 ios: declare ITSAppUsesNonExemptEncryption=false (HTTPS-only, export-exempt)
App uses only standard OS-provided encryption (HTTPS via Firebase/AdMob),
which qualifies for export compliance exemption. Baking the key into
Info.plist avoids the per-build encryption prompt on future uploads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:21:45 +09:00
airkjw 8e3ed2951d docs(store): add real landing page for web.app root (support/marketing URL)
Replaces the default Firebase placeholder so the App Store support and
marketing URLs (https://block-seasons.web.app) show a real Block Seasons
page. Deployed to Firebase Hosting root as index.html.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:11:16 +09:00
airkjw b79960c949 docs(store): host privacy policy + app-ads.txt on Firebase, fill submission URLs
Deployed privacy-policy.html and app-ads.txt to block-seasons.web.app
(gru.farm root is an external site builder, not the NAS, so app-ads.txt
could not live there). Updated phase7 guide with the live URLs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:55:21 +09:00
airkjw 08372995bc docs(store): generate store screenshots (iOS 6.7" + Android phone)
Headless screenshot harness renders Home, Gameplay, and a denser
score-chase board to PNGs at 1290x2796 (iOS) and 1080x1920 (Android),
in English with a real font loaded. Captures the boundary layer at 3x
inside runAsync (the only way it completes under the test binding).
Season-map cut deferred — its scrollable build stalls headlessly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 21:20:37 +09:00
airkjw 30572e3912 docs(store): self-serve submission guide + privacy policy + Play 512 icon
Rewrote the Phase 7 guide to reflect completed Play setup (app, AAB,
payments profile, remove_ads) and lay out remaining ordered steps for
both stores with copy/answers inline. Added a hostable bilingual
privacy policy page and a 512x512 Play store icon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:49:41 +09:00
airkjw c7c558cb96 release: drop test-only build tag from settings footer
The '(build 4)' marker was a device build-delivery debugging aid, now
resolved. Public release shows a clean version string.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:47:33 +09:00
airkjw 7c7c7afad0 content: regenerate Season 1 with a gentle onboarding difficulty curve
Generator: raise the early bot-win-rate floor to ~0.97 (near-guaranteed first
stages), widen the early move-buffer margin, and delay score-chase/line-sprint
objectives until stage 9/13 so the opening stretch is pure clear-a-gem. Result:
stage 1 now 16 moves (was 11), stages 1-9 all simple gems at 90-99% bot win.
Preserves theme.bgm=season_001; manifest SHA regenerated. Settings tag -> build 4.
2026-06-14 11:02:29 +09:00
airkjw 1682578501 feat: start at Season 1, add menu/season_001 BGM tracks, settings build tag
- activeSeason now returns the first season (Season 1 'First Bloom') so a new
  player starts at spring instead of the newest season.
- Bundle the owner-picked CC0 tracks menu.mp3 + season_001.mp3 (BGM now audible).
- Settings footer shows 'v1.0.0 (build 3)' so test builds are identifiable.
180 tests green, analyze clean.
2026-06-14 10:32:12 +09:00
airkjw 8947221b27 feat(audio): looping per-season background music system
MusicService (looping audioplayers player, independent of SFX) driven by the
active season theme's new 'bgm' key; switches track on season change, pauses on
app background, all failures swallowed. Separate Music on/off toggle in Settings
(persisted, independent of SFX). Season packs carry bgm keys (menu/season_001/
season_002), manifest regenerated. Assets slot assets/audio/bgm/ ready — drop in
menu.mp3/season_001.mp3/season_002.mp3 (CC0) and it plays; silent until then.
180 tests green, analyze clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-14 09:31:10 +09:00
airkjw 2310aabdb9 fix(ui): move game-screen close button to its own top row (was overlapping HUD)
On device the floating top-left X (48px IconButton) overlapped the HUD moves
chip. Move it into the column flow above the HUD, left-aligned, so it never
collides. Found via on-device test build.
2026-06-14 09:02:01 +09:00
airkjw a9380a7b27 fix(android): launcher label 'Block Seasons' (was default 'block_seasons') 2026-06-13 23:14:12 +09:00
airkjw 40abc26f5d build(release): Android signing, iOS privacy manifest, store assets (Phase 7)
- Android: release keystore signing wired via gitignored key.properties (falls
  back to debug when absent). Verified: signed AAB built (signer CN=Block Seasons).
- iOS: app PrivacyInfo.xcprivacy (ATT tracking flag, device-id/usage data types,
  UserDefaults+FileTimestamp required-reason APIs) registered in the Runner target.
- Store: app-ads.txt (pub-5605900229781491), EN/KO listing copy, owner submission
  guide (privacy labels, app-ads hosting, upload/submit steps).
Secrets (keystore, key.properties) are gitignored — owner backs them up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 22:49:43 +09:00
airkjw 734c8a4cf7 feat(ads): wire real AdMob ids (Phase 5 finalize)
Replaces the TODO_REAL_* ad-unit placeholders with the owner's real AdMob
console ids (publisher pub-5605900229781491) and the native AdMob app ids
(iOS ~8397095848, Android ~8257495040) in place of the Google sample ids.
Debug builds still serve Google test ads via kDebugMode; release builds now
use the real units.
2026-06-13 19:05:10 +09:00
airkjw b31228d987 docs: AdMob + IAP real-id setup guide for owner (Phase 5 finalize)
Step-by-step owner guide to register the AdMob apps + 6 ad units, create the
remove_ads IAP in both stores, and the exact values to send back so the
TODO_REAL_* ad ids and native AdMob app ids get swapped in.
2026-06-13 18:31:20 +09:00
airkjw 219da8677a Merge Phase 6: localization finalize + brand icon + juice + store assets
Brand app icon (navy 2x2 glossy block mark, vector-drawn, no AI image) applied
on iOS+Android via flutter_launcher_icons. Sound & vibration toggle (themed
Settings). Juice: button press feedback + fade screen transitions. Play feature
graphic (Titan One wordmark). l10n EN/KO verified (no hardcoded strings, key
parity). 176 tests green; analyze clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 18:26:48 +09:00
airkjw ac49168c02 test(juice): PressableScale doesn't swallow the inner button tap 2026-06-13 18:26:20 +09:00
airkjw 23f90d5b89 style: drop unnecessary dart:ui import in app_icon_painter 2026-06-13 18:25:13 +09:00
airkjw bc62127d1a docs: Phase 6 verified — brand app icon applied on device build 2026-06-13 18:24:36 +09:00
airkjw b0839aba2a fix(store): render feature-graphic wordmark with real font (Titan One, OFL)
The flutter_test default font draws every glyph as a box, so the wordmark was
unreadable. Load the OFL Titan One TTF via FontLoader in the generator and
render 'Block Seasons' + tagline with it; fit the text within the 1024 width.
Font used only by the asset generator, not bundled in the app.
2026-06-13 18:20:11 +09:00
airkjw 7fe2bc2776 feat(store): Play feature graphic (1024x500) generator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:15:11 +09:00
airkjw 536807e7c8 feat(juice): button press feedback + fade screen transitions
Adds PressableScale (0.94 squish on tap-down) around the Adventure
FilledButton, Classic OutlinedButton, and each season-map stage node.
Replaces all in-app MaterialPageRoute pushes with a gentle fade+scale
PageRouteBuilder (320ms in, 240ms out) via a new fadeRoute helper.
2026-06-13 18:12:07 +09:00
airkjw e1098949be build(brand): generate iOS/Android launcher icons from brand mark
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:08:13 +09:00
airkjw 099ced377d feat(brand): app icon painter + generated 1024px icon PNGs
Introduces AppIconMark (navy gradient field + 2×2 glossy blocks reusing
paintGlossyTile) and a testWidgets generator that writes icon.png,
icon_background.png, and icon_foreground.png to assets/icon/ for the
upcoming flutter_launcher_icons task.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 18:06:18 +09:00