import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../core/state.dart'; import 'models.dart'; final _log = Logger('app.state'); // Override this to alter the set of supported apps. final supportedAppsProvider = Provider>((ref) => Application.values); // Default implementation is always focused, override with platform specific version. final windowStateProvider = Provider( (ref) => WindowState(focused: true, visible: true, active: true), ); final themeModeProvider = StateNotifierProvider( (ref) => ThemeModeNotifier(ref.watch(prefProvider))); class ThemeModeNotifier extends StateNotifier { static const String _key = 'APP_STATE_THEME'; final SharedPreferences _prefs; ThemeModeNotifier(this._prefs) : super(_fromName(_prefs.getString(_key))); void setThemeMode(ThemeMode mode) { _log.config('Set theme to $mode'); state = mode; _prefs.setString(_key, mode.name); } static ThemeMode _fromName(String? name) { switch (name) { case 'light': return ThemeMode.light; case 'dark': return ThemeMode.dark; default: return ThemeMode.system; } } } // Override with platform implementation final attachedDevicesProvider = StateNotifierProvider>( (ref) => AttachedDevicesNotifier([]), ); class AttachedDevicesNotifier extends StateNotifier> { AttachedDevicesNotifier(List state) : super(state); /// Force a refresh of all device data. void refresh() {} } // Override with platform implementation final currentDeviceDataProvider = Provider( (ref) => throw UnimplementedError(), ); // Override with platform implementation final currentDeviceProvider = StateNotifierProvider( (ref) => throw UnimplementedError()); abstract class CurrentDeviceNotifier extends StateNotifier { CurrentDeviceNotifier(DeviceNode? state) : super(state); setCurrentDevice(DeviceNode device); } final currentAppProvider = StateNotifierProvider((ref) { final notifier = CurrentAppNotifier(ref.watch(supportedAppsProvider)); ref.listen(currentDeviceDataProvider, (_, data) { notifier._notifyDeviceChanged(data); }, fireImmediately: true); return notifier; }); class CurrentAppNotifier extends StateNotifier { final List _supportedApps; CurrentAppNotifier(this._supportedApps) : super(_supportedApps.first); void setCurrentApp(Application app) { state = app; } void _notifyDeviceChanged(YubiKeyData? data) { if (data == null || state.getAvailability(data) != Availability.unsupported) { // Keep current app return; } state = _supportedApps.firstWhere( (app) => app.getAvailability(data) == Availability.enabled, orElse: () => _supportedApps.first, ); } } abstract class QrScanner { Future scanQr([String? imageData]); } final qrScannerProvider = Provider( (ref) => null, ); final contextProvider = StateNotifierProvider( (ref) => ContextProvider()); typedef WithContext = Future Function( Future Function(BuildContext context) action); class ContextProvider extends StateNotifier { ContextProvider() : super(null); Future withContext(Future Function(BuildContext context) action) { final completer = Completer(); if (mounted) { state = (context) async { completer.complete(await action(context)); }; } else { completer.completeError('Not attached'); } return completer.future; } }