import 'dart:async'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../core/api/api_client_provider.dart'; import '../../core/auth/auth_notifier.dart'; part 'usp_seen_provider.g.dart'; const _kPrefsKey = 'usp_seen'; /// One-time gate for the S5b USP onboarding screen (Phase 4, 2026-05-12). /// /// Local SharedPreferences flag is the runtime source of truth. When an /// authenticated session is hydrated (bootstrap, OTP verify, social, name /// patch), the server-side `customers.usp_seen` value is OR-merged into the /// local flag — true wins. When the user dismisses the USP screen and an /// account exists, the local true is best-effort propagated to the server via /// `POST /api/client/auth/usp-seen`. @Riverpod(keepAlive: true) class UspSeen extends _$UspSeen { @override FutureOr build() async { // Watch auth state; whenever an auth-bearing profile arrives, OR-merge the // server flag into local. Disposed/recreated automatically with the // notifier so no manual cleanup needed. ref.listen>(authProvider, (prev, next) { final profile = _profileOf(next.valueOrNull); if (profile != null) { unawaited(_hydrateFromProfile(profile)); } }); final prefs = await SharedPreferences.getInstance(); return prefs.getBool(_kPrefsKey) ?? false; } Map? _profileOf(AuthData? data) => switch (data) { AuthAuthenticatedData d => d.profile, AuthAnonymousData d => d.profile, AuthForceRegisterData d => d.profile, AuthNeedsDisplayNameData d => d.profile, _ => null, }; Future _hydrateFromProfile(Map profile) async { final serverSeen = profile['usp_seen'] as bool? ?? false; if (!serverSeen) return; if ((state.valueOrNull ?? false) == true) return; final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_kPrefsKey, true); state = const AsyncData(true); } /// Mark seen locally; if an account exists, also persist to DB best-effort. /// Safe to call when already seen — no-ops out of the network hit if local /// is already true AND no account exists yet. Future markSeen() async { final alreadySeen = (state.valueOrNull ?? false) == true; if (!alreadySeen) { final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_kPrefsKey, true); state = const AsyncData(true); } final authData = ref.read(authProvider).valueOrNull; final hasAccount = authData is AuthAuthenticatedData || authData is AuthAnonymousData || authData is AuthForceRegisterData || authData is AuthNeedsDisplayNameData; if (!hasAccount) return; try { await ref.read(apiClientProvider).post('/api/client/auth/usp-seen'); } catch (_) { // Local stays true; next markSeen call (or a successful login on a // different device) will re-attempt the DB write. } } }