From 9a19a9c608e81e62eba82dce2d454e7e478f4eca Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 13 Mar 2024 10:36:50 +0100 Subject: [PATCH] use ViewModelData interface --- .../com/yubico/authenticator/ChannelHelper.kt | 67 ++++--------------- .../yubico/authenticator/JsonSerializer.kt | 4 +- .../com/yubico/authenticator/MainActivity.kt | 6 +- .../yubico/authenticator/fido/FidoManager.kt | 19 ++---- .../authenticator/fido/FidoResetHelper.kt | 2 +- .../authenticator/fido/FidoViewModel.kt | 34 ++++------ .../yubico/authenticator/fido/data/Session.kt | 8 ++- .../yubico/authenticator/oath/OathManager.kt | 15 ++--- .../authenticator/oath/OathViewModel.kt | 30 ++++----- lib/android/fido/state.dart | 13 ++-- lib/android/oath/state.dart | 6 +- lib/app/error_data_empty.dart | 3 - lib/desktop/fido/state.dart | 6 +- lib/exception/no_data_exception.dart | 19 ++++++ lib/fido/state.dart | 4 +- lib/fido/views/fingerprints_screen.dart | 13 ++-- lib/fido/views/passkeys_screen.dart | 12 ++-- lib/oath/views/oath_screen.dart | 4 +- 18 files changed, 112 insertions(+), 153 deletions(-) delete mode 100644 lib/app/error_data_empty.dart create mode 100644 lib/exception/no_data_exception.dart diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/ChannelHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/ChannelHelper.kt index f1abbae7..c33b5ee3 100755 --- a/android/app/src/main/kotlin/com/yubico/authenticator/ChannelHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/ChannelHelper.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Yubico. + * Copyright (C) 2022,2024 Yubico. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,18 +36,12 @@ interface JsonSerializable { fun toJson() : String } -sealed interface SessionState { - data object Empty : SessionState - data object Loading : SessionState - data class Value(val data: T) : SessionState +sealed interface ViewModelData { + data object Empty : ViewModelData + data object Loading : ViewModelData + data class Value(val data: T) : ViewModelData } -data class ChannelData(val data: T?, val isLoading: Boolean = false) { - companion object { - fun loading() = ChannelData(null, true) - fun empty() = ChannelData(null) - } -} /** * Observes a LiveData value, sending each change to Flutter via an EventChannel. */ @@ -78,55 +72,20 @@ inline fun LiveData.streamTo(lifecycleOwner: LifecycleOwner, mess } /** - * Observes a Loadable LiveData value, sending each change to Flutter via an EventChannel. + * Observes a ViewModelData LiveData value, sending each change to Flutter via an EventChannel. */ -inline fun LiveData>.streamData(lifecycleOwner: LifecycleOwner, messenger: BinaryMessenger, channelName: String): Closeable { +@JvmName("streamViewModelData") +inline fun LiveData.streamTo(lifecycleOwner: LifecycleOwner, messenger: BinaryMessenger, channelName: String): Closeable { val channel = EventChannel(messenger, channelName) var sink: EventChannel.EventSink? = null - channel.setStreamHandler(object : EventChannel.StreamHandler { - override fun onListen(arguments: Any?, events: EventChannel.EventSink) { - sink = events - events.success( - value?.let { - if (it.isLoading) LOADING - else it.data?.let(jsonSerializer::encodeToString) ?: NULL - } ?: NULL - ) + val get: (ViewModelData) -> String = { + when (it) { + is ViewModelData.Empty -> NULL + is ViewModelData.Loading -> LOADING + is ViewModelData.Value<*> -> it.data.toJson() } - - override fun onCancel(arguments: Any?) { - sink = null - } - }) - - val observer = Observer> { - sink?.success( - if (it.isLoading) LOADING - else it.data?.let(jsonSerializer::encodeToString) ?: NULL - ) } - observe(lifecycleOwner, observer) - - return Closeable { - removeObserver(observer) - channel.setStreamHandler(null) - } -} - - -fun get(state: SessionState) : String = when (state) { - is SessionState.Empty -> NULL - is SessionState.Loading -> LOADING - is SessionState.Value<*> -> state.data.toJson() -} - -/** - * Observes a Loadable LiveData value, sending each change to Flutter via an EventChannel. - */ -inline fun LiveData.streamState(lifecycleOwner: LifecycleOwner, messenger: BinaryMessenger, channelName: String): Closeable { - val channel = EventChannel(messenger, channelName) - var sink: EventChannel.EventSink? = null channel.setStreamHandler(object : EventChannel.StreamHandler { override fun onListen(arguments: Any?, events: EventChannel.EventSink) { diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/JsonSerializer.kt b/android/app/src/main/kotlin/com/yubico/authenticator/JsonSerializer.kt index a1f60555..982e91ac 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/JsonSerializer.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/JsonSerializer.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Yubico. + * Copyright (C) 2022,2024 Yubico. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import kotlinx.serialization.json.Json const val NULL = "null" -const val LOADING = """{ "loading": true }""" +const val LOADING = "\"loading\"" val jsonSerializer = Json { // creates properties for default values diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt index 319a0059..a91c2b79 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -320,10 +320,10 @@ class MainActivity : FlutterFragmentActivity() { flutterStreams = listOf( viewModel.deviceInfo.streamTo(this, messenger, "android.devices.deviceInfo"), - oathViewModel.sessionState.streamState(this, messenger, "android.oath.sessionState"), + oathViewModel.sessionState.streamTo(this, messenger, "android.oath.sessionState"), oathViewModel.credentials.streamTo(this, messenger, "android.oath.credentials"), - fidoViewModel.sessionState.streamData(this, messenger, "android.fido.sessionState"), - fidoViewModel.credentials.streamData(this, messenger, "android.fido.credentials"), + fidoViewModel.sessionState.streamTo(this, messenger, "android.fido.sessionState"), + fidoViewModel.credentials.streamTo(this, messenger, "android.fido.credentials"), fidoViewModel.resetState.streamTo(this, messenger, "android.fido.reset"), ) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoManager.kt index 8f844cbb..b780309d 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoManager.kt @@ -136,14 +136,6 @@ class FidoManager( else -> throw NotImplementedError() } } - - if (!deviceManager.isUsbKeyConnected()) { - // for NFC connections require extra tap when switching context - if (fidoViewModel.sessionState.value == null) { - fidoViewModel.clearSessionState() - } - } - } override fun dispose() { @@ -151,7 +143,7 @@ class FidoManager( deviceManager.removeDeviceListener(this) fidoChannel.setMethodCallHandler(null) fidoViewModel.clearSessionState() - fidoViewModel.clearCredentials() + fidoViewModel.updateCredentials(emptyList()) coroutineScope.cancel() } @@ -198,7 +190,7 @@ class FidoManager( YubiKitFidoSession(connection as SmartCardConnection) } - val previousSession = fidoViewModel.sessionState.value?.data?.info + val previousSession = fidoViewModel.currentSession()?.info val currentSession = SessionInfo(fidoSession.cachedInfo) logger.debug( "Previous session: {}, current session: {}", @@ -286,7 +278,7 @@ class FidoManager( ctapException.ctapError == CtapException.ERR_PIN_AUTH_BLOCKED ) { pinStore.setPin(null) - fidoViewModel.clearCredentials() + fidoViewModel.updateCredentials(emptyList()) val pinRetriesResult = clientPin.pinRetries JSONObject( mapOf( @@ -356,9 +348,6 @@ class FidoManager( pinUvAuthToken: ByteArray ): List = try { - - fidoViewModel.setCredentialsLoadingState() - val credMan = CredentialManagement(fidoSession, clientPin.pinUvAuth, pinUvAuthToken) val rpIds = credMan.enumerateRps() @@ -394,7 +383,7 @@ class FidoManager( val credMan = CredentialManagement(fidoSession, clientPin.pinUvAuth, token) val credentialDescriptor = - fidoViewModel.credentials.value?.data?.firstOrNull { + fidoViewModel.credentials.value?.firstOrNull { it.credentialId == credentialId && it.rpId == rpId }?.publicKeyCredentialDescriptor diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoResetHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoResetHelper.kt index 00e729f0..ca35f1bd 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoResetHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoResetHelper.kt @@ -202,7 +202,7 @@ class FidoResetHelper( logger.debug("Calling FIDO reset") fidoSession.reset(resetCommandState) fidoViewModel.setSessionState(Session(fidoSession.info, true)) - fidoViewModel.clearCredentials() + fidoViewModel.updateCredentials(emptyList()) pinStore.setPin(null) } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoViewModel.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoViewModel.kt index 05c50c77..53a07a75 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoViewModel.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoViewModel.kt @@ -19,45 +19,39 @@ package com.yubico.authenticator.fido import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.yubico.authenticator.ChannelData +import com.yubico.authenticator.ViewModelData import com.yubico.authenticator.fido.data.FidoCredential import com.yubico.authenticator.fido.data.Session class FidoViewModel : ViewModel() { - private val _sessionState = MutableLiveData>() - val sessionState: LiveData> = _sessionState + private val _sessionState = MutableLiveData() + val sessionState: LiveData = _sessionState - fun setSessionState(sessionState: Session?) { - _sessionState.postValue(ChannelData(sessionState)) + fun currentSession() : Session? = (_sessionState.value as? ViewModelData.Value<*>)?.data as? Session? + + fun setSessionState(sessionState: Session) { + _sessionState.postValue(ViewModelData.Value(sessionState)) } fun clearSessionState() { - _sessionState.postValue(ChannelData.empty()) + _sessionState.postValue(ViewModelData.Empty) } fun setSessionLoadingState() { - _sessionState.postValue(ChannelData.loading()) + _sessionState.postValue(ViewModelData.Loading) } - private val _credentials = MutableLiveData?>>() - val credentials: LiveData?>> = _credentials - - fun setCredentialsLoadingState() { - _credentials.postValue(ChannelData.loading()) - } + private val _credentials = MutableLiveData>() + val credentials: LiveData> = _credentials fun updateCredentials(credentials: List) { - _credentials.postValue(ChannelData(credentials)) - } - - fun clearCredentials() { - _credentials.postValue(ChannelData.empty()) + _credentials.postValue(credentials) } fun removeCredential(rpId: String, credentialId: String) { - _credentials.postValue(ChannelData(_credentials.value?.data?.filter { + _credentials.postValue(_credentials.value?.filter { it.credentialId != credentialId || it.rpId != rpId - })) + }) } private val _resetState = MutableLiveData(FidoResetState.Remove.value) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/data/Session.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/data/Session.kt index efd86daa..73e343cf 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/data/Session.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/data/Session.kt @@ -16,6 +16,8 @@ package com.yubico.authenticator.fido.data +import com.yubico.authenticator.JsonSerializable +import com.yubico.authenticator.jsonSerializer import com.yubico.yubikit.fido.ctap.Ctap2Session.InfoData import kotlinx.serialization.* @@ -87,8 +89,12 @@ data class Session( @SerialName("info") val info: SessionInfo, val unlocked: Boolean -) { +) : JsonSerializable { constructor(infoData: InfoData, unlocked: Boolean) : this( SessionInfo(infoData), unlocked ) + + override fun toJson(): String { + return jsonSerializer.encodeToString(this) + } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathManager.kt index d7ddfee5..c32e053a 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathManager.kt @@ -200,13 +200,6 @@ class OathManager( else -> throw NotImplementedError() } } - - if (!deviceManager.isUsbKeyConnected()) { - // for NFC connections require extra tap when switching context - if (oathViewModel.sessionState.value is SessionState.Empty) { - oathViewModel.setSessionState(null) - } - } } override fun dispose() { @@ -214,7 +207,7 @@ class OathManager( deviceManager.removeDeviceListener(this) oathViewModel.credentials.removeObserver(credentialObserver) oathChannel.setMethodCallHandler(null) - oathViewModel.setSessionState(null) + oathViewModel.clearSession() oathViewModel.updateCredentials(mapOf()) coroutineScope.cancel() } @@ -325,7 +318,7 @@ class OathManager( } // Clear any cached OATH state - oathViewModel.setSessionState(null) + oathViewModel.clearSession() } } @@ -752,10 +745,10 @@ class OathManager( override fun onDisconnected() { refreshJob?.cancel() - oathViewModel.setSessionState(null) + oathViewModel.clearSession() } override fun onTimeout() { - oathViewModel.setSessionState(null) + oathViewModel.clearSession() } } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathViewModel.kt b/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathViewModel.kt index 777f0b75..cbef7238 100755 --- a/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathViewModel.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2023 Yubico. + * Copyright (C) 2022-2024 Yubico. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ package com.yubico.authenticator.oath import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.yubico.authenticator.SessionState -import com.yubico.authenticator.JsonSerializable +import com.yubico.authenticator.ViewModelData import com.yubico.authenticator.oath.data.Code import com.yubico.authenticator.oath.data.Credential import com.yubico.authenticator.oath.data.CredentialWithCode @@ -28,32 +27,31 @@ import com.yubico.authenticator.oath.data.Session class OathViewModel: ViewModel() { - private val _sessionState = MutableLiveData() - val sessionState: LiveData = _sessionState + private val _sessionState = MutableLiveData() + val sessionState: LiveData = _sessionState - fun currentSession() : Session? = (sessionState.value as? SessionState.Value<*>)?.data as Session? + fun currentSession() : Session? = (_sessionState.value as? ViewModelData.Value<*>)?.data as? Session? // Sets session and credentials after performing OATH reset // Note: we cannot use [setSessionState] because resetting OATH changes deviceId fun resetOathSession(sessionState: Session, credentials: Map) { - _sessionState.postValue(SessionState.Value(sessionState)) + _sessionState.postValue(ViewModelData.Value(sessionState)) updateCredentials(credentials) } - fun setSessionState(sessionState: Session?) { + fun setSessionState(sessionState: Session) { val oldDeviceId = currentSession()?.deviceId - - if (sessionState == null) { - _sessionState.postValue(SessionState.Empty) - } else { - _sessionState.postValue(SessionState.Value(sessionState)) - } - - if(oldDeviceId != sessionState?.deviceId) { + _sessionState.postValue(ViewModelData.Value(sessionState)) + if(oldDeviceId != sessionState.deviceId) { _credentials.postValue(null) } } + fun clearSession() { + _sessionState.postValue(ViewModelData.Empty) + _credentials.postValue(null) + } + private val _credentials = MutableLiveData?>() val credentials: LiveData?> = _credentials diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index 0790d657..69d77f0b 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -24,6 +24,7 @@ import 'package:logging/logging.dart'; import '../../app/logging.dart'; import '../../app/models.dart'; import '../../exception/cancellation_exception.dart'; +import '../../exception/no_data_exception.dart'; import '../../exception/platform_exception_decoder.dart'; import '../../fido/models.dart'; import '../../fido/state.dart'; @@ -33,19 +34,19 @@ final _log = Logger('android.fido.state'); const _methods = MethodChannel('android.fido.methods'); final androidFidoStateProvider = AsyncNotifierProvider.autoDispose - .family(_FidoStateNotifier.new); + .family(_FidoStateNotifier.new); class _FidoStateNotifier extends FidoStateNotifier { final _events = const EventChannel('android.fido.sessionState'); late StreamSubscription _sub; @override - FutureOr build(DevicePath devicePath) async { + FutureOr build(DevicePath devicePath) async { _sub = _events.receiveBroadcastStream().listen((event) { final json = jsonDecode(event); if (json == null) { - state = const AsyncValue.data(null); - } else if (json.containsKey('loading') && json['loading'] == true) { + state = AsyncValue.error(const NoDataException(), StackTrace.current); + } else if (json == 'loading') { state = const AsyncValue.loading(); } else { final fidoState = FidoState.fromJson(json); @@ -57,7 +58,7 @@ class _FidoStateNotifier extends FidoStateNotifier { ref.onDispose(_sub.cancel); - return Completer().future; + return Completer().future; } @override @@ -191,8 +192,6 @@ class _FidoCredentialsNotifier extends FidoCredentialsNotifier { _sub = _events.receiveBroadcastStream().listen((event) { final json = jsonDecode(event); if (json == null) { - state = const AsyncValue.data(null); - } else if (json[0] is Map && json[0]['loading'] == true) { state = const AsyncValue.loading(); } else { List newState = List.from( diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index d9d48186..14b4d1b5 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -24,13 +24,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:material_symbols_icons/symbols.dart'; -import '../../app/error_data_empty.dart'; import '../../app/logging.dart'; import '../../app/models.dart'; import '../../app/state.dart'; import '../../app/views/user_interaction.dart'; import '../../core/models.dart'; import '../../exception/cancellation_exception.dart'; +import '../../exception/no_data_exception.dart'; import '../../exception/platform_exception_decoder.dart'; import '../../oath/models.dart'; import '../../oath/state.dart'; @@ -52,8 +52,8 @@ class _AndroidOathStateNotifier extends OathStateNotifier { _sub = _events.receiveBroadcastStream().listen((event) { final json = jsonDecode(event); if (json == null) { - state = AsyncValue.error(const ErrorDataEmpty(), StackTrace.current); - } else if (json[0] is Map && json[0]['loading'] == true) { + state = AsyncValue.error(const NoDataException(), StackTrace.current); + } else if (json == 'loading') { state = const AsyncValue.loading(); } else { final oathState = OathState.fromJson(json); diff --git a/lib/app/error_data_empty.dart b/lib/app/error_data_empty.dart deleted file mode 100644 index 2db2c9e3..00000000 --- a/lib/app/error_data_empty.dart +++ /dev/null @@ -1,3 +0,0 @@ -class ErrorDataEmpty { - const ErrorDataEmpty(); -} diff --git a/lib/desktop/fido/state.dart b/lib/desktop/fido/state.dart index 66f79aa0..9460ec4c 100755 --- a/lib/desktop/fido/state.dart +++ b/lib/desktop/fido/state.dart @@ -47,14 +47,14 @@ final _sessionProvider = ); final desktopFidoState = AsyncNotifierProvider.autoDispose - .family( + .family( _DesktopFidoStateNotifier.new); class _DesktopFidoStateNotifier extends FidoStateNotifier { late RpcNodeSession _session; late StateController _pinController; - FutureOr _build(DevicePath devicePath) async { + FutureOr _build(DevicePath devicePath) async { var result = await _session.command('get'); FidoState fidoState = FidoState.fromJson(result['data']); if (fidoState.hasPin && !fidoState.unlocked) { @@ -71,7 +71,7 @@ class _DesktopFidoStateNotifier extends FidoStateNotifier { } @override - FutureOr build(DevicePath devicePath) async { + FutureOr build(DevicePath devicePath) async { _session = ref.watch(_sessionProvider(devicePath)); if (Platform.isWindows) { // Make sure to rebuild if isAdmin changes diff --git a/lib/exception/no_data_exception.dart b/lib/exception/no_data_exception.dart new file mode 100644 index 00000000..c5498870 --- /dev/null +++ b/lib/exception/no_data_exception.dart @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 Yubico. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +class NoDataException implements Exception { + const NoDataException(); +} diff --git a/lib/fido/state.dart b/lib/fido/state.dart index 1c2a54fb..270a751b 100755 --- a/lib/fido/state.dart +++ b/lib/fido/state.dart @@ -21,11 +21,11 @@ import '../core/state.dart'; import 'models.dart'; final fidoStateProvider = AsyncNotifierProvider.autoDispose - .family( + .family( () => throw UnimplementedError(), ); -abstract class FidoStateNotifier extends ApplicationStateNotifier { +abstract class FidoStateNotifier extends ApplicationStateNotifier { Stream reset(); Future setPin(String newPin, {String? oldPin}); Future unlock(String pin); diff --git a/lib/fido/views/fingerprints_screen.dart b/lib/fido/views/fingerprints_screen.dart index b160a29b..abe7e571 100644 --- a/lib/fido/views/fingerprints_screen.dart +++ b/lib/fido/views/fingerprints_screen.dart @@ -31,6 +31,7 @@ import '../../app/views/app_page.dart'; import '../../app/views/message_page.dart'; import '../../app/views/message_page_not_initialized.dart'; import '../../core/state.dart'; +import '../../exception/no_data_exception.dart'; import '../../management/models.dart'; import '../../widgets/list_title.dart'; import '../features.dart' as features; @@ -45,6 +46,7 @@ import 'pin_entry_form.dart'; class FingerprintsScreen extends ConsumerWidget { final YubiKeyData deviceData; + const FingerprintsScreen(this.deviceData, {super.key}); @override @@ -57,6 +59,9 @@ class FingerprintsScreen extends ConsumerWidget { builder: (context, _) => const CircularProgressIndicator(), ), error: (error, _) { + if (error is NoDataException) { + return MessagePageNotInitialized(title: l10n.s_fingerprints); + } final enabled = deviceData .info.config.enabledCapabilities[deviceData.node.transport] ?? 0; @@ -74,11 +79,9 @@ class FingerprintsScreen extends ConsumerWidget { ); }, data: (fidoState) { - return fidoState == null - ? MessagePageNotInitialized(title: l10n.s_fingerprints) - : fidoState.unlocked - ? _FidoUnlockedPage(deviceData.node, fidoState) - : _FidoLockedPage(deviceData.node, fidoState); + return fidoState.unlocked + ? _FidoUnlockedPage(deviceData.node, fidoState) + : _FidoLockedPage(deviceData.node, fidoState); }); } } diff --git a/lib/fido/views/passkeys_screen.dart b/lib/fido/views/passkeys_screen.dart index 44d6d1a8..9571d01e 100644 --- a/lib/fido/views/passkeys_screen.dart +++ b/lib/fido/views/passkeys_screen.dart @@ -32,6 +32,7 @@ import '../../app/views/app_page.dart'; import '../../app/views/message_page.dart'; import '../../app/views/message_page_not_initialized.dart'; import '../../core/state.dart'; +import '../../exception/no_data_exception.dart'; import '../../management/models.dart'; import '../../widgets/list_title.dart'; import '../features.dart' as features; @@ -58,6 +59,9 @@ class PasskeysScreen extends ConsumerWidget { builder: (context, _) => const CircularProgressIndicator(), ), error: (error, _) { + if (error is NoDataException) { + return MessagePageNotInitialized(title: l10n.s_passkeys); + } final enabled = deviceData .info.config.enabledCapabilities[deviceData.node.transport] ?? 0; @@ -76,11 +80,9 @@ class PasskeysScreen extends ConsumerWidget { ); }, data: (fidoState) { - return fidoState == null - ? MessagePageNotInitialized(title: l10n.s_passkeys) - : fidoState.unlocked - ? _FidoUnlockedPage(deviceData.node, fidoState) - : _FidoLockedPage(deviceData.node, fidoState); + return fidoState.unlocked + ? _FidoUnlockedPage(deviceData.node, fidoState) + : _FidoLockedPage(deviceData.node, fidoState); }); } } diff --git a/lib/oath/views/oath_screen.dart b/lib/oath/views/oath_screen.dart index 8415c783..43e78a8e 100755 --- a/lib/oath/views/oath_screen.dart +++ b/lib/oath/views/oath_screen.dart @@ -23,7 +23,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:material_symbols_icons/symbols.dart'; -import '../../app/error_data_empty.dart'; import '../../app/message.dart'; import '../../app/models.dart'; import '../../app/shortcuts.dart'; @@ -34,6 +33,7 @@ import '../../app/views/app_page.dart'; import '../../app/views/message_page.dart'; import '../../app/views/message_page_not_initialized.dart'; import '../../core/state.dart'; +import '../../exception/no_data_exception.dart'; import '../../management/models.dart'; import '../../widgets/app_input_decoration.dart'; import '../../widgets/app_text_form_field.dart'; @@ -65,7 +65,7 @@ class OathScreen extends ConsumerWidget { graphic: CircularProgressIndicator(), delayedContent: true, ), - error: (error, _) => error is ErrorDataEmpty + error: (error, _) => error is NoDataException ? MessagePageNotInitialized(title: l10n.s_accounts) : AppFailurePage( cause: error,