super-productivity/android/app
Johannes Millan b692eb72ce
feat(rate-dialog): calm, recurring, win-timed store rating prompt (#8704)
* feat(rate-dialog): native store review + prompt after a productive win

Baseline of PR #8680 (squashed) so review improvements land as a separate,
cherry-pickable commit on top.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(rate-dialog): calm banner, feedback cooldown, delayed win prompt

Review + UX follow-ups on top of PR #8680:
- Fix (correctness): re-check full eligibility at prompt fire-time, not just
  opt-out, so a crash/data-damage recorded after arming still suppresses it.
- Fix: iOS advances the rating cadence only once the native request resolves;
  a reject leaves eligibility intact instead of burning a lifetime prompt.
- Fix: Android review-flow failure now logs and abandons instead of opening the
  Play Store unprompted (the trigger is an automatic win, not a user tap).
- UX: web/electron/F-Droid now show a calm, non-modal banner that opens the full
  rate/feedback dialog on request, rather than a modal shoved in mid-flow.
- UX: 'give feedback' no longer permanently opts out — it starts a long cooldown
  (~90 app-start days) so an engaged user can still be asked once more later.
- UX: the win prompt fires a few seconds after the completion, not on the tap.
- UX: dedicated 'Send feedback' entry in the Help menu (GitHub Discussions).
- UX: show the maintainer email as selectable text (mailto: dead-end fallback).
- Refactor: move selectTodayProgress into work-context.selectors (colocation);
  idempotency guard on the win subscription.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(rate-dialog): import WIN_PROMPT_DELAY_MS in spec instead of mirroring it

Export the delay constant from the service and reference it in the spec so the
test can't silently drift from the real value. Found by a review pass.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(rate-dialog): recurring cadence, version-age gate, GitHub star CTA

Growth-focused follow-ups (review found the lifetime cap starves review
recency/velocity, which is what stores rank on):

- Recurring cadence: after the fixed onboarding tiers (32/96 app-start days) the
  prompt no longer stops forever — it recurs every ~180 app-start days (~6+
  months of real use), well inside Apple's ~3/365 allowance and Play's own
  quota. Still calm: win-timed, opt-out/crash/feedback-gated, OS-throttled.
- Version-age gate: hold the prompt for 7 days after the app version changes so a
  fresh (possibly regressed) release isn't asked to be rated immediately. Tracked
  via two device-local LS keys, checked at arm time and fire time.
- Play tier-burn is now only a deferral, not a lifetime loss (recurring cadence).
- Web/Electron CTA: 'Star us on GitHub' (the desktop-distribution equivalent of
  store ranking) instead of the near-zero-conversion how-to-rate doc.

Tests cover recurrence at + beyond the last tier, the version-age gate, and the
updated cadence expectations. 60/60 green; tsc clean.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(rate-dialog): register iOS plugin, drop version-age gate, cut per-tick churn

Second multi-review pass (7 reviewers):

- CRITICAL (Codex, verified): the iOS StoreReview Capacitor plugin was never
  registered — CustomViewController registers WebDavHttpPlugin but not
  StoreReviewPlugin, so requestReview() rejected and the native App Store review
  card never showed. Register it via registerPluginInstance.
- Remove the version-age gate (4 reviewers): getAppVersionStr() changes every
  release and the app ships ~weekly, so the 7-day window kept re-arming on every
  update (web SW reload / Electron auto-update) and near-permanently suppressed
  the prompt on desktop — the platform where the GitHub-star intent matters most.
  It also duplicated the 30-day crash gate, which already covers crashing
  regressions. Drops 2 LS keys, a constant, and ~40 LOC.
- Perf: add distinctUntilChanged on the armed win stream so the 1s time-tracking
  tick (new {done,total} with identical numbers) no longer re-runs scan/filter
  every second for the whole session.

59/59 specs green; tsc clean. Kotlin/Swift can't be compiled here — the iOS
registration needs an on-device/simulator smoke test.

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(i18n): translate new rate-prompt strings into all locales

Add BANNER_ACTION, BTN_STAR_GITHUB (F.D_RATE) and SEND_FEEDBACK (MH.HM) to all
27 non-en locale files — previously they existed only in en.json and fell back
to English for everyone else. SEND_FEEDBACK reuses each locale's existing
feedback wording for consistency; GitHub is kept untranslated.

Deliberately edits locale files beyond the usual en-only workflow, per request.
Best-effort translations — a native check on the less-common languages is
welcome.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-02 13:52:10 +02:00
..
src feat(rate-dialog): calm, recurring, win-timed store rating prompt (#8704) 2026-07-02 13:52:10 +02:00
.gitignore ____MERGING SUPER PRODUCTIVITY ANDROID ____ 2024-10-11 14:11:23 +02:00
app_config.properties ____MERGING SUPER PRODUCTIVITY ANDROID ____ 2024-10-11 14:11:23 +02:00
build.gradle feat(rate-dialog): calm, recurring, win-timed store rating prompt (#8704) 2026-07-02 13:52:10 +02:00
capacitor.build.gradle feat(android): migrate edge-to-edge to built-in SystemBars (#8543) 2026-06-22 16:07:06 +02:00
config.gradle ____MERGING SUPER PRODUCTIVITY ANDROID ____ 2024-10-11 14:11:23 +02:00
google.properties ____MERGING SUPER PRODUCTIVITY ANDROID ____ 2024-10-11 14:11:23 +02:00
proguard-rules.pro feat(droid): background sync notifications for android 2026-03-20 10:24:02 +01:00