2021-12-02 13:44:17 +03:00
|
|
|
import 'package:flutter/material.dart';
|
2021-11-19 17:05:57 +03:00
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
2021-11-23 15:02:05 +03:00
|
|
|
import 'package:logging/logging.dart';
|
2021-11-23 16:51:36 +03:00
|
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
2021-11-19 17:05:57 +03:00
|
|
|
|
2021-12-02 13:44:17 +03:00
|
|
|
import '../core/state.dart';
|
|
|
|
import '../oath/menu_actions.dart';
|
2021-11-19 17:05:57 +03:00
|
|
|
import 'models.dart';
|
|
|
|
|
2021-11-23 15:02:05 +03:00
|
|
|
final log = Logger('app.state');
|
|
|
|
|
2022-01-27 14:34:29 +03:00
|
|
|
// Default implementation is always focused, override with platform specific version.
|
|
|
|
final windowStateProvider = Provider<WindowState>(
|
|
|
|
(ref) => WindowState(focused: true, visible: true, active: true),
|
|
|
|
);
|
2021-12-03 12:27:29 +03:00
|
|
|
|
2021-12-02 13:44:17 +03:00
|
|
|
final themeModeProvider = StateNotifierProvider<ThemeModeNotifier, ThemeMode>(
|
|
|
|
(ref) => ThemeModeNotifier(ref.watch(prefProvider)));
|
|
|
|
|
|
|
|
class ThemeModeNotifier extends StateNotifier<ThemeMode> {
|
|
|
|
static const String _key = 'APP_STATE_THEME';
|
|
|
|
final SharedPreferences _prefs;
|
|
|
|
ThemeModeNotifier(this._prefs) : super(_fromName(_prefs.getString(_key)));
|
|
|
|
|
|
|
|
void setThemeMode(ThemeMode 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final searchProvider =
|
|
|
|
StateNotifierProvider<SearchNotifier, String>((ref) => SearchNotifier());
|
|
|
|
|
|
|
|
class SearchNotifier extends StateNotifier<String> {
|
|
|
|
SearchNotifier() : super('');
|
|
|
|
|
|
|
|
setFilter(String value) {
|
|
|
|
state = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-27 14:34:29 +03:00
|
|
|
// Override with platform implementation
|
|
|
|
final attachedDevicesProvider = Provider<List<DeviceNode>>(
|
|
|
|
(ref) => [],
|
|
|
|
);
|
2022-01-12 14:49:04 +03:00
|
|
|
|
2022-01-27 14:34:29 +03:00
|
|
|
// Override with platform implementation
|
|
|
|
final currentDeviceDataProvider = Provider<YubiKeyData?>(
|
|
|
|
(ref) => null,
|
|
|
|
);
|
2022-01-12 14:49:04 +03:00
|
|
|
|
2021-11-19 17:05:57 +03:00
|
|
|
final currentDeviceProvider =
|
|
|
|
StateNotifierProvider<CurrentDeviceNotifier, DeviceNode?>((ref) {
|
2021-11-23 16:51:36 +03:00
|
|
|
final provider = CurrentDeviceNotifier(ref.watch(prefProvider));
|
2021-11-19 17:05:57 +03:00
|
|
|
ref.listen(attachedDevicesProvider, provider._updateAttachedDevices);
|
|
|
|
return provider;
|
|
|
|
});
|
|
|
|
|
|
|
|
class CurrentDeviceNotifier extends StateNotifier<DeviceNode?> {
|
2022-01-12 14:49:04 +03:00
|
|
|
static const String _lastDevice = 'APP_STATE_LAST_DEVICE';
|
2021-11-23 16:51:36 +03:00
|
|
|
final SharedPreferences _prefs;
|
|
|
|
CurrentDeviceNotifier(this._prefs) : super(null);
|
2021-11-19 17:05:57 +03:00
|
|
|
|
|
|
|
_updateAttachedDevices(List<DeviceNode>? previous, List<DeviceNode> devices) {
|
2022-01-12 14:49:04 +03:00
|
|
|
if (!devices.contains(state)) {
|
|
|
|
final lastDevice = _prefs.getString(_lastDevice) ?? '';
|
|
|
|
try {
|
|
|
|
state = devices.firstWhere(
|
|
|
|
(dev) => dev.when(
|
|
|
|
usbYubiKey: (path, name, pid, info) =>
|
|
|
|
lastDevice == 'serial:${info.serial}',
|
|
|
|
nfcReader: (path, name) => lastDevice == 'name:$name',
|
|
|
|
),
|
|
|
|
orElse: () => devices.whereType<UsbYubiKeyNode>().first);
|
|
|
|
} on StateError {
|
|
|
|
state = null;
|
|
|
|
}
|
2021-11-19 17:05:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
setCurrentDevice(DeviceNode device) {
|
|
|
|
state = device;
|
2022-01-12 14:49:04 +03:00
|
|
|
device.when(
|
|
|
|
usbYubiKey: (path, name, pid, info) {
|
|
|
|
final serial = info.serial;
|
|
|
|
if (serial != null) {
|
|
|
|
_prefs.setString(_lastDevice, 'serial:$serial');
|
|
|
|
}
|
|
|
|
},
|
|
|
|
nfcReader: (path, name) {
|
|
|
|
_prefs.setString(_lastDevice, 'name:$name');
|
|
|
|
},
|
|
|
|
);
|
2021-11-19 17:05:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final subPageProvider = StateNotifierProvider<SubPageNotifier, SubPage>(
|
|
|
|
(ref) => SubPageNotifier(SubPage.authenticator));
|
|
|
|
|
|
|
|
class SubPageNotifier extends StateNotifier<SubPage> {
|
|
|
|
SubPageNotifier(SubPage state) : super(state);
|
|
|
|
|
|
|
|
void setSubPage(SubPage page) {
|
|
|
|
state = page;
|
|
|
|
}
|
|
|
|
}
|
2021-12-02 13:44:17 +03:00
|
|
|
|
|
|
|
typedef BuildActions = List<MenuAction> Function(BuildContext);
|
|
|
|
|
|
|
|
final menuActionsProvider = Provider.autoDispose<BuildActions>((ref) {
|
|
|
|
switch (ref.watch(subPageProvider)) {
|
|
|
|
case SubPage.authenticator:
|
|
|
|
return (context) => buildOathMenuActions(context, ref);
|
|
|
|
case SubPage.yubikey:
|
|
|
|
// TODO: Handle this case.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return (_) => [];
|
|
|
|
});
|