import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../core/permissions.dart'; /// Injected in `main()` once SharedPreferences has loaded. final prefsProvider = Provider( (ref) => throw UnimplementedError('prefsProvider must be overridden'), ); class SessionState { const SessionState({ this.phone, this.signedIn = false, this.locationGranted = false, this.notificationsGranted = false, this.cameraGranted = false, }); 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({ String? phone, bool? signedIn, bool? locationGranted, bool? notificationsGranted, bool? cameraGranted, }) => SessionState( 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 _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( phone: p.getString(_kPhone), signedIn: p.getBool(_kSignedIn) ?? false, locationGranted: p.getBool(_kLocation) ?? false, notificationsGranted: p.getBool(_kNotifications) ?? false, cameraGranted: p.getBool(_kCamera) ?? false, ); } /// 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; } /// Asks the OS, then records what it actually said. /// /// The stored flag is a cache for the UI, never the source of truth — the /// user can revoke a permission in system settings without the app running, /// so [refresh] re-reads the real status whenever a screen needs it. Future request(AppPermission permission) async { final granted = await ref .read(permissionServiceProvider) .request(permission); _record(permission, granted); return granted; } /// Re-reads all three from the OS. Called when a screen that shows their /// state appears, so a change made in system settings is picked up. Future refresh() async { final service = ref.read(permissionServiceProvider); for (final permission in AppPermission.values) { _record(permission, await service.isGranted(permission)); } } void _record(AppPermission permission, bool granted) { switch (permission) { case AppPermission.location: _prefs.setBool(_kLocation, granted); state = state.copyWith(locationGranted: granted); case AppPermission.notifications: _prefs.setBool(_kNotifications, granted); state = state.copyWith(notificationsGranted: granted); case AppPermission.camera: _prefs.setBool(_kCamera, granted); state = state.copyWith(cameraGranted: granted); } } Future signOut() async { await _prefs.remove(_kSignedIn); await _prefs.remove(_kPhone); state = state.copyWith(signedIn: false, phone: ''); } } final sessionProvider = NotifierProvider( SessionController.new, );