import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// Injected in `main()` once SharedPreferences has loaded. final prefsProvider = Provider( (ref) => throw UnimplementedError('prefsProvider must be overridden'), ); class SessionState { const SessionState({ this.onboardingSeen = false, this.phone, this.signedIn = false, this.locationGranted = false, this.notificationsGranted = false, this.cameraGranted = false, }); final bool onboardingSeen; final String? phone; final bool signedIn; final bool locationGranted; final bool notificationsGranted; final bool cameraGranted; /// The three-step permission run shown right after verification. bool get permissionsDone => locationGranted && notificationsGranted && cameraGranted; SessionState copyWith({ bool? onboardingSeen, String? phone, bool? signedIn, bool? locationGranted, bool? notificationsGranted, bool? cameraGranted, }) => SessionState( onboardingSeen: onboardingSeen ?? this.onboardingSeen, phone: phone ?? this.phone, signedIn: signedIn ?? this.signedIn, locationGranted: locationGranted ?? this.locationGranted, notificationsGranted: notificationsGranted ?? this.notificationsGranted, cameraGranted: cameraGranted ?? this.cameraGranted, ); } class SessionController extends Notifier { static const _kOnboarding = 'onboarding_seen'; static const _kPhone = 'phone'; static const _kSignedIn = 'signed_in'; static const _kLocation = 'perm_location'; static const _kNotifications = 'perm_notifications'; static const _kCamera = 'perm_camera'; SharedPreferences get _prefs => ref.read(prefsProvider); @override SessionState build() { final p = _prefs; return SessionState( onboardingSeen: p.getBool(_kOnboarding) ?? false, phone: p.getString(_kPhone), signedIn: p.getBool(_kSignedIn) ?? false, locationGranted: p.getBool(_kLocation) ?? false, notificationsGranted: p.getBool(_kNotifications) ?? false, cameraGranted: p.getBool(_kCamera) ?? false, ); } void completeOnboarding() { _prefs.setBool(_kOnboarding, true); state = state.copyWith(onboardingSeen: true); } /// The demo backend accepts any 10-digit number and always sends `123456`. static const demoCode = '123456'; String sendCode(String phone) { _prefs.setString(_kPhone, phone); state = state.copyWith(phone: phone); return demoCode; } bool verifyCode(String code) { if (code != demoCode) return false; _prefs.setBool(_kSignedIn, true); state = state.copyWith(signedIn: true); return true; } void grantLocation() { _prefs.setBool(_kLocation, true); state = state.copyWith(locationGranted: true); } void grantNotifications() { _prefs.setBool(_kNotifications, true); state = state.copyWith(notificationsGranted: true); } void grantCamera() { _prefs.setBool(_kCamera, true); state = state.copyWith(cameraGranted: true); } Future signOut() async { await _prefs.remove(_kSignedIn); await _prefs.remove(_kPhone); state = state.copyWith(signedIn: false, phone: ''); } } final sessionProvider = NotifierProvider( SessionController.new, );