OTP screen rewrite: 6 rounded boxes, auto-advance focus, auto-submit on the
6th digit, hardware-backspace on empty boxes (intercepted via Focus.onKeyEvent
since TextField.onChanged doesn't fire on already-empty input), resend
cooldown sourced from /api/shared/config/otp, and an inline error message
under the boxes instead of a SnackBar.
Several bugs fixed inline that surfaced during testing:
- ref.listen inside build() accumulates listeners on every rebuild — the
resend countdown's per-second setState was piling up duplicate listeners
so one error triggered N callback fires. Moved to ref.listenManual in
initState; subscription disposed in dispose().
- RouterNotifier was calling notifyListeners() on every auth state change
including AsyncError, which rebuilt the Navigator/Scaffold mid-snackbar
and visually duplicated the error toast. Now skips AsyncError and
same-data-variant transitions.
- ScaffoldMessenger.showSnackBar from a Riverpod listener callback could
still render twice even with hideCurrentSnackBar — replaced with an
inline error widget to sidestep the snackbar machinery entirely.
- register_screen now uses context.go instead of context.push for the
OTP route, so re-submitting the phone form doesn't stack multiple
OtpScreen instances with active subscriptions.
Lockout UX: AuthErrorInfo wraps the error message + code + retry_after_seconds
parsed from the backend's structured error response. On rate-limit codes
(OTP_COOLDOWN, OTP_RATE_LIMIT_PHONE, OTP_RATE_LIMIT_IP), the OTP screen
extends "Kirim ulang kode" cooldown to match the server's wait, and the
register screen disables "Kirim OTP" with a "Coba lagi dalam …" countdown.
formatCountdown() in core/constants.dart renders Xd under 90 seconds and
Xm Yd above (clearer than raw seconds for long lockouts).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add phase3.1 requirement and implementation plan docs
- Add Riverpod dependencies to both client_app and mitra_app
- Wrap both app roots with ProviderScope
- Migrate client_app AuthBloc → AuthNotifier (@riverpod annotation)
- Migrate client_app ChatOpeningBloc → chatPricingProvider (FutureProvider)
- Update router to use Riverpod-based auth state for redirects
- Update all auth screens (display name, register, OTP, force register)
- Update home screen and pricing bottom sheet
- Add android:usesCleartextTraffic for dev HTTP access on both apps
- mitra_app prepared with ProviderScope + ApiClient provider (blocs next)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>