Convert StateNotifiers to AsyncNotifiers for App states.

This commit is contained in:
Dain Nilsson 2023-05-03 21:20:08 +02:00
parent fbe3cab253
commit 16f6732f09
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
11 changed files with 141 additions and 160 deletions

View File

@ -23,22 +23,19 @@ import '../../app/models.dart';
import '../../app/state.dart';
import '../../management/state.dart';
final androidManagementState = StateNotifierProvider.autoDispose
.family<ManagementStateNotifier, AsyncValue<DeviceInfo>, DevicePath>(
(ref, devicePath) {
// Make sure to rebuild if currentDevice changes (as on reboot)
ref.watch(currentDeviceProvider);
final notifier = _AndroidManagementStateNotifier(ref);
return notifier..refresh();
},
final androidManagementState = AsyncNotifierProvider.autoDispose
.family<ManagementStateNotifier, DeviceInfo, DevicePath>(
_AndroidManagementStateNotifier.new,
);
class _AndroidManagementStateNotifier extends ManagementStateNotifier {
final Ref _ref;
@override
FutureOr<DeviceInfo> build(DevicePath devicePath) {
// Make sure to rebuild if currentDevice changes (as on reboot)
ref.watch(currentDeviceProvider);
_AndroidManagementStateNotifier(this._ref) : super();
void refresh() async {}
return Completer<DeviceInfo>().future;
}
@override
Future<void> setMode(
@ -55,6 +52,6 @@ class _AndroidManagementStateNotifier extends ManagementStateNotifier {
state = const AsyncValue.loading();
}
_ref.read(attachedDevicesProvider.notifier).refresh();
ref.read(attachedDevicesProvider.notifier).refresh();
}
}

View File

@ -36,33 +36,31 @@ final _log = Logger('android.oath.state');
const _methods = MethodChannel('android.oath.methods');
final androidOathStateProvider = StateNotifierProvider.autoDispose
.family<OathStateNotifier, AsyncValue<OathState>, DevicePath>(
(ref, devicePath) => _AndroidOathStateNotifier());
final androidOathStateProvider = AsyncNotifierProvider.autoDispose
.family<OathStateNotifier, OathState, DevicePath>(
_AndroidOathStateNotifier.new);
class _AndroidOathStateNotifier extends OathStateNotifier {
final _events = const EventChannel('android.oath.sessionState');
late StreamSubscription _sub;
_AndroidOathStateNotifier() : super() {
@override
FutureOr<OathState> build(DevicePath arg) {
_sub = _events.receiveBroadcastStream().listen((event) {
final json = jsonDecode(event);
if (mounted) {
if (json == null) {
state = const AsyncValue.loading();
} else {
final oathState = OathState.fromJson(json);
state = AsyncValue.data(oathState);
}
if (json == null) {
state = const AsyncValue.loading();
} else {
final oathState = OathState.fromJson(json);
state = AsyncValue.data(oathState);
}
}, onError: (err, stackTrace) {
state = AsyncValue.error(err, stackTrace);
});
}
@override
void dispose() {
_sub.cancel();
super.dispose();
ref.onDispose(_sub.cancel);
return Completer<OathState>().future;
}
@override

View File

@ -18,6 +18,8 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../app/models.dart';
bool get isDesktop {
return const [
TargetPlatform.windows,
@ -36,21 +38,16 @@ final prefProvider = Provider<SharedPreferences>((ref) {
});
abstract class ApplicationStateNotifier<T>
extends StateNotifier<AsyncValue<T>> {
ApplicationStateNotifier() : super(const AsyncValue.loading());
extends AutoDisposeFamilyAsyncNotifier<T, DevicePath> {
ApplicationStateNotifier() : super();
@protected
Future<void> updateState(Future<T> Function() guarded) async {
final result = await AsyncValue.guard(guarded);
if (mounted) {
state = result;
}
state = await AsyncValue.guard(guarded);
}
@protected
void setData(T value) {
if (mounted) {
state = AsyncValue.data(value);
}
state = AsyncValue.data(value);
}
}

View File

@ -45,47 +45,42 @@ final _sessionProvider =
},
);
final desktopFidoState = StateNotifierProvider.autoDispose
.family<FidoStateNotifier, AsyncValue<FidoState>, DevicePath>(
(ref, devicePath) {
final session = ref.watch(_sessionProvider(devicePath));
final desktopFidoState = AsyncNotifierProvider.autoDispose
.family<FidoStateNotifier, FidoState, DevicePath>(
_DesktopFidoStateNotifier.new);
class _DesktopFidoStateNotifier extends FidoStateNotifier {
late RpcNodeSession _session;
late StateController<String?> _pinController;
@override
FutureOr<FidoState> build(DevicePath devicePath) async {
_session = ref.watch(_sessionProvider(devicePath));
if (Platform.isWindows) {
// Make sure to rebuild if isAdmin changes
ref.watch(rpcStateProvider.select((state) => state.isAdmin));
}
final notifier = _DesktopFidoStateNotifier(
session,
ref.watch(_pinProvider(devicePath).notifier),
);
session.setErrorHandler('state-reset', (_) async {
_pinController = ref.watch(_pinProvider(devicePath).notifier);
_session.setErrorHandler('state-reset', (_) async {
ref.invalidate(_sessionProvider(devicePath));
});
session.setErrorHandler('auth-required', (_) async {
_session.setErrorHandler('auth-required', (_) async {
final pin = ref.read(_pinProvider(devicePath));
if (pin != null) {
await notifier.unlock(pin);
await unlock(pin);
}
});
ref.onDispose(() {
session.unsetErrorHandler('auth-required');
_session.unsetErrorHandler('auth-required');
});
ref.onDispose(() {
session.unsetErrorHandler('state-reset');
_session.unsetErrorHandler('state-reset');
});
return notifier..refresh();
},
);
class _DesktopFidoStateNotifier extends FidoStateNotifier {
final RpcNodeSession _session;
final StateController<String?> _pinController;
_DesktopFidoStateNotifier(this._session, this._pinController) : super();
Future<void> refresh() => updateState(() async {
final result = await _session.command('get');
_log.debug('application status', jsonEncode(result));
return FidoState.fromJson(result['data']);
});
final result = await _session.command('get');
_log.debug('application status', jsonEncode(result));
return FidoState.fromJson(result['data']);
}
@override
Stream<InteractionEvent> reset() {
@ -105,8 +100,8 @@ class _DesktopFidoStateNotifier extends FidoStateNotifier {
controller.onListen = () async {
try {
await _session.command('reset', signal: signaler);
await refresh();
await controller.sink.close();
ref.invalidateSelf();
} catch (e) {
controller.sink.addError(e);
}
@ -155,16 +150,19 @@ final desktopFingerprintProvider = StateNotifierProvider.autoDispose.family<
FidoFingerprintsNotifier, AsyncValue<List<Fingerprint>>, DevicePath>(
(ref, devicePath) => _DesktopFidoFingerprintsNotifier(
ref.watch(_sessionProvider(devicePath)),
ref,
));
class _DesktopFidoFingerprintsNotifier extends FidoFingerprintsNotifier {
final RpcNodeSession _session;
final Ref _ref;
_DesktopFidoFingerprintsNotifier(this._session) {
_DesktopFidoFingerprintsNotifier(this._session, this._ref) {
_refresh();
}
Future<void> _refresh() async {
_ref.invalidate(fidoStateProvider(_session.devicePath));
final result = await _session.command('fingerprints');
setItems((result['children'] as Map<String, dynamic>)
.entries
@ -236,12 +234,14 @@ final desktopCredentialProvider = StateNotifierProvider.autoDispose.family<
FidoCredentialsNotifier, AsyncValue<List<FidoCredential>>, DevicePath>(
(ref, devicePath) => _DesktopFidoCredentialsNotifier(
ref.watch(_sessionProvider(devicePath)),
ref,
));
class _DesktopFidoCredentialsNotifier extends FidoCredentialsNotifier {
final RpcNodeSession _session;
final Ref _ref;
_DesktopFidoCredentialsNotifier(this._session) {
_DesktopFidoCredentialsNotifier(this._session, this._ref) {
_refresh();
}
@ -259,6 +259,7 @@ class _DesktopFidoCredentialsNotifier extends FidoCredentialsNotifier {
}
}
setItems(creds);
_ref.invalidate(fidoStateProvider(_session.devicePath));
}
@override

View File

@ -36,53 +36,51 @@ final _sessionProvider =
RpcNodeSession(ref.watch(rpcProvider).requireValue, devicePath, []),
);
final desktopManagementState = StateNotifierProvider.autoDispose
.family<ManagementStateNotifier, AsyncValue<DeviceInfo>, DevicePath>(
(ref, devicePath) {
final desktopManagementState = AsyncNotifierProvider.autoDispose
.family<ManagementStateNotifier, DeviceInfo, DevicePath>(
_DesktopManagementStateNotifier.new);
class _DesktopManagementStateNotifier extends ManagementStateNotifier {
late RpcNodeSession _session;
List<String> _subpath = [];
_DesktopManagementStateNotifier() : super();
@override
FutureOr<DeviceInfo> build(DevicePath devicePath) async {
// Make sure to rebuild if currentDevice changes (as on reboot)
ref.watch(currentDeviceProvider);
final session = ref.watch(_sessionProvider(devicePath));
final notifier = _DesktopManagementStateNotifier(ref, session);
session.setErrorHandler('state-reset', (_) async {
_session = ref.watch(_sessionProvider(devicePath));
_session.setErrorHandler('state-reset', (_) async {
ref.invalidate(_sessionProvider(devicePath));
});
ref.onDispose(() {
session.unsetErrorHandler('state-reset');
_session.unsetErrorHandler('state-reset');
});
return notifier..refresh();
},
);
class _DesktopManagementStateNotifier extends ManagementStateNotifier {
final Ref _ref;
final RpcNodeSession _session;
List<String> _subpath = [];
_DesktopManagementStateNotifier(this._ref, this._session) : super();
Future<void> refresh() => updateState(() async {
final result = await _session.command('get');
final info = DeviceInfo.fromJson(result['data']['info']);
final interfaces = (result['children'] as Map).keys.toSet();
for (final iface in [
// This is the preferred order
UsbInterface.ccid,
UsbInterface.otp,
UsbInterface.fido,
]) {
if (interfaces.contains(iface.name)) {
final path = [iface.name, 'management'];
try {
await _session.command('get', target: path);
_subpath = path;
_log.debug('Using transport $iface for management');
return info;
} catch (e) {
_log.warning('Failed connecting to management via $iface');
}
}
final result = await _session.command('get');
final info = DeviceInfo.fromJson(result['data']['info']);
final interfaces = (result['children'] as Map).keys.toSet();
for (final iface in [
// This is the preferred order
UsbInterface.ccid,
UsbInterface.otp,
UsbInterface.fido,
]) {
if (interfaces.contains(iface.name)) {
final path = [iface.name, 'management'];
try {
await _session.command('get', target: path);
_subpath = path;
_log.debug('Using transport $iface for management');
return info;
} catch (e) {
_log.warning('Failed connecting to management via $iface');
}
throw 'Failed connection over all interfaces';
});
}
}
throw 'Failed connection over all interfaces';
}
@override
Future<void> setMode(
@ -94,7 +92,7 @@ class _DesktopManagementStateNotifier extends ManagementStateNotifier {
'challenge_response_timeout': challengeResponseTimeout,
'auto_eject_timeout': autoEjectTimeout,
});
_ref.read(attachedDevicesProvider.notifier).refresh();
ref.read(attachedDevicesProvider.notifier).refresh();
}
@override
@ -111,6 +109,6 @@ class _DesktopManagementStateNotifier extends ManagementStateNotifier {
'new_lock_code': newLockCode,
'reboot': reboot,
});
_ref.read(attachedDevicesProvider.notifier).refresh();
ref.read(attachedDevicesProvider.notifier).refresh();
}
}

View File

@ -57,56 +57,48 @@ class _LockKeyNotifier extends StateNotifier<String?> {
}
}
final desktopOathState = StateNotifierProvider.autoDispose
.family<OathStateNotifier, AsyncValue<OathState>, DevicePath>(
(ref, devicePath) {
final session = ref.watch(_sessionProvider(devicePath));
final notifier = _DesktopOathStateNotifier(session, ref);
session
final desktopOathState = AsyncNotifierProvider.autoDispose
.family<OathStateNotifier, OathState, DevicePath>(
_DesktopOathStateNotifier.new);
class _DesktopOathStateNotifier extends OathStateNotifier {
late RpcNodeSession _session;
@override
FutureOr<OathState> build(DevicePath devicePath) async {
_session = ref.watch(_sessionProvider(devicePath));
_session
..setErrorHandler('state-reset', (_) async {
ref.invalidate(_sessionProvider(devicePath));
})
..setErrorHandler('auth-required', (_) async {
await notifier.refresh();
ref.invalidateSelf();
});
ref.onDispose(() {
session
_session
..unsetErrorHandler('state-reset')
..unsetErrorHandler('auth-required');
});
return notifier..refresh();
},
);
class _DesktopOathStateNotifier extends OathStateNotifier {
final RpcNodeSession _session;
final Ref _ref;
_DesktopOathStateNotifier(this._session, this._ref) : super();
refresh() => updateState(() async {
final result = await _session.command('get');
_log.debug('application status', jsonEncode(result));
var oathState = OathState.fromJson(result['data']);
final key = _ref.read(_oathLockKeyProvider(_session.devicePath));
if (oathState.locked && key != null) {
final result =
await _session.command('validate', params: {'key': key});
if (result['valid']) {
oathState = oathState.copyWith(locked: false);
} else {
_ref
.read(_oathLockKeyProvider(_session.devicePath).notifier)
.unsetKey();
}
}
return oathState;
});
final result = await _session.command('get');
_log.debug('application status', jsonEncode(result));
var oathState = OathState.fromJson(result['data']);
final key = ref.read(_oathLockKeyProvider(_session.devicePath));
if (oathState.locked && key != null) {
final result = await _session.command('validate', params: {'key': key});
if (result['valid']) {
oathState = oathState.copyWith(locked: false);
} else {
ref.read(_oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
}
}
return oathState;
}
@override
Future<void> reset() async {
await _session.command('reset');
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
_ref.invalidate(_sessionProvider(_session.devicePath));
ref.read(_oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
ref.invalidate(_sessionProvider(_session.devicePath));
}
@override
@ -120,7 +112,7 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
final bool remembered = validate['remembered'];
if (valid) {
_log.debug('applet unlocked');
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).setKey(key);
ref.read(_oathLockKeyProvider(_session.devicePath).notifier).setKey(key);
setData(state.value!.copyWith(
locked: false,
remembered: remembered,
@ -158,7 +150,7 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
await _session.command('derive', params: {'password': password});
var key = derive['key'];
await _session.command('set_key', params: {'key': key});
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).setKey(key);
ref.read(_oathLockKeyProvider(_session.devicePath).notifier).setKey(key);
}
_log.debug('OATH key set');
@ -177,7 +169,7 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
}
}
await _session.command('unset_key');
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
ref.read(_oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
setData(oathState.copyWith(hasKey: false, locked: false));
return true;
}
@ -185,7 +177,7 @@ class _DesktopOathStateNotifier extends OathStateNotifier {
@override
Future<void> forgetPassword() async {
await _session.command('forget');
_ref.read(_oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
ref.read(_oathLockKeyProvider(_session.devicePath).notifier).unsetKey();
setData(state.value!.copyWith(remembered: false));
}
}

View File

@ -21,9 +21,9 @@ import '../app/models.dart';
import '../core/state.dart';
import 'models.dart';
final fidoStateProvider = StateNotifierProvider.autoDispose
.family<FidoStateNotifier, AsyncValue<FidoState>, DevicePath>(
(ref, devicePath) => throw UnimplementedError(),
final fidoStateProvider = AsyncNotifierProvider.autoDispose
.family<FidoStateNotifier, FidoState, DevicePath>(
() => throw UnimplementedError(),
);
abstract class FidoStateNotifier extends ApplicationStateNotifier<FidoState> {

View File

@ -20,9 +20,9 @@ import 'package:yubico_authenticator/management/models.dart';
import '../app/models.dart';
import '../core/state.dart';
final managementStateProvider = StateNotifierProvider.autoDispose
.family<ManagementStateNotifier, AsyncValue<DeviceInfo>, DevicePath>(
(ref, devicePath) => throw UnimplementedError(),
final managementStateProvider = AsyncNotifierProvider.autoDispose
.family<ManagementStateNotifier, DeviceInfo, DevicePath>(
() => throw UnimplementedError(),
);
abstract class ManagementStateNotifier

View File

@ -37,9 +37,9 @@ class SearchNotifier extends StateNotifier<String> {
}
}
final oathStateProvider = StateNotifierProvider.autoDispose
.family<OathStateNotifier, AsyncValue<OathState>, DevicePath>(
(ref, devicePath) => throw UnimplementedError(),
final oathStateProvider = AsyncNotifierProvider.autoDispose
.family<OathStateNotifier, OathState, DevicePath>(
() => throw UnimplementedError(),
);
abstract class OathStateNotifier extends ApplicationStateNotifier<OathState> {

View File

@ -14,7 +14,6 @@
* limitations under the License.
*/
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';

View File

@ -42,7 +42,6 @@ Widget oathBuildActions(
}) {
final l10n = AppLocalizations.of(context)!;
final capacity = oathState.version.isAtLeast(4) ? 32 : null;
//final theme = Theme.of(context).colorScheme;
final theme =
ButtonTheme.of(context).colorScheme ?? Theme.of(context).colorScheme;
return FsDialog(