mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-22 08:22:16 +03:00
use ViewModelData interface
This commit is contained in:
parent
ff8f2c8ce0
commit
9a19a9c608
@ -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<T : JsonSerializable>(val data: T) : SessionState
|
||||
sealed interface ViewModelData {
|
||||
data object Empty : ViewModelData
|
||||
data object Loading : ViewModelData
|
||||
data class Value<T : JsonSerializable>(val data: T) : ViewModelData
|
||||
}
|
||||
|
||||
data class ChannelData<T>(val data: T?, val isLoading: Boolean = false) {
|
||||
companion object {
|
||||
fun <T> loading() = ChannelData<T>(null, true)
|
||||
fun <T> empty() = ChannelData<T>(null)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Observes a LiveData value, sending each change to Flutter via an EventChannel.
|
||||
*/
|
||||
@ -78,55 +72,20 @@ inline fun <reified T> LiveData<T>.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 <reified T> LiveData<ChannelData<T>>.streamData(lifecycleOwner: LifecycleOwner, messenger: BinaryMessenger, channelName: String): Closeable {
|
||||
@JvmName("streamViewModelData")
|
||||
inline fun <reified T : ViewModelData> LiveData<T>.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<ChannelData<T>> {
|
||||
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 <reified T : SessionState> LiveData<T>.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) {
|
||||
|
@ -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
|
||||
|
@ -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"),
|
||||
)
|
||||
|
||||
|
@ -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<FidoCredential> =
|
||||
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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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<ChannelData<Session?>>()
|
||||
val sessionState: LiveData<ChannelData<Session?>> = _sessionState
|
||||
private val _sessionState = MutableLiveData<ViewModelData>()
|
||||
val sessionState: LiveData<ViewModelData> = _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<ChannelData<List<FidoCredential>?>>()
|
||||
val credentials: LiveData<ChannelData<List<FidoCredential>?>> = _credentials
|
||||
|
||||
fun setCredentialsLoadingState() {
|
||||
_credentials.postValue(ChannelData.loading())
|
||||
}
|
||||
private val _credentials = MutableLiveData<List<FidoCredential>>()
|
||||
val credentials: LiveData<List<FidoCredential>> = _credentials
|
||||
|
||||
fun updateCredentials(credentials: List<FidoCredential>) {
|
||||
_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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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<SessionState>()
|
||||
val sessionState: LiveData<SessionState> = _sessionState
|
||||
private val _sessionState = MutableLiveData<ViewModelData>()
|
||||
val sessionState: LiveData<ViewModelData> = _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<Credential, Code?>) {
|
||||
_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<List<CredentialWithCode>?>()
|
||||
val credentials: LiveData<List<CredentialWithCode>?> = _credentials
|
||||
|
||||
|
@ -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, FidoState?, DevicePath>(_FidoStateNotifier.new);
|
||||
.family<FidoStateNotifier, FidoState, DevicePath>(_FidoStateNotifier.new);
|
||||
|
||||
class _FidoStateNotifier extends FidoStateNotifier {
|
||||
final _events = const EventChannel('android.fido.sessionState');
|
||||
late StreamSubscription _sub;
|
||||
|
||||
@override
|
||||
FutureOr<FidoState?> build(DevicePath devicePath) async {
|
||||
FutureOr<FidoState> 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<FidoState?>().future;
|
||||
return Completer<FidoState>().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<FidoCredential> newState = List.from(
|
||||
|
@ -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);
|
||||
|
@ -1,3 +0,0 @@
|
||||
class ErrorDataEmpty {
|
||||
const ErrorDataEmpty();
|
||||
}
|
@ -47,14 +47,14 @@ final _sessionProvider =
|
||||
);
|
||||
|
||||
final desktopFidoState = AsyncNotifierProvider.autoDispose
|
||||
.family<FidoStateNotifier, FidoState?, DevicePath>(
|
||||
.family<FidoStateNotifier, FidoState, DevicePath>(
|
||||
_DesktopFidoStateNotifier.new);
|
||||
|
||||
class _DesktopFidoStateNotifier extends FidoStateNotifier {
|
||||
late RpcNodeSession _session;
|
||||
late StateController<String?> _pinController;
|
||||
|
||||
FutureOr<FidoState?> _build(DevicePath devicePath) async {
|
||||
FutureOr<FidoState> _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<FidoState?> build(DevicePath devicePath) async {
|
||||
FutureOr<FidoState> build(DevicePath devicePath) async {
|
||||
_session = ref.watch(_sessionProvider(devicePath));
|
||||
if (Platform.isWindows) {
|
||||
// Make sure to rebuild if isAdmin changes
|
||||
|
19
lib/exception/no_data_exception.dart
Normal file
19
lib/exception/no_data_exception.dart
Normal file
@ -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();
|
||||
}
|
@ -21,11 +21,11 @@ import '../core/state.dart';
|
||||
import 'models.dart';
|
||||
|
||||
final fidoStateProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<FidoStateNotifier, FidoState?, DevicePath>(
|
||||
.family<FidoStateNotifier, FidoState, DevicePath>(
|
||||
() => throw UnimplementedError(),
|
||||
);
|
||||
|
||||
abstract class FidoStateNotifier extends ApplicationStateNotifier<FidoState?> {
|
||||
abstract class FidoStateNotifier extends ApplicationStateNotifier<FidoState> {
|
||||
Stream<InteractionEvent> reset();
|
||||
Future<PinResult> setPin(String newPin, {String? oldPin});
|
||||
Future<PinResult> unlock(String pin);
|
||||
|
@ -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,9 +79,7 @@ class FingerprintsScreen extends ConsumerWidget {
|
||||
);
|
||||
},
|
||||
data: (fidoState) {
|
||||
return fidoState == null
|
||||
? MessagePageNotInitialized(title: l10n.s_fingerprints)
|
||||
: fidoState.unlocked
|
||||
return fidoState.unlocked
|
||||
? _FidoUnlockedPage(deviceData.node, fidoState)
|
||||
: _FidoLockedPage(deviceData.node, fidoState);
|
||||
});
|
||||
|
@ -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,9 +80,7 @@ class PasskeysScreen extends ConsumerWidget {
|
||||
);
|
||||
},
|
||||
data: (fidoState) {
|
||||
return fidoState == null
|
||||
? MessagePageNotInitialized(title: l10n.s_passkeys)
|
||||
: fidoState.unlocked
|
||||
return fidoState.unlocked
|
||||
? _FidoUnlockedPage(deviceData.node, fidoState)
|
||||
: _FidoLockedPage(deviceData.node, fidoState);
|
||||
});
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user