From ec87865643b460b98ed8ec45dc2b79b245295576 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 8 Jun 2023 17:04:17 +0200 Subject: [PATCH 01/50] add widget --- .../com/yubico/authenticator/MainActivity.kt | 50 ++++++++++- .../yubico/authenticator/oath/OathManager.kt | 9 +- .../yubikit/NfcActivityDispatcher.kt | 77 ++++++++++++++++ .../authenticator/yubikit/NfcActivityState.kt | 25 ++++++ lib/android/app_methods.dart | 6 ++ lib/android/state.dart | 30 +++++++ lib/android/tap_request_dialog.dart | 47 ++++------ .../nfc/main_page_nfc_activity_widget.dart | 41 +++++++++ lib/android/views/nfc/nfc_activity_icon.dart | 90 +++++++++++++++++++ .../views/nfc/nfc_activity_widget.dart | 49 ++++++++++ lib/app/views/main_page.dart | 3 +- 11 files changed, 392 insertions(+), 35 deletions(-) create mode 100644 android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityDispatcher.kt create mode 100644 android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityState.kt create mode 100644 lib/android/views/nfc/main_page_nfc_activity_widget.dart create mode 100644 lib/android/views/nfc/nfc_activity_icon.dart create mode 100644 lib/android/views/nfc/nfc_activity_widget.dart 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 173eb214..36e97a58 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -16,6 +16,11 @@ package com.yubico.authenticator +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.IntentFilter import android.annotation.SuppressLint import android.content.* import android.content.SharedPreferences.OnSharedPreferenceChangeListener @@ -42,16 +47,22 @@ import com.yubico.authenticator.logging.FlutterLog import com.yubico.authenticator.oath.AppLinkMethodChannel import com.yubico.authenticator.oath.OathManager import com.yubico.authenticator.oath.OathViewModel +import com.yubico.authenticator.yubikit.NfcActivityDispatcher +import com.yubico.authenticator.yubikit.NfcActivityListener +import com.yubico.authenticator.yubikit.NfcActivityState import com.yubico.yubikit.android.YubiKitManager import com.yubico.yubikit.android.transport.nfc.NfcConfiguration import com.yubico.yubikit.android.transport.nfc.NfcNotAvailable import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice +import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyManager import com.yubico.yubikit.android.transport.usb.UsbConfiguration +import com.yubico.yubikit.android.transport.usb.UsbYubiKeyManager import com.yubico.yubikit.core.YubiKeyDevice import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.MethodChannel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.json.JSONObject import org.slf4j.LoggerFactory @@ -74,6 +85,20 @@ class MainActivity : FlutterFragmentActivity() { private val logger = LoggerFactory.getLogger(MainActivity::class.java) + private val nfcActivityListener = object : NfcActivityListener { + + var appMethodChannel : AppMethodChannel? = null + + override fun onChange(newState: NfcActivityState) { + appMethodChannel?.let { + logger.debug("setting nfc activity state to ${newState.name}") + it.nfcActivityStateChanged(newState) + } ?: { + logger.warn("cannot set nfc activity state to ${newState.name} - no method channel") + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -85,7 +110,10 @@ class MainActivity : FlutterFragmentActivity() { allowScreenshots(false) - yubikit = YubiKitManager(this) + yubikit = YubiKitManager( + UsbYubiKeyManager(this), + NfcYubiKeyManager(this, NfcActivityDispatcher(nfcActivityListener)) + ) } /** @@ -263,6 +291,11 @@ class MainActivity : FlutterFragmentActivity() { lifecycleScope.launch { try { it.processYubiKey(device) + if (device is NfcYubiKeyDevice) { + device.remove { + appMethodChannel.nfcActivityStateChanged(NfcActivityState.READY) + } + } } catch (e: Throwable) { logger.error("Error processing YubiKey in AppContextManager", e) } @@ -291,6 +324,8 @@ class MainActivity : FlutterFragmentActivity() { appMethodChannel = AppMethodChannel(messenger) appLinkMethodChannel = AppLinkMethodChannel(messenger) + nfcActivityListener.appMethodChannel = appMethodChannel + flutterStreams = listOf( viewModel.deviceInfo.streamTo(this, messenger, "android.devices.deviceInfo"), oathViewModel.sessionState.streamTo(this, messenger, "android.oath.sessionState"), @@ -306,7 +341,8 @@ class MainActivity : FlutterFragmentActivity() { viewModel, oathViewModel, dialogManager, - appPreferences + appPreferences, + nfcActivityListener ) else -> null } @@ -315,6 +351,7 @@ class MainActivity : FlutterFragmentActivity() { } override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) { + nfcActivityListener.appMethodChannel = null flutterStreams.forEach { it.close() } super.cleanUpFlutterEngine(flutterEngine) } @@ -427,6 +464,15 @@ class MainActivity : FlutterFragmentActivity() { JSONObject(mapOf("nfcEnabled" to value)).toString() ) } + + fun nfcActivityStateChanged(activityState: NfcActivityState) { + lifecycleScope.launch(Dispatchers.Main) { + methodChannel.invokeMethod( + "nfcActivityChanged", + JSONObject(mapOf("state" to activityState.value)).toString() + ) + } + } } private fun allowScreenshots(value: Boolean): Boolean { 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 ab88b531..e8b9d37e 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 @@ -27,7 +27,6 @@ import com.yubico.authenticator.* import com.yubico.authenticator.device.Capabilities import com.yubico.authenticator.device.Info import com.yubico.authenticator.device.UnknownDevice -import com.yubico.authenticator.logging.Log import com.yubico.authenticator.oath.data.Code import com.yubico.authenticator.oath.data.CodeType import com.yubico.authenticator.oath.data.Credential @@ -43,6 +42,8 @@ import com.yubico.authenticator.oath.keystore.ClearingMemProvider import com.yubico.authenticator.oath.keystore.KeyProvider import com.yubico.authenticator.oath.keystore.KeyStoreProvider import com.yubico.authenticator.oath.keystore.SharedPrefProvider +import com.yubico.authenticator.yubikit.NfcActivityListener +import com.yubico.authenticator.yubikit.NfcActivityState import com.yubico.authenticator.yubikit.getDeviceInfo import com.yubico.authenticator.yubikit.withConnection import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice @@ -76,6 +77,7 @@ class OathManager( private val oathViewModel: OathViewModel, private val dialogManager: DialogManager, private val appPreferences: AppPreferences, + private val nfcActivityListener: NfcActivityListener ) : AppContextManager { companion object { const val NFC_DATA_CLEANUP_DELAY = 30L * 1000 // 30s @@ -330,9 +332,14 @@ class OathManager( logger.debug( "Successfully read Oath session info (and credentials if unlocked) from connected key" ) + + nfcActivityListener.onChange(NfcActivityState.PROCESSING_FINISHED) } catch (e: Exception) { // OATH not enabled/supported, try to get DeviceInfo over other USB interfaces logger.error("Failed to connect to CCID", e) + + nfcActivityListener.onChange(NfcActivityState.PROCESSING_INTERRUPTED) + if (device.transport == Transport.USB || e is ApplicationNotAvailableException) { val deviceInfo = try { getDeviceInfo(device) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityDispatcher.kt b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityDispatcher.kt new file mode 100644 index 00000000..0bb14899 --- /dev/null +++ b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityDispatcher.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 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. + */ + +package com.yubico.authenticator.yubikit + +import android.app.Activity +import android.nfc.NfcAdapter +import android.nfc.Tag + +import com.yubico.yubikit.android.transport.nfc.NfcConfiguration +import com.yubico.yubikit.android.transport.nfc.NfcDispatcher +import com.yubico.yubikit.android.transport.nfc.NfcReaderDispatcher + +import org.slf4j.LoggerFactory + +interface NfcActivityListener { + fun onChange(newState: NfcActivityState) +} + +class NfcActivityDispatcher(private val listener: NfcActivityListener) : NfcDispatcher { + + private lateinit var adapter: NfcAdapter + private lateinit var yubikitNfcDispatcher: NfcReaderDispatcher + + private val logger = LoggerFactory.getLogger(NfcActivityDispatcher::class.java) + + override fun enable( + activity: Activity, + nfcConfiguration: NfcConfiguration, + handler: NfcDispatcher.OnTagHandler + ) { + adapter = NfcAdapter.getDefaultAdapter(activity) + yubikitNfcDispatcher = NfcReaderDispatcher(adapter) + + logger.debug("enabling yubikit NFC activity dispatcher") + yubikitNfcDispatcher.enable( + activity, + nfcConfiguration, + TagInterceptor(listener, handler) + ) + listener.onChange(NfcActivityState.READY) + } + + override fun disable(activity: Activity) { + listener.onChange(NfcActivityState.NOT_ACTIVE) + yubikitNfcDispatcher.disable(activity) + logger.debug("disabling yubikit NFC activity dispatcher") + } + + class TagInterceptor( + private val listener: NfcActivityListener, + private val tagHandler: NfcDispatcher.OnTagHandler + ) : NfcDispatcher.OnTagHandler { + + private val logger = LoggerFactory.getLogger(TagInterceptor::class.java) + + override fun onTag(tag: Tag) { + listener.onChange(NfcActivityState.PROCESSING_STARTED) + logger.debug("forwarding tag") + tagHandler.onTag(tag) + } + + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityState.kt b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityState.kt new file mode 100644 index 00000000..3d6af5fa --- /dev/null +++ b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityState.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 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. + */ + +package com.yubico.authenticator.yubikit + +enum class NfcActivityState(val value: Int) { + NOT_ACTIVE(0), + READY(1), + PROCESSING_STARTED(2), + PROCESSING_FINISHED(3), + PROCESSING_INTERRUPTED(4) +} \ No newline at end of file diff --git a/lib/android/app_methods.dart b/lib/android/app_methods.dart index 9b47c8d9..75df6b6f 100644 --- a/lib/android/app_methods.dart +++ b/lib/android/app_methods.dart @@ -57,6 +57,12 @@ void setupAppMethodsChannel(WidgetRef ref) { ref.read(androidNfcStateProvider.notifier).setNfcEnabled(nfcEnabled); break; } + case 'nfcActivityChanged': + { + var nfcActivityState = args['state']; + ref.read(androidNfcActivityProvider.notifier).setActivityState(nfcActivityState); + break; + } default: throw PlatformException( code: 'NotImplemented', diff --git a/lib/android/state.dart b/lib/android/state.dart index ea1b46b8..0b3c20cc 100644 --- a/lib/android/state.dart +++ b/lib/android/state.dart @@ -73,6 +73,32 @@ class NfcStateNotifier extends StateNotifier { } } +enum NfcActivity { + notActive, + ready, + processingStarted, + processingFinished, + processingInterrupted, +} + +class NfcActivityNotifier extends StateNotifier { + NfcActivityNotifier() : super(NfcActivity.notActive); + + void setActivityState(int stateValue) { + + var newState = switch (stateValue) { + 0 => NfcActivity.notActive, + 1 => NfcActivity.ready, + 2 => NfcActivity.processingStarted, + 3 => NfcActivity.processingFinished, + 4 => NfcActivity.processingInterrupted, + _ => NfcActivity.notActive + }; + + state = newState; + } +} + final androidSdkVersionProvider = Provider((ref) => -1); final androidNfcSupportProvider = Provider((ref) => false); @@ -80,6 +106,10 @@ final androidNfcSupportProvider = Provider((ref) => false); final androidNfcStateProvider = StateNotifierProvider((ref) => NfcStateNotifier()); +final androidNfcActivityProvider = StateNotifierProvider((ref) => + NfcActivityNotifier() +); + final androidSupportedThemesProvider = StateProvider>((ref) { if (ref.read(androidSdkVersionProvider) < 29) { // the user can select from light or dark theme of the app diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 1a6c1be1..34ae7a91 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Yubico. + * Copyright (C) 2022-2023 Yubico. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,9 +21,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'views/nfc/nfc_activity_widget.dart'; + import '../app/state.dart'; import '../app/views/user_interaction.dart'; -import '../widgets/custom_icons.dart'; const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); @@ -35,6 +36,7 @@ final androidDialogProvider = Provider<_DialogProvider>( class _DialogProvider { final WithContext _withContext; + final Widget _icon = const NfcActivityWidget(width: 64, height: 64); UserInteractionController? _controller; _DialogProvider(this._withContext) { @@ -65,46 +67,29 @@ class _DialogProvider { _controller = null; } - Widget? _getIcon(String? icon) => switch (icon) { - 'nfc' => nfcIcon, - 'success' => const Icon(Icons.check_circle), - 'error' => const Icon(Icons.error), - _ => null, - }; - Future _updateDialogState( String? title, String? description, String? iconName) async { - final icon = _getIcon(iconName); await _withContext((context) async { _controller?.updateContent( title: title, description: description, - icon: icon != null - ? IconTheme( - data: IconTheme.of(context).copyWith(size: 64), - child: icon, - ) - : null, + icon: _icon, ); }); } Future _showDialog( String title, String description, String? iconName) async { - final icon = _getIcon(iconName); - _controller = await _withContext((context) async => promptUserInteraction( - context, - title: title, - description: description, - icon: icon != null - ? IconTheme( - data: IconTheme.of(context).copyWith(size: 64), - child: icon, - ) - : null, - onCancel: () { - _channel.invokeMethod('cancel'); - }, - )); + _controller = await _withContext((context) async { + return promptUserInteraction( + context, + title: title, + description: description, + icon: _icon, + onCancel: () { + _channel.invokeMethod('cancel'); + }, + ); + }); } } diff --git a/lib/android/views/nfc/main_page_nfc_activity_widget.dart b/lib/android/views/nfc/main_page_nfc_activity_widget.dart new file mode 100644 index 00000000..b0526cf9 --- /dev/null +++ b/lib/android/views/nfc/main_page_nfc_activity_widget.dart @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 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. + */ + +import 'package:flutter/material.dart'; +import 'package:yubico_authenticator/android/state.dart'; +import 'package:yubico_authenticator/android/views/nfc/nfc_activity_widget.dart'; + +class MainPageNfcActivityWidget extends StatelessWidget { + final Widget widget; + const MainPageNfcActivityWidget(this.widget, {super.key}); + + @override + Widget build(BuildContext context) { + return NfcActivityWidget( + width: 128.0, + height: 128.0, + iconView: (nfcActivityState) { + return Opacity( + opacity: switch (nfcActivityState) { + NfcActivity.processingStarted => 1.0, + _ => 0.8 + }, + child: widget, + ); + }, + ); + } +} diff --git a/lib/android/views/nfc/nfc_activity_icon.dart b/lib/android/views/nfc/nfc_activity_icon.dart new file mode 100644 index 00000000..9b160bb4 --- /dev/null +++ b/lib/android/views/nfc/nfc_activity_icon.dart @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2023 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. + */ + +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:yubico_authenticator/android/state.dart'; + +/// Default icon for [NfcActivityWidget] +class NfcActivityIcon extends StatelessWidget { + final NfcActivity nfcActivity; + + const NfcActivityIcon(this.nfcActivity, {super.key}); + + @override + Widget build(BuildContext context) => switch (nfcActivity) { + NfcActivity.processingStarted => const _NfcIconWithOpacity(1.0), + _ => const _NfcIconWithOpacity(0.8) + }; +} + +class _NfcIconWithOpacity extends StatelessWidget { + final double opacity; + + const _NfcIconWithOpacity(this.opacity); + + @override + Widget build(BuildContext context) => Opacity( + opacity: opacity, + child: const _NfcIcon(), + ); +} + +class _NfcIcon extends StatelessWidget { + const _NfcIcon(); + + @override + Widget build(BuildContext context) { + final theme = IconTheme.of(context); + return LayoutBuilder( + builder: (BuildContext buildContext, BoxConstraints constraints) => + CustomPaint( + size: Size.copy(constraints.biggest), + painter: _NfcIconPainter(theme.color ?? Colors.black), + ), + ); + } +} + +class _NfcIconPainter extends CustomPainter { + final Color color; + + _NfcIconPainter(this.color); + + @override + void paint(Canvas canvas, Size size) { + final step = size.width / 4; + const sweep = pi / 4; + + final paint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round + ..strokeWidth = step / 2; + + final rect = + Offset(size.width * -1.7, 0) & Size(size.width * 2, size.height); + for (var i = 0; i < 3; i++) { + canvas.drawArc(rect.inflate(i * step), -sweep / 2, sweep, false, paint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return false; + } +} diff --git a/lib/android/views/nfc/nfc_activity_widget.dart b/lib/android/views/nfc/nfc_activity_widget.dart new file mode 100644 index 00000000..703ffa08 --- /dev/null +++ b/lib/android/views/nfc/nfc_activity_widget.dart @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 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. + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; + +import '../../../app/logging.dart'; +import '../../state.dart'; +import 'nfc_activity_icon.dart'; + +final _logger = Logger('NfcActivityWidget'); + +class NfcActivityWidget extends ConsumerWidget { + final double width; + final double height; + final Widget Function(NfcActivity)? iconView; + + const NfcActivityWidget( + {super.key, this.width = 32.0, this.height = 32.0, this.iconView}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final NfcActivity nfcActivityState = ref.watch(androidNfcActivityProvider); + + _logger.debug('State for NfcActivityWidget changed to $nfcActivityState'); + + return IgnorePointer( + child: SizedBox( + width: width, + height: height, + child: iconView?.call(nfcActivityState) ?? + NfcActivityIcon(nfcActivityState)), + ); + } +} diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index 229272c3..5091dcf6 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -20,6 +20,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../../android/app_methods.dart'; import '../../android/state.dart'; +import '../../android/views/nfc/main_page_nfc_activity_widget.dart'; import '../../exception/cancellation_exception.dart'; import '../../core/state.dart'; import '../../fido/views/fido_screen.dart'; @@ -82,7 +83,7 @@ class MainPage extends ConsumerWidget { var hasNfcSupport = ref.watch(androidNfcSupportProvider); var isNfcEnabled = ref.watch(androidNfcStateProvider); return MessagePage( - graphic: noKeyImage, + graphic: MainPageNfcActivityWidget(noKeyImage), message: hasNfcSupport && isNfcEnabled ? l10n.l_insert_or_tap_yk : l10n.l_insert_yk, From 44844c7d90a38775ca29cdcdb37fdf65d7f640a3 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 26 Sep 2023 13:23:36 +0200 Subject: [PATCH 02/50] add widgets for different nfc states --- .../com/yubico/authenticator/DialogManager.kt | 19 ++-- .../yubico/authenticator/oath/OathManager.kt | 19 ++-- lib/android/tap_request_dialog.dart | 95 ++++++++++--------- .../nfc/main_page_nfc_activity_widget.dart | 19 ++-- lib/android/views/nfc/nfc_activity_icon.dart | 54 ++++++++++- lib/app/views/horizontal_shake.dart | 88 +++++++++++++++++ 6 files changed, 215 insertions(+), 79 deletions(-) create mode 100644 lib/app/views/horizontal_shake.dart diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/DialogManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/DialogManager.kt index c3df2e04..f6cf0eea 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/DialogManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/DialogManager.kt @@ -18,18 +18,15 @@ package com.yubico.authenticator import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.MethodChannel -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json typealias OnDialogCancelled = suspend () -> Unit -enum class DialogIcon(val value: Int) { - Nfc(0), - Success(1), - Failure(2); -} - enum class DialogTitle(val value: Int) { TapKey(0), OperationSuccessful(1), @@ -52,7 +49,6 @@ class DialogManager(messenger: BinaryMessenger, private val coroutineScope: Coro } fun showDialog( - dialogIcon: DialogIcon, dialogTitle: DialogTitle, dialogDescriptionId: Int, cancelled: OnDialogCancelled? @@ -64,8 +60,7 @@ class DialogManager(messenger: BinaryMessenger, private val coroutineScope: Coro Json.encodeToString( mapOf( "title" to dialogTitle.value, - "description" to dialogDescriptionId, - "icon" to dialogIcon.value + "description" to dialogDescriptionId ) ) ) @@ -73,7 +68,6 @@ class DialogManager(messenger: BinaryMessenger, private val coroutineScope: Coro } suspend fun updateDialogState( - dialogIcon: DialogIcon? = null, dialogTitle: DialogTitle, dialogDescriptionId: Int? = null, ) { @@ -82,8 +76,7 @@ class DialogManager(messenger: BinaryMessenger, private val coroutineScope: Coro Json.encodeToString( mapOf( "title" to dialogTitle.value, - "description" to dialogDescriptionId, - "icon" to dialogIcon?.value + "description" to dialogDescriptionId ) ) ) 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 2157eba6..9253e530 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 @@ -339,14 +339,10 @@ class OathManager( logger.debug( "Successfully read Oath session info (and credentials if unlocked) from connected key" ) - - nfcActivityListener.onChange(NfcActivityState.PROCESSING_FINISHED) } catch (e: Exception) { // OATH not enabled/supported, try to get DeviceInfo over other USB interfaces logger.error("Failed to connect to CCID", e) - nfcActivityListener.onChange(NfcActivityState.PROCESSING_INTERRUPTED) - if (device.transport == Transport.USB || e is ApplicationNotAvailableException) { val deviceInfo = try { getDeviceInfo(device) @@ -458,7 +454,7 @@ class OathManager( oathViewModel.setSessionState(Session(it, remembered)) // fetch credentials after unlocking only if the YubiKey is connected over USB - if ( appViewModel.connectedYubiKey.value != null) { + if (appViewModel.connectedYubiKey.value != null) { oathViewModel.updateCredentials(calculateOathCodes(it)) } } @@ -597,7 +593,10 @@ class OathManager( logger.error("IOException when accessing USB device: ", ioException) clearCodes() } catch (illegalStateException: IllegalStateException) { - logger.error("IllegalStateException when accessing USB device: ", illegalStateException) + logger.error( + "IllegalStateException when accessing USB device: ", + illegalStateException + ) clearCodes() } } @@ -748,24 +747,24 @@ class OathManager( block.invoke(it.value) }) } - dialogManager.showDialog(DialogIcon.Nfc, DialogTitle.TapKey, oathActionDescription.id) { + dialogManager.showDialog(DialogTitle.TapKey, oathActionDescription.id) { logger.debug("Cancelled Dialog {}", oathActionDescription.name) pendingAction?.invoke(Result.failure(CancellationException())) pendingAction = null } } + nfcActivityListener.onChange(NfcActivityState.PROCESSING_FINISHED) dialogManager.updateDialogState( - dialogIcon = DialogIcon.Success, dialogTitle = DialogTitle.OperationSuccessful ) // TODO: This delays the closing of the dialog, but also the return value - delay(500) + delay(1500) return result } catch (cancelled: CancellationException) { throw cancelled } catch (error: Throwable) { + nfcActivityListener.onChange(NfcActivityState.PROCESSING_INTERRUPTED) dialogManager.updateDialogState( - dialogIcon = DialogIcon.Failure, dialogTitle = DialogTitle.OperationFailed, dialogDescriptionId = OathActionDescription.ActionFailure.id ) diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 7e80473c..fa0ef314 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -22,31 +22,30 @@ import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'views/nfc/nfc_activity_widget.dart'; - import '../app/state.dart'; import '../app/views/user_interaction.dart'; +import 'views/nfc/nfc_activity_widget.dart'; const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); // _DDesc contains id of title resource for the dialog -enum _DTitle { +enum _DialogTitle { tapKey, operationSuccessful, operationFailed, invalid; - static _DTitle fromId(int? id) => + static _DialogTitle fromId(int? id) => const { - 0: _DTitle.tapKey, - 1: _DTitle.operationSuccessful, - 2: _DTitle.operationFailed + 0: _DialogTitle.tapKey, + 1: _DialogTitle.operationSuccessful, + 2: _DialogTitle.operationFailed }[id] ?? - _DTitle.invalid; + _DialogTitle.invalid; } // _DDesc contains action description in the dialog -enum _DDesc { +enum _DialogDescription { // oath descriptions oathResetApplet, oathUnlockSession, @@ -62,20 +61,21 @@ enum _DDesc { static const int dialogDescriptionOathIndex = 100; - static _DDesc fromId(int? id) => + static _DialogDescription fromId(int? id) => const { - dialogDescriptionOathIndex + 0: _DDesc.oathResetApplet, - dialogDescriptionOathIndex + 1: _DDesc.oathUnlockSession, - dialogDescriptionOathIndex + 2: _DDesc.oathSetPassword, - dialogDescriptionOathIndex + 3: _DDesc.oathUnsetPassword, - dialogDescriptionOathIndex + 4: _DDesc.oathAddAccount, - dialogDescriptionOathIndex + 5: _DDesc.oathRenameAccount, - dialogDescriptionOathIndex + 6: _DDesc.oathDeleteAccount, - dialogDescriptionOathIndex + 7: _DDesc.oathCalculateCode, - dialogDescriptionOathIndex + 8: _DDesc.oathActionFailure, - dialogDescriptionOathIndex + 9: _DDesc.oathAddMultipleAccounts + dialogDescriptionOathIndex + 0: _DialogDescription.oathResetApplet, + dialogDescriptionOathIndex + 1: _DialogDescription.oathUnlockSession, + dialogDescriptionOathIndex + 2: _DialogDescription.oathSetPassword, + dialogDescriptionOathIndex + 3: _DialogDescription.oathUnsetPassword, + dialogDescriptionOathIndex + 4: _DialogDescription.oathAddAccount, + dialogDescriptionOathIndex + 5: _DialogDescription.oathRenameAccount, + dialogDescriptionOathIndex + 6: _DialogDescription.oathDeleteAccount, + dialogDescriptionOathIndex + 7: _DialogDescription.oathCalculateCode, + dialogDescriptionOathIndex + 8: _DialogDescription.oathActionFailure, + dialogDescriptionOathIndex + 9: + _DialogDescription.oathAddMultipleAccounts }[id] ?? - _DDesc.invalid; + _DialogDescription.invalid; } final androidDialogProvider = Provider<_DialogProvider>( @@ -97,11 +97,10 @@ class _DialogProvider { _closeDialog(); break; case 'show': - await _showDialog(args['title'], args['description'], args['icon']); + await _showDialog(args['title'], args['description']); break; case 'state': - await _updateDialogState( - args['title'], args['description'], args['icon']); + await _updateDialogState(args['title'], args['description']); break; default: throw PlatformException( @@ -119,44 +118,50 @@ class _DialogProvider { String _getTitle(BuildContext context, int? titleId) { final l10n = AppLocalizations.of(context)!; - return switch (_DTitle.fromId(titleId)) { - _DTitle.tapKey => l10n.s_nfc_dialog_tap_key, - _DTitle.operationSuccessful => l10n.s_nfc_dialog_operation_success, - _DTitle.operationFailed => l10n.s_nfc_dialog_operation_failed, + return switch (_DialogTitle.fromId(titleId)) { + _DialogTitle.tapKey => l10n.s_nfc_dialog_tap_key, + _DialogTitle.operationSuccessful => l10n.s_nfc_dialog_operation_success, + _DialogTitle.operationFailed => l10n.s_nfc_dialog_operation_failed, _ => '' }; } String _getDialogDescription(BuildContext context, int? descriptionId) { final l10n = AppLocalizations.of(context)!; - return switch (_DDesc.fromId(descriptionId)) { - _DDesc.oathResetApplet => l10n.s_nfc_dialog_oath_reset, - _DDesc.oathUnlockSession => l10n.s_nfc_dialog_oath_unlock, - _DDesc.oathSetPassword => l10n.s_nfc_dialog_oath_set_password, - _DDesc.oathUnsetPassword => l10n.s_nfc_dialog_oath_unset_password, - _DDesc.oathAddAccount => l10n.s_nfc_dialog_oath_add_account, - _DDesc.oathRenameAccount => l10n.s_nfc_dialog_oath_rename_account, - _DDesc.oathDeleteAccount => l10n.s_nfc_dialog_oath_delete_account, - _DDesc.oathCalculateCode => l10n.s_nfc_dialog_oath_calculate_code, - _DDesc.oathActionFailure => l10n.s_nfc_dialog_oath_failure, - _DDesc.oathAddMultipleAccounts => l10n.s_nfc_dialog_oath_add_multiple_accounts, - _ => '' + return switch (_DialogDescription.fromId(descriptionId)) { + _DialogDescription.oathResetApplet => l10n.s_nfc_dialog_oath_reset, + _DialogDescription.oathUnlockSession => l10n.s_nfc_dialog_oath_unlock, + _DialogDescription.oathSetPassword => l10n.s_nfc_dialog_oath_set_password, + _DialogDescription.oathUnsetPassword => + l10n.s_nfc_dialog_oath_unset_password, + _DialogDescription.oathAddAccount => l10n.s_nfc_dialog_oath_add_account, + _DialogDescription.oathRenameAccount => + l10n.s_nfc_dialog_oath_rename_account, + _DialogDescription.oathDeleteAccount => + l10n.s_nfc_dialog_oath_delete_account, + _DialogDescription.oathCalculateCode => + l10n.s_nfc_dialog_oath_calculate_code, + _DialogDescription.oathActionFailure => l10n.s_nfc_dialog_oath_failure, + _DialogDescription.oathAddMultipleAccounts => + l10n.s_nfc_dialog_oath_add_multiple_accounts, + _ => ' ' }; } - Future _updateDialogState( - int? title, int? description, int? iconName) async { + Future _updateDialogState(int? title, int? description) async { await _withContext((context) async { _controller?.updateContent( title: _getTitle(context, title), description: _getDialogDescription(context, description), - icon: _icon, + icon: (_DialogDescription.fromId(description) != + _DialogDescription.oathActionFailure) + ? _icon + : const Icon(Icons.warning_amber_rounded, size: 64), ); }); } - Future _showDialog( - int title, int description, int? iconName) async { + Future _showDialog(int title, int description) async { _controller = await _withContext((context) async { return promptUserInteraction( context, diff --git a/lib/android/views/nfc/main_page_nfc_activity_widget.dart b/lib/android/views/nfc/main_page_nfc_activity_widget.dart index b0526cf9..bc25493c 100644 --- a/lib/android/views/nfc/main_page_nfc_activity_widget.dart +++ b/lib/android/views/nfc/main_page_nfc_activity_widget.dart @@ -17,9 +17,11 @@ import 'package:flutter/material.dart'; import 'package:yubico_authenticator/android/state.dart'; import 'package:yubico_authenticator/android/views/nfc/nfc_activity_widget.dart'; +import 'package:yubico_authenticator/app/views/horizontal_shake.dart'; class MainPageNfcActivityWidget extends StatelessWidget { final Widget widget; + const MainPageNfcActivityWidget(this.widget, {super.key}); @override @@ -28,13 +30,16 @@ class MainPageNfcActivityWidget extends StatelessWidget { width: 128.0, height: 128.0, iconView: (nfcActivityState) { - return Opacity( - opacity: switch (nfcActivityState) { - NfcActivity.processingStarted => 1.0, - _ => 0.8 - }, - child: widget, - ); + return switch (nfcActivityState) { + NfcActivity.ready => HorizontalShake( + shakeCount: 2, + shakeDuration: const Duration(milliseconds: 50), + delayBetweenShakesDuration: const Duration(seconds: 6), + startupDelay: const Duration(seconds: 3), + child: widget, + ), + _ => widget + }; }, ); } diff --git a/lib/android/views/nfc/nfc_activity_icon.dart b/lib/android/views/nfc/nfc_activity_icon.dart index 9b160bb4..7eff20c5 100644 --- a/lib/android/views/nfc/nfc_activity_icon.dart +++ b/lib/android/views/nfc/nfc_activity_icon.dart @@ -15,9 +15,13 @@ */ import 'dart:math'; +import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:yubico_authenticator/android/state.dart'; +import 'package:yubico_authenticator/app/views/horizontal_shake.dart'; + +import '../../../theme.dart'; /// Default icon for [NfcActivityWidget] class NfcActivityIcon extends StatelessWidget { @@ -27,8 +31,32 @@ class NfcActivityIcon extends StatelessWidget { @override Widget build(BuildContext context) => switch (nfcActivity) { - NfcActivity.processingStarted => const _NfcIconWithOpacity(1.0), - _ => const _NfcIconWithOpacity(0.8) + NfcActivity.ready => const HorizontalShake( + startupDelay: Duration(seconds: 4), + child: _NfcIconWithOpacity(0.8)), + NfcActivity.processingStarted => Stack( + fit: StackFit.loose, + children: [ + ImageFiltered( + imageFilter: ImageFilter.blur( + sigmaX: 7.0, + sigmaY: 7.0, + tileMode: TileMode.decal, + ), + child: const Opacity( + opacity: 0.6, + child: _NfcIcon( + color: accentGreen, + ), + ), + ), + const _NfcIcon(), + ], + ), + + NfcActivity.processingInterrupted => + const _NfcIconWrapper(Icons.warning_amber_rounded), + _ => const _NfcIcon(), }; } @@ -44,8 +72,26 @@ class _NfcIconWithOpacity extends StatelessWidget { ); } +class _NfcIconWrapper extends StatelessWidget { + final IconData _iconData; + + const _NfcIconWrapper(this._iconData); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext buildContext, BoxConstraints constraints) => + Icon( + _iconData, + size: constraints.biggest.width, + )); + } +} + class _NfcIcon extends StatelessWidget { - const _NfcIcon(); + final Color? color; + + const _NfcIcon({this.color}); @override Widget build(BuildContext context) { @@ -54,7 +100,7 @@ class _NfcIcon extends StatelessWidget { builder: (BuildContext buildContext, BoxConstraints constraints) => CustomPaint( size: Size.copy(constraints.biggest), - painter: _NfcIconPainter(theme.color ?? Colors.black), + painter: _NfcIconPainter(color ?? theme.color ?? Colors.black), ), ); } diff --git a/lib/app/views/horizontal_shake.dart b/lib/app/views/horizontal_shake.dart new file mode 100644 index 00000000..31df2fc4 --- /dev/null +++ b/lib/app/views/horizontal_shake.dart @@ -0,0 +1,88 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class HorizontalShake extends StatefulWidget { + final Widget child; + final double shakeAmount; + final int shakeCount; + final Duration shakeDuration; + final Duration delayBetweenShakesDuration; + final Duration startupDelay; + + const HorizontalShake( + {super.key, + required this.child, + this.shakeAmount = 2, + this.shakeCount = 3, + this.shakeDuration = const Duration(milliseconds: 50), + this.delayBetweenShakesDuration = const Duration(seconds: 3), + this.startupDelay = const Duration(seconds: 0)}); + + @override + State createState() => _HorizontalShakeState(); +} + +class _HorizontalShakeState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + late Timer delayTimer; + + int _shakeCounter = 0; + + @override + void initState() { + super.initState(); + _controller = + AnimationController(vsync: this, duration: widget.shakeDuration); + + _controller.addListener(() async { + if (_controller.isCompleted || _controller.isDismissed) { + var delay = const Duration(milliseconds: 0); + if (_shakeCounter++ > widget.shakeCount * 2) { + delay = widget.delayBetweenShakesDuration; + _shakeCounter = 0; + } + + delayTimer = Timer(delay, () async { + if (_controller.isCompleted) { + await _controller.reverse(); + } else if (_controller.isDismissed) { + await _controller.forward(); + } + }); + } + }); + + _animation = + Tween(begin: 0, end: widget.shakeAmount) + .animate( + CurvedAnimation(parent: _controller, curve: Curves.ease), + ); + + delayTimer = Timer(widget.startupDelay, () { + _controller.forward(); + }); + } + + @override + void dispose() { + delayTimer.cancel(); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (BuildContext context, Widget? child) { + return Transform.translate( + offset: Offset(_animation.value, 0), + child: widget.child, + ); + }, + ); + } +} From 39dfa1775c9201dc97f8f0a106c7a6017244d6fb Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 26 Sep 2023 14:20:24 +0200 Subject: [PATCH 03/50] add and use fade_in_out widget --- lib/android/views/nfc/fade_in_out.dart | 94 ++++++++++++++++++++ lib/android/views/nfc/nfc_activity_icon.dart | 28 +++--- lib/app/views/horizontal_shake.dart | 4 + 3 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 lib/android/views/nfc/fade_in_out.dart diff --git a/lib/android/views/nfc/fade_in_out.dart b/lib/android/views/nfc/fade_in_out.dart new file mode 100644 index 00000000..2cd270a1 --- /dev/null +++ b/lib/android/views/nfc/fade_in_out.dart @@ -0,0 +1,94 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +/// Repeatedly fades in and out its child +class FadeInOut extends StatefulWidget { + final Widget child; + final double minOpacity; + final double maxOpacity; + final Duration pulseDuration; + final Duration delayBetweenShakesDuration; + final Duration startupDelay; + + const FadeInOut( + {super.key, + required this.child, + this.minOpacity = 0.0, + this.maxOpacity = 1.0, + this.pulseDuration = const Duration(milliseconds: 300), + this.delayBetweenShakesDuration = const Duration(seconds: 3), + this.startupDelay = Duration.zero}); + + @override + State createState() => _FadeInOutState(); +} + +class _FadeInOutState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + late Timer delayTimer; + + bool playingForward = true; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: + Duration(milliseconds: widget.pulseDuration.inMilliseconds ~/ 2)); + + _controller.addListener(() async { + if (_controller.isCompleted || _controller.isDismissed) { + playingForward = !playingForward; + var delay = Duration.zero; + if (playingForward == true) { + delay = widget.delayBetweenShakesDuration; + } + + if (delayTimer.isActive) { + delayTimer.cancel(); + } + + delayTimer = Timer(delay, () async { + if (_controller.isCompleted) { + await _controller.reverse(); + } else if (_controller.isDismissed) { + await _controller.forward(); + } + }); + } + }); + + _animation = + Tween(begin: widget.minOpacity, end: widget.maxOpacity).animate( + CurvedAnimation(parent: _controller, curve: Curves.ease), + ); + + delayTimer = Timer(widget.startupDelay, () { + _controller.forward(); + }); + } + + @override + void dispose() { + delayTimer.cancel(); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (BuildContext context, Widget? child) { + return Opacity( + opacity: _animation.value, + child: widget.child, + ); + }, + ); + } +} diff --git a/lib/android/views/nfc/nfc_activity_icon.dart b/lib/android/views/nfc/nfc_activity_icon.dart index 7eff20c5..1e61bf01 100644 --- a/lib/android/views/nfc/nfc_activity_icon.dart +++ b/lib/android/views/nfc/nfc_activity_icon.dart @@ -19,6 +19,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:yubico_authenticator/android/state.dart'; +import 'package:yubico_authenticator/android/views/nfc/fade_in_out.dart'; import 'package:yubico_authenticator/app/views/horizontal_shake.dart'; import '../../../theme.dart'; @@ -37,23 +38,28 @@ class NfcActivityIcon extends StatelessWidget { NfcActivity.processingStarted => Stack( fit: StackFit.loose, children: [ - ImageFiltered( - imageFilter: ImageFilter.blur( - sigmaX: 7.0, - sigmaY: 7.0, - tileMode: TileMode.decal, - ), - child: const Opacity( - opacity: 0.6, - child: _NfcIcon( - color: accentGreen, + FadeInOut( + minOpacity: 0.1, + maxOpacity: 1.0, + pulseDuration: const Duration(milliseconds: 300), + delayBetweenShakesDuration: const Duration(milliseconds: 20), + child: ImageFiltered( + imageFilter: ImageFilter.blur( + sigmaX: 7.0, + sigmaY: 7.0, + tileMode: TileMode.decal, + ), + child: const Opacity( + opacity: 0.6, + child: _NfcIcon( + color: accentGreen, + ), ), ), ), const _NfcIcon(), ], ), - NfcActivity.processingInterrupted => const _NfcIconWrapper(Icons.warning_amber_rounded), _ => const _NfcIcon(), diff --git a/lib/app/views/horizontal_shake.dart b/lib/app/views/horizontal_shake.dart index 31df2fc4..a1863b1a 100644 --- a/lib/app/views/horizontal_shake.dart +++ b/lib/app/views/horizontal_shake.dart @@ -45,6 +45,10 @@ class _HorizontalShakeState extends State _shakeCounter = 0; } + if (delayTimer.isActive) { + delayTimer.cancel(); + } + delayTimer = Timer(delay, () async { if (_controller.isCompleted) { await _controller.reverse(); From 4ec26984e21992a057f1b50de56d00dd87398558 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 31 Jul 2024 12:50:34 +0200 Subject: [PATCH 04/50] fix formatting --- lib/app/views/horizontal_shake.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/app/views/horizontal_shake.dart b/lib/app/views/horizontal_shake.dart index a1863b1a..adb156ea 100644 --- a/lib/app/views/horizontal_shake.dart +++ b/lib/app/views/horizontal_shake.dart @@ -59,9 +59,7 @@ class _HorizontalShakeState extends State } }); - _animation = - Tween(begin: 0, end: widget.shakeAmount) - .animate( + _animation = Tween(begin: 0, end: widget.shakeAmount).animate( CurvedAnimation(parent: _controller, curve: Curves.ease), ); From 69ff029e4326e5ac475ebb7eaa68bdab8bdd541b Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 31 Jul 2024 13:31:19 +0200 Subject: [PATCH 05/50] revert naming --- lib/android/tap_request_dialog.dart | 72 +++++++++++++---------------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 925160e1..78f13f09 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -29,23 +29,23 @@ import 'views/nfc/nfc_activity_widget.dart'; const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); // _DDesc contains id of title resource for the dialog -enum _DialogTitle { +enum _DTitle { tapKey, operationSuccessful, operationFailed, invalid; - static _DialogTitle fromId(int? id) => + static _DTitle fromId(int? id) => const { - 0: _DialogTitle.tapKey, - 1: _DialogTitle.operationSuccessful, - 2: _DialogTitle.operationFailed + 0: _DTitle.tapKey, + 1: _DTitle.operationSuccessful, + 2: _DTitle.operationFailed }[id] ?? - _DialogTitle.invalid; + _DTitle.invalid; } // _DDesc contains action description in the dialog -enum _DialogDescription { +enum _DDesc { // oath descriptions oathResetApplet, oathUnlockSession, @@ -71,7 +71,7 @@ enum _DialogDescription { static const int dialogDescriptionOathIndex = 100; static const int dialogDescriptionFidoIndex = 200; - static _DialogDescription fromId(int? id) => + static _DDesc fromId(int? id) => const { dialogDescriptionOathIndex + 0: oathResetApplet, dialogDescriptionOathIndex + 1: oathUnlockSession, @@ -91,7 +91,7 @@ enum _DialogDescription { dialogDescriptionFidoIndex + 5: fidoRenameFingerprint, dialogDescriptionFidoIndex + 6: fidoActionFailure, }[id] ?? - _DialogDescription.invalid; + _DDesc.invalid; } final androidDialogProvider = Provider<_DialogProvider>( @@ -134,42 +134,35 @@ class _DialogProvider { String _getTitle(BuildContext context, int? titleId) { final l10n = AppLocalizations.of(context)!; - return switch (_DialogTitle.fromId(titleId)) { - _DialogTitle.tapKey => l10n.l_nfc_dialog_tap_key, - _DialogTitle.operationSuccessful => l10n.s_nfc_dialog_operation_success, - _DialogTitle.operationFailed => l10n.s_nfc_dialog_operation_failed, + return switch (_DTitle.fromId(titleId)) { + _DTitle.tapKey => l10n.l_nfc_dialog_tap_key, + _DTitle.operationSuccessful => l10n.s_nfc_dialog_operation_success, + _DTitle.operationFailed => l10n.s_nfc_dialog_operation_failed, _ => '' }; } String _getDialogDescription(BuildContext context, int? descriptionId) { final l10n = AppLocalizations.of(context)!; - return switch (_DialogDescription.fromId(descriptionId)) { - _DialogDescription.oathResetApplet => l10n.s_nfc_dialog_oath_reset, - _DialogDescription.oathUnlockSession => l10n.s_nfc_dialog_oath_unlock, - _DialogDescription.oathSetPassword => l10n.s_nfc_dialog_oath_set_password, - _DialogDescription.oathUnsetPassword => - l10n.s_nfc_dialog_oath_unset_password, - _DialogDescription.oathAddAccount => l10n.s_nfc_dialog_oath_add_account, - _DialogDescription.oathRenameAccount => - l10n.s_nfc_dialog_oath_rename_account, - _DialogDescription.oathDeleteAccount => - l10n.s_nfc_dialog_oath_delete_account, - _DialogDescription.oathCalculateCode => - l10n.s_nfc_dialog_oath_calculate_code, - _DialogDescription.oathActionFailure => l10n.s_nfc_dialog_oath_failure, - _DialogDescription.oathAddMultipleAccounts => + return switch (_DDesc.fromId(descriptionId)) { + _DDesc.oathResetApplet => l10n.s_nfc_dialog_oath_reset, + _DDesc.oathUnlockSession => l10n.s_nfc_dialog_oath_unlock, + _DDesc.oathSetPassword => l10n.s_nfc_dialog_oath_set_password, + _DDesc.oathUnsetPassword => l10n.s_nfc_dialog_oath_unset_password, + _DDesc.oathAddAccount => l10n.s_nfc_dialog_oath_add_account, + _DDesc.oathRenameAccount => l10n.s_nfc_dialog_oath_rename_account, + _DDesc.oathDeleteAccount => l10n.s_nfc_dialog_oath_delete_account, + _DDesc.oathCalculateCode => l10n.s_nfc_dialog_oath_calculate_code, + _DDesc.oathActionFailure => l10n.s_nfc_dialog_oath_failure, + _DDesc.oathAddMultipleAccounts => l10n.s_nfc_dialog_oath_add_multiple_accounts, - _DialogDescription.fidoResetApplet => l10n.s_nfc_dialog_fido_reset, - _DialogDescription.fidoUnlockSession => l10n.s_nfc_dialog_fido_unlock, - _DialogDescription.fidoSetPin => l10n.l_nfc_dialog_fido_set_pin, - _DialogDescription.fidoDeleteCredential => - l10n.s_nfc_dialog_fido_delete_credential, - _DialogDescription.fidoDeleteFingerprint => - l10n.s_nfc_dialog_fido_delete_fingerprint, - _DialogDescription.fidoRenameFingerprint => - l10n.s_nfc_dialog_fido_rename_fingerprint, - _DialogDescription.fidoActionFailure => l10n.s_nfc_dialog_fido_failure, + _DDesc.fidoResetApplet => l10n.s_nfc_dialog_fido_reset, + _DDesc.fidoUnlockSession => l10n.s_nfc_dialog_fido_unlock, + _DDesc.fidoSetPin => l10n.l_nfc_dialog_fido_set_pin, + _DDesc.fidoDeleteCredential => l10n.s_nfc_dialog_fido_delete_credential, + _DDesc.fidoDeleteFingerprint => l10n.s_nfc_dialog_fido_delete_fingerprint, + _DDesc.fidoRenameFingerprint => l10n.s_nfc_dialog_fido_rename_fingerprint, + _DDesc.fidoActionFailure => l10n.s_nfc_dialog_fido_failure, _ => '' }; } @@ -179,8 +172,7 @@ class _DialogProvider { _controller?.updateContent( title: _getTitle(context, title), description: _getDialogDescription(context, description), - icon: (_DialogDescription.fromId(description) != - _DialogDescription.oathActionFailure) + icon: (_DDesc.fromId(description) != _DDesc.oathActionFailure) ? _icon : const Icon(Icons.warning_amber_rounded, size: 64), ); From d8a55a0297c21bd84015229f540c47c689c621e5 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 28 Aug 2024 16:27:46 +0200 Subject: [PATCH 06/50] first version of the feature, wip still --- .../com/yubico/authenticator/DialogManager.kt | 39 +- .../com/yubico/authenticator/MainActivity.kt | 5 +- .../fido/FidoActionDescription.kt | 34 -- .../fido/FidoConnectionHelper.kt | 20 +- .../yubico/authenticator/fido/FidoManager.kt | 14 +- .../authenticator/fido/FidoResetHelper.kt | 2 +- .../management/ManagementConnectionHelper.kt | 6 +- .../oath/OathActionDescription.kt | 35 -- .../yubico/authenticator/oath/OathManager.kt | 200 +++++---- .../authenticator/oath/data/Credential.kt | 10 +- .../yubikit/NfcActivityDispatcher.kt | 5 +- android/settings.gradle | 2 +- lib/android/fido/state.dart | 145 +++++-- lib/android/init.dart | 3 + lib/android/oath/state.dart | 198 +++++++-- lib/android/tap_request_dialog.dart | 352 ++++++++------- .../nfc/nfc_activity_command_listener.dart | 84 ++++ .../views/nfc/nfc_activity_overlay.dart | 172 ++++++++ lib/app/models.dart | 43 ++ lib/app/models.freezed.dart | 407 ++++++++++++++++++ lib/l10n/app_de.arb | 107 ++++- lib/l10n/app_en.arb | 107 ++++- lib/l10n/app_fr.arb | 107 ++++- lib/l10n/app_ja.arb | 101 ++++- lib/l10n/app_pl.arb | 101 ++++- lib/theme.dart | 9 +- lib/widgets/pulsing.dart | 68 +++ 27 files changed, 1830 insertions(+), 546 deletions(-) delete mode 100644 android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoActionDescription.kt delete mode 100644 android/app/src/main/kotlin/com/yubico/authenticator/oath/OathActionDescription.kt create mode 100644 lib/android/views/nfc/nfc_activity_command_listener.dart create mode 100644 lib/android/views/nfc/nfc_activity_overlay.dart create mode 100644 lib/widgets/pulsing.dart diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/DialogManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/DialogManager.kt index f6cf0eea..425f774d 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/DialogManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/DialogManager.kt @@ -22,17 +22,9 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json typealias OnDialogCancelled = suspend () -> Unit -enum class DialogTitle(val value: Int) { - TapKey(0), - OperationSuccessful(1), - OperationFailed(2) -} - class DialogManager(messenger: BinaryMessenger, private val coroutineScope: CoroutineScope) { private val channel = MethodChannel(messenger, "com.yubico.authenticator.channel.dialog") @@ -48,40 +40,13 @@ class DialogManager(messenger: BinaryMessenger, private val coroutineScope: Coro } } - fun showDialog( - dialogTitle: DialogTitle, - dialogDescriptionId: Int, - cancelled: OnDialogCancelled? - ) { + fun showDialog(cancelled: OnDialogCancelled?) { onCancelled = cancelled coroutineScope.launch { - channel.invoke( - "show", - Json.encodeToString( - mapOf( - "title" to dialogTitle.value, - "description" to dialogDescriptionId - ) - ) - ) + channel.invoke("show", null) } } - suspend fun updateDialogState( - dialogTitle: DialogTitle, - dialogDescriptionId: Int? = null, - ) { - channel.invoke( - "state", - Json.encodeToString( - mapOf( - "title" to dialogTitle.value, - "description" to dialogDescriptionId - ) - ) - ) - } - suspend fun closeDialog() { channel.invoke("close", NULL) } 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 62fa7546..fe941f4d 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -17,7 +17,6 @@ package com.yubico.authenticator import android.content.BroadcastReceiver -import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.IntentFilter @@ -355,13 +354,14 @@ class MainActivity : FlutterFragmentActivity() { try { it.processYubiKey(device) if (device is NfcYubiKeyDevice) { + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_FINISHED) device.remove { appMethodChannel.nfcActivityStateChanged(NfcActivityState.READY) } } } catch (e: Throwable) { + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) logger.error("Error processing YubiKey in AppContextManager", e) - } } } @@ -441,6 +441,7 @@ class MainActivity : FlutterFragmentActivity() { oathViewModel, dialogManager, appPreferences, + appMethodChannel, nfcActivityListener ) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoActionDescription.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoActionDescription.kt deleted file mode 100644 index ae0d8945..00000000 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoActionDescription.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ - -package com.yubico.authenticator.fido - -const val dialogDescriptionFidoIndex = 200 - -enum class FidoActionDescription(private val value: Int) { - Reset(0), - Unlock(1), - SetPin(2), - DeleteCredential(3), - DeleteFingerprint(4), - RenameFingerprint(5), - RegisterFingerprint(6), - EnableEnterpriseAttestation(7), - ActionFailure(8); - - val id: Int - get() = value + dialogDescriptionFidoIndex -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt index adc2bee7..445067f2 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt @@ -17,7 +17,6 @@ package com.yubico.authenticator.fido import com.yubico.authenticator.DialogManager -import com.yubico.authenticator.DialogTitle import com.yubico.authenticator.device.DeviceManager import com.yubico.authenticator.fido.data.YubiKitFidoSession import com.yubico.authenticator.yubikit.withConnection @@ -48,12 +47,9 @@ class FidoConnectionHelper( } } - suspend fun useSession( - actionDescription: FidoActionDescription, - action: (YubiKitFidoSession) -> T - ): T { + suspend fun useSession(action: (YubiKitFidoSession) -> T): T { return deviceManager.withKey( - onNfc = { useSessionNfc(actionDescription,action) }, + onNfc = { useSessionNfc(action) }, onUsb = { useSessionUsb(it, action) }) } @@ -64,10 +60,7 @@ class FidoConnectionHelper( block(YubiKitFidoSession(it)) } - suspend fun useSessionNfc( - actionDescription: FidoActionDescription, - block: (YubiKitFidoSession) -> T - ): T { + suspend fun useSessionNfc(block: (YubiKitFidoSession) -> T): T { try { val result = suspendCoroutine { outer -> pendingAction = { @@ -75,11 +68,8 @@ class FidoConnectionHelper( block.invoke(it.value) }) } - dialogManager.showDialog( - DialogTitle.TapKey, - actionDescription.id - ) { - logger.debug("Cancelled Dialog {}", actionDescription.name) + dialogManager.showDialog { + logger.debug("Cancelled dialog") pendingAction?.invoke(Result.failure(CancellationException())) pendingAction = null } 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 6919c2cb..09b98cb1 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 @@ -343,7 +343,7 @@ class FidoManager( } private suspend fun unlock(pin: CharArray): String = - connectionHelper.useSession(FidoActionDescription.Unlock) { fidoSession -> + connectionHelper.useSession { fidoSession -> try { val clientPin = @@ -380,7 +380,7 @@ class FidoManager( } private suspend fun setPin(pin: CharArray?, newPin: CharArray): String = - connectionHelper.useSession(FidoActionDescription.SetPin) { fidoSession -> + connectionHelper.useSession { fidoSession -> try { val clientPin = ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo)) @@ -428,7 +428,7 @@ class FidoManager( } private suspend fun deleteCredential(rpId: String, credentialId: String): String = - connectionHelper.useSession(FidoActionDescription.DeleteCredential) { fidoSession -> + connectionHelper.useSession { fidoSession -> val clientPin = ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo)) @@ -476,7 +476,7 @@ class FidoManager( } private suspend fun deleteFingerprint(templateId: String): String = - connectionHelper.useSession(FidoActionDescription.DeleteFingerprint) { fidoSession -> + connectionHelper.useSession { fidoSession -> val clientPin = ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo)) @@ -501,7 +501,7 @@ class FidoManager( } private suspend fun renameFingerprint(templateId: String, name: String): String = - connectionHelper.useSession(FidoActionDescription.RenameFingerprint) { fidoSession -> + connectionHelper.useSession { fidoSession -> val clientPin = ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo)) @@ -531,7 +531,7 @@ class FidoManager( } private suspend fun registerFingerprint(name: String?): String = - connectionHelper.useSession(FidoActionDescription.RegisterFingerprint) { fidoSession -> + connectionHelper.useSession { fidoSession -> state?.cancel() state = CommandState() val clientPin = @@ -607,7 +607,7 @@ class FidoManager( } private suspend fun enableEnterpriseAttestation(): String = - connectionHelper.useSession(FidoActionDescription.EnableEnterpriseAttestation) { fidoSession -> + connectionHelper.useSession { fidoSession -> try { val uvAuthProtocol = getPreferredPinUvAuthProtocol(fidoSession.cachedInfo) val clientPin = ClientPin(fidoSession, uvAuthProtocol) 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 a89a8526..d7f4d367 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 @@ -211,7 +211,7 @@ class FidoResetHelper( coroutineScope.launch { fidoViewModel.updateResetState(FidoResetState.Touch) try { - connectionHelper.useSessionNfc(FidoActionDescription.Reset) { fidoSession -> + connectionHelper.useSessionNfc { fidoSession -> doReset(fidoSession) continuation.resume(Unit) } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt index 24ac7b85..4c82c856 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt @@ -17,7 +17,6 @@ package com.yubico.authenticator.management import com.yubico.authenticator.DialogManager -import com.yubico.authenticator.DialogTitle import com.yubico.authenticator.device.DeviceManager import com.yubico.authenticator.yubikit.withConnection import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice @@ -63,10 +62,7 @@ class ManagementConnectionHelper( block.invoke(it.value) }) } - dialogManager.showDialog( - DialogTitle.TapKey, - actionDescription.id - ) { + dialogManager.showDialog { logger.debug("Cancelled Dialog {}", actionDescription.name) action?.invoke(Result.failure(CancellationException())) action = null diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathActionDescription.kt b/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathActionDescription.kt deleted file mode 100644 index ac78d2c5..00000000 --- a/android/app/src/main/kotlin/com/yubico/authenticator/oath/OathActionDescription.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2023 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. - */ - -package com.yubico.authenticator.oath - -const val dialogDescriptionOathIndex = 100 - -enum class OathActionDescription(private val value: Int) { - Reset(0), - Unlock(1), - SetPassword(2), - UnsetPassword(3), - AddAccount(4), - RenameAccount(5), - DeleteAccount(6), - CalculateCode(7), - ActionFailure(8), - AddMultipleAccounts(9); - - val id: Int - get() = value + dialogDescriptionOathIndex -} \ 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 44e5bdcb..1a5802f1 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 @@ -58,6 +58,7 @@ import com.yubico.yubikit.core.smartcard.SmartCardProtocol import com.yubico.yubikit.core.util.Result import com.yubico.yubikit.management.Capability import com.yubico.yubikit.oath.CredentialData +import com.yubico.yubikit.support.DeviceUtil import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.MethodChannel import kotlinx.coroutines.* @@ -65,6 +66,7 @@ import kotlinx.serialization.encodeToString import org.slf4j.LoggerFactory import java.io.IOException import java.net.URI +import java.util.TimerTask import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicBoolean import kotlin.coroutines.suspendCoroutine @@ -78,6 +80,7 @@ class OathManager( private val oathViewModel: OathViewModel, private val dialogManager: DialogManager, private val appPreferences: AppPreferences, + private val appMethodChannel: MainActivity.AppMethodChannel, private val nfcActivityListener: NfcActivityListener ) : AppContextManager(), DeviceListener { @@ -214,24 +217,33 @@ class OathManager( coroutineScope.cancel() } + var showProcessingTimerTask: TimerTask? = null + override suspend fun processYubiKey(device: YubiKeyDevice) { try { + if (device is NfcYubiKeyDevice) { + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_STARTED) + } + device.withConnection { connection -> val session = getOathSession(connection) val previousId = oathViewModel.currentSession()?.deviceId if (session.deviceId == previousId && device is NfcYubiKeyDevice) { - // Run any pending action - pendingAction?.let { action -> - action.invoke(Result.success(session)) - pendingAction = null - } - - // Refresh codes - if (!session.isLocked) { - try { - oathViewModel.updateCredentials(calculateOathCodes(session)) - } catch (error: Exception) { - logger.error("Failed to refresh codes", error) + // Either run a pending action, or just refresh codes + if (pendingAction != null) { + pendingAction?.let { action -> + action.invoke(Result.success(session)) + pendingAction = null + } + } else { + // Refresh codes + if (!session.isLocked) { + try { + oathViewModel.updateCredentials(calculateOathCodes(session)) + } catch (error: Exception) { + logger.error("Failed to refresh codes", error) + throw error + } } } } else { @@ -261,6 +273,7 @@ class OathManager( } else { // Awaiting an action for a different device? Fail it and stop processing. action.invoke(Result.failure(IllegalStateException("Wrong deviceId"))) + showProcessingTimerTask?.cancel() return@withConnection } } @@ -281,11 +294,14 @@ class OathManager( supportedCapabilities = oathCapabilities ) ) + showProcessingTimerTask?.cancel() return@withConnection } } } } + + showProcessingTimerTask?.cancel() logger.debug( "Successfully read Oath session info (and credentials if unlocked) from connected key" ) @@ -294,10 +310,12 @@ class OathManager( deviceManager.setDeviceInfo(getDeviceInfo(device)) } } catch (e: Exception) { + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) // OATH not enabled/supported, try to get DeviceInfo over other USB interfaces logger.error("Failed to connect to CCID: ", e) // Clear any cached OATH state oathViewModel.clearSession() + throw e } } @@ -308,7 +326,7 @@ class OathManager( val credentialData: CredentialData = CredentialData.parseUri(URI.create(uri)) addToAny = true - return useOathSessionNfc(OathActionDescription.AddAccount) { session -> + return useOathSessionNfc { session -> // We need to check for duplicates here since we haven't yet read the credentials if (session.credentials.any { it.id.contentEquals(credentialData.id) }) { throw IllegalArgumentException() @@ -338,7 +356,7 @@ class OathManager( logger.trace("Adding following accounts: {}", uris) addToAny = true - return useOathSession(OathActionDescription.AddMultipleAccounts) { session -> + return useOathSession { session -> var successCount = 0 for (index in uris.indices) { @@ -370,7 +388,7 @@ class OathManager( } private suspend fun reset(): String = - useOathSession(OathActionDescription.Reset, updateDeviceInfo = true) { + useOathSession(updateDeviceInfo = true) { // note, it is ok to reset locked session it.reset() keyManager.removeKey(it.deviceId) @@ -382,7 +400,7 @@ class OathManager( } private suspend fun unlock(password: String, remember: Boolean): String = - useOathSession(OathActionDescription.Unlock) { + useOathSession { val accessKey = it.deriveAccessKey(password.toCharArray()) keyManager.addKey(it.deviceId, accessKey, remember) @@ -390,11 +408,7 @@ class OathManager( val remembered = keyManager.isRemembered(it.deviceId) if (unlocked) { oathViewModel.setSessionState(Session(it, remembered)) - - // fetch credentials after unlocking only if the YubiKey is connected over USB - if (deviceManager.isUsbKeyConnected()) { - oathViewModel.updateCredentials(calculateOathCodes(it)) - } + oathViewModel.updateCredentials(calculateOathCodes(it)) } jsonSerializer.encodeToString(mapOf("unlocked" to unlocked, "remembered" to remembered)) @@ -405,7 +419,6 @@ class OathManager( newPassword: String, ): String = useOathSession( - OathActionDescription.SetPassword, unlock = false, updateDeviceInfo = true ) { session -> @@ -427,7 +440,7 @@ class OathManager( } private suspend fun unsetPassword(currentPassword: String): String = - useOathSession(OathActionDescription.UnsetPassword, unlock = false) { session -> + useOathSession(unlock = false) { session -> if (session.isAccessKeySet) { // test current password sent by the user if (session.unlock(currentPassword.toCharArray())) { @@ -459,7 +472,7 @@ class OathManager( uri: String, requireTouch: Boolean, ): String = - useOathSession(OathActionDescription.AddAccount) { session -> + useOathSession { session -> val credentialData: CredentialData = CredentialData.parseUri(URI.create(uri)) @@ -480,21 +493,30 @@ class OathManager( } private suspend fun renameAccount(uri: String, name: String, issuer: String?): String = - useOathSession(OathActionDescription.RenameAccount) { session -> - val credential = getOathCredential(session, uri) - val renamedCredential = - Credential(session.renameCredential(credential, name, issuer), session.deviceId) - oathViewModel.renameCredential( - Credential(credential, session.deviceId), - renamedCredential + useOathSession { session -> + val credential = getCredential(uri) + val renamed = Credential( + session.renameCredential(credential, name, issuer), + session.deviceId ) - jsonSerializer.encodeToString(renamedCredential) + oathViewModel.renameCredential( + Credential(credential, session.deviceId), + renamed + ) + +// // simulate long taking op +// val renamedCredential = credential +// logger.debug("simulate error") +// Thread.sleep(3000) +// throw IOException("Test exception") + + jsonSerializer.encodeToString(renamed) } private suspend fun deleteAccount(credentialId: String): String = - useOathSession(OathActionDescription.DeleteAccount) { session -> - val credential = getOathCredential(session, credentialId) + useOathSession { session -> + val credential = getCredential(credentialId) session.deleteCredential(credential) oathViewModel.removeCredential(Credential(credential, session.deviceId)) NULL @@ -546,8 +568,8 @@ class OathManager( private suspend fun calculate(credentialId: String): String = - useOathSession(OathActionDescription.CalculateCode) { session -> - val credential = getOathCredential(session, credentialId) + useOathSession { session -> + val credential = getCredential(credentialId) val code = Code.from(calculateCode(session, credential)) oathViewModel.updateCode( @@ -649,31 +671,43 @@ class OathManager( return session.calculateCodes(timestamp).map { (credential, code) -> Pair( Credential(credential, session.deviceId), - Code.from(if (credential.isSteamCredential() && (!credential.isTouchRequired || bypassTouch)) { - session.calculateSteamCode(credential, timestamp) - } else if (credential.isTouchRequired && bypassTouch) { - session.calculateCode(credential, timestamp) - } else { - code - }) + Code.from( + if (credential.isSteamCredential() && (!credential.isTouchRequired || bypassTouch)) { + session.calculateSteamCode(credential, timestamp) + } else if (credential.isTouchRequired && bypassTouch) { + session.calculateCode(credential, timestamp) + } else { + code + } + ) ) }.toMap() } + private fun getCredential(id: String): YubiKitCredential { + val credential = + oathViewModel.credentials.value?.find { it.credential.id == id }?.credential + + if (credential == null || credential.data == null) { + logger.debug("Failed to find credential with id: {}", id) + throw Exception("Failed to find account") + } + + return credential.data + } + private suspend fun useOathSession( - oathActionDescription: OathActionDescription, unlock: Boolean = true, updateDeviceInfo: Boolean = false, action: (YubiKitOathSession) -> T ): T { - // callers can decide whether the session should be unlocked first unlockOnConnect.set(unlock) // callers can request whether device info should be updated after session operation this@OathManager.updateDeviceInfo.set(updateDeviceInfo) return deviceManager.withKey( onUsb = { useOathSessionUsb(it, updateDeviceInfo, action) }, - onNfc = { useOathSessionNfc(oathActionDescription, action) } + onNfc = { useOathSessionNfc(action) } ) } @@ -690,50 +724,42 @@ class OathManager( } private suspend fun useOathSessionNfc( - oathActionDescription: OathActionDescription, block: (YubiKitOathSession) -> T ): T { - try { - val result = suspendCoroutine { outer -> - pendingAction = { - outer.resumeWith(runCatching { - block.invoke(it.value) - }) - } - dialogManager.showDialog(DialogTitle.TapKey, oathActionDescription.id) { - logger.debug("Cancelled Dialog {}", oathActionDescription.name) - pendingAction?.invoke(Result.failure(CancellationException())) - pendingAction = null - } - } - nfcActivityListener.onChange(NfcActivityState.PROCESSING_FINISHED) - dialogManager.updateDialogState( - dialogTitle = DialogTitle.OperationSuccessful - ) - // TODO: This delays the closing of the dialog, but also the return value - delay(1500) - return result - } catch (cancelled: CancellationException) { - throw cancelled - } catch (error: Throwable) { - nfcActivityListener.onChange(NfcActivityState.PROCESSING_INTERRUPTED) - dialogManager.updateDialogState( - dialogTitle = DialogTitle.OperationFailed, - dialogDescriptionId = OathActionDescription.ActionFailure.id - ) - // TODO: This delays the closing of the dialog, but also the return value - delay(1500) - throw error - } finally { - dialogManager.closeDialog() - } - } + var firstShow = true + while (true) { // loop until success or cancel + try { + val result = suspendCoroutine { outer -> + pendingAction = { + outer.resumeWith(runCatching { + val session = it.value // this can throw CancellationException + nfcActivityListener.onChange(NfcActivityState.PROCESSING_STARTED) + block.invoke(session) + }) + } - private fun getOathCredential(session: YubiKitOathSession, credentialId: String) = - // we need to use oathSession.calculateCodes() to get proper Credential.touchRequired value - session.calculateCodes().map { e -> e.key }.firstOrNull { credential -> - (credential != null) && credential.id.asString() == credentialId - } ?: throw Exception("Failed to find account") + if (firstShow) { + dialogManager.showDialog { + logger.debug("Cancelled dialog") + pendingAction?.invoke(Result.failure(CancellationException())) + pendingAction = null + } + firstShow = false + } + // here the coroutine is suspended and waits till pendingAction is + // invoked - the pending action result will resume this coroutine + } + nfcActivityListener.onChange(NfcActivityState.PROCESSING_FINISHED) + return result + } catch (cancelled: CancellationException) { + throw cancelled + } catch (e: Exception) { + logger.error("Exception during action: ", e) + nfcActivityListener.onChange(NfcActivityState.PROCESSING_INTERRUPTED) + throw e + } + } // while + } override fun onConnected(device: YubiKeyDevice) { refreshJob?.cancel() diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/oath/data/Credential.kt b/android/app/src/main/kotlin/com/yubico/authenticator/oath/data/Credential.kt index 60c45ab9..b827605f 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/oath/data/Credential.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/oath/data/Credential.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yubico. + * Copyright (C) 2023-2024 Yubico. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,9 +35,10 @@ data class Credential( @SerialName("name") val accountName: String, @SerialName("touch_required") - val touchRequired: Boolean + val touchRequired: Boolean, + @kotlinx.serialization.Transient + val data: YubiKitCredential? = null ) { - constructor(credential: YubiKitCredential, deviceId: String) : this( deviceId = deviceId, id = credential.id.asString(), @@ -48,7 +49,8 @@ data class Credential( period = credential.period, issuer = credential.issuer, accountName = credential.accountName, - touchRequired = credential.isTouchRequired + touchRequired = credential.isTouchRequired, + data = credential ) override fun equals(other: Any?): Boolean = diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityDispatcher.kt b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityDispatcher.kt index 0bb14899..cea74967 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityDispatcher.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityDispatcher.kt @@ -19,6 +19,7 @@ package com.yubico.authenticator.yubikit import android.app.Activity import android.nfc.NfcAdapter import android.nfc.Tag +import com.yubico.authenticator.yubikit.NfcActivityListener import com.yubico.yubikit.android.transport.nfc.NfcConfiguration import com.yubico.yubikit.android.transport.nfc.NfcDispatcher @@ -51,7 +52,7 @@ class NfcActivityDispatcher(private val listener: NfcActivityListener) : NfcDisp nfcConfiguration, TagInterceptor(listener, handler) ) - listener.onChange(NfcActivityState.READY) + //listener.onChange(NfcActivityState.READY) } override fun disable(activity: Activity) { @@ -68,7 +69,7 @@ class NfcActivityDispatcher(private val listener: NfcActivityListener) : NfcDisp private val logger = LoggerFactory.getLogger(TagInterceptor::class.java) override fun onTag(tag: Tag) { - listener.onChange(NfcActivityState.PROCESSING_STARTED) + //listener.onChange(NfcActivityState.PROCESSING_STARTED) logger.debug("forwarding tag") tagHandler.onTag(tag) } diff --git a/android/settings.gradle b/android/settings.gradle index 5c254c38..e462de1b 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -26,7 +26,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.5.0" apply false + id "com.android.application" version '8.5.2' apply false id "org.jetbrains.kotlin.android" version "2.0.0" apply false id "org.jetbrains.kotlin.plugin.serialization" version "2.0.0" apply false id "com.google.android.gms.oss-licenses-plugin" version "0.10.6" apply false diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index 956c58b7..b7cc7c88 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -32,17 +32,18 @@ import '../../exception/no_data_exception.dart'; import '../../exception/platform_exception_decoder.dart'; import '../../fido/models.dart'; import '../../fido/state.dart'; +import '../tap_request_dialog.dart'; final _log = Logger('android.fido.state'); -const _methods = MethodChannel('android.fido.methods'); - final androidFidoStateProvider = AsyncNotifierProvider.autoDispose .family(_FidoStateNotifier.new); class _FidoStateNotifier extends FidoStateNotifier { final _events = const EventChannel('android.fido.sessionState'); late StreamSubscription _sub; + late final _FidoMethodChannelNotifier fido = + ref.read(_fidoMethodsProvider.notifier); @override FutureOr build(DevicePath devicePath) async { @@ -79,7 +80,7 @@ class _FidoStateNotifier extends FidoStateNotifier { }); controller.onCancel = () async { - await _methods.invokeMethod('cancelReset'); + await fido.cancelReset(); if (!controller.isClosed) { await subscription.cancel(); } @@ -87,7 +88,7 @@ class _FidoStateNotifier extends FidoStateNotifier { controller.onListen = () async { try { - await _methods.invokeMethod('reset'); + await fido.reset(); await controller.sink.close(); ref.invalidateSelf(); } catch (e) { @@ -102,13 +103,7 @@ class _FidoStateNotifier extends FidoStateNotifier { @override Future setPin(String newPin, {String? oldPin}) async { try { - final response = jsonDecode(await _methods.invokeMethod( - 'setPin', - { - 'pin': oldPin, - 'newPin': newPin, - }, - )); + final response = jsonDecode(await fido.setPin(newPin, oldPin: oldPin)); if (response['success'] == true) { _log.debug('FIDO PIN set/change successful'); return PinResult.success(); @@ -134,10 +129,7 @@ class _FidoStateNotifier extends FidoStateNotifier { @override Future unlock(String pin) async { try { - final response = jsonDecode(await _methods.invokeMethod( - 'unlock', - {'pin': pin}, - )); + final response = jsonDecode(await fido.unlock(pin)); if (response['success'] == true) { _log.debug('FIDO applet unlocked'); @@ -165,9 +157,7 @@ class _FidoStateNotifier extends FidoStateNotifier { @override Future enableEnterpriseAttestation() async { try { - final response = jsonDecode(await _methods.invokeMethod( - 'enableEnterpriseAttestation', - )); + final response = jsonDecode(await fido.enableEnterpriseAttestation()); if (response['success'] == true) { _log.debug('Enterprise attestation enabled'); @@ -193,6 +183,8 @@ final androidFingerprintProvider = AsyncNotifierProvider.autoDispose class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier { final _events = const EventChannel('android.fido.fingerprints'); late StreamSubscription _sub; + late final _FidoMethodChannelNotifier fido = + ref.read(_fidoMethodsProvider.notifier); @override FutureOr> build(DevicePath devicePath) async { @@ -243,15 +235,14 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier { controller.onCancel = () async { if (!controller.isClosed) { _log.debug('Cancelling fingerprint registration'); - await _methods.invokeMethod('cancelRegisterFingerprint'); + await fido.cancelFingerprintRegistration(); await registerFpSub.cancel(); } }; controller.onListen = () async { try { - final registerFpResult = - await _methods.invokeMethod('registerFingerprint', {'name': name}); + final registerFpResult = await fido.registerFingerprint(name); _log.debug('Finished registerFingerprint with: $registerFpResult'); @@ -286,13 +277,8 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier { Future renameFingerprint( Fingerprint fingerprint, String name) async { try { - final renameFingerprintResponse = jsonDecode(await _methods.invokeMethod( - 'renameFingerprint', - { - 'templateId': fingerprint.templateId, - 'name': name, - }, - )); + final renameFingerprintResponse = + jsonDecode(await fido.renameFingerprint(fingerprint, name)); if (renameFingerprintResponse['success'] == true) { _log.debug('FIDO rename fingerprint succeeded'); @@ -316,12 +302,8 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier { @override Future deleteFingerprint(Fingerprint fingerprint) async { try { - final deleteFingerprintResponse = jsonDecode(await _methods.invokeMethod( - 'deleteFingerprint', - { - 'templateId': fingerprint.templateId, - }, - )); + final deleteFingerprintResponse = + jsonDecode(await fido.deleteFingerprint(fingerprint)); if (deleteFingerprintResponse['success'] == true) { _log.debug('FIDO delete fingerprint succeeded'); @@ -348,6 +330,8 @@ final androidCredentialProvider = AsyncNotifierProvider.autoDispose class _FidoCredentialsNotifier extends FidoCredentialsNotifier { final _events = const EventChannel('android.fido.credentials'); late StreamSubscription _sub; + late final _FidoMethodChannelNotifier fido = + ref.read(_fidoMethodsProvider.notifier); @override FutureOr> build(DevicePath devicePath) async { @@ -371,13 +355,7 @@ class _FidoCredentialsNotifier extends FidoCredentialsNotifier { @override Future deleteCredential(FidoCredential credential) async { try { - await _methods.invokeMethod( - 'deleteCredential', - { - 'rpId': credential.rpId, - 'credentialId': credential.credentialId, - }, - ); + await fido.deleteCredential(credential); } on PlatformException catch (pe) { var decodedException = pe.decode(); if (decodedException is CancellationException) { @@ -388,3 +366,88 @@ class _FidoCredentialsNotifier extends FidoCredentialsNotifier { } } } + +final _fidoMethodsProvider = NotifierProvider<_FidoMethodChannelNotifier, void>( + () => _FidoMethodChannelNotifier()); + +class _FidoMethodChannelNotifier extends MethodChannelNotifier { + _FidoMethodChannelNotifier() + : super(const MethodChannel('android.fido.methods')); + late final l10n = ref.read(l10nProvider); + + @override + void build() {} + + Future deleteCredential(FidoCredential credential) async => + invoke('deleteCredential', { + 'callArgs': { + 'rpId': credential.rpId, + 'credentialId': credential.credentialId + }, + 'operationName': l10n.s_nfc_dialog_fido_delete_credential, + 'operationProcessing': + l10n.s_nfc_dialog_fido_delete_credential_processing, + 'operationSuccess': l10n.s_nfc_dialog_fido_delete_credential_success, + 'operationFailure': l10n.s_nfc_dialog_fido_delete_credential_failure, + 'showSuccess': true + }); + + Future cancelReset() async => invoke('cancelReset'); + + Future reset() async => invoke('reset', { + 'operationName': l10n.s_nfc_dialog_fido_reset, + 'operationProcessing': l10n.s_nfc_dialog_fido_reset_processing, + 'operationSuccess': l10n.s_nfc_dialog_fido_reset_success, + 'operationFailure': l10n.s_nfc_dialog_fido_reset_failure, + 'showSuccess': true + }); + + Future setPin(String newPin, {String? oldPin}) async => + invoke('setPin', { + 'callArgs': {'pin': oldPin, 'newPin': newPin}, + 'operationName': oldPin != null + ? l10n.s_nfc_dialog_fido_change_pin + : l10n.s_nfc_dialog_fido_set_pin, + 'operationProcessing': oldPin != null + ? l10n.s_nfc_dialog_fido_change_pin_processing + : l10n.s_nfc_dialog_fido_set_pin_processing, + 'operationSuccess': oldPin != null + ? l10n.s_nfc_dialog_fido_change_pin_success + : l10n.s_nfc_dialog_fido_set_pin_success, + 'operationFailure': oldPin != null + ? l10n.s_nfc_dialog_fido_change_pin_failure + : l10n.s_nfc_dialog_fido_set_pin_failure, + 'showSuccess': true + }); + + Future unlock(String pin) async => invoke('unlock', { + 'callArgs': {'pin': pin}, + 'operationName': l10n.s_nfc_dialog_fido_unlock, + 'operationProcessing': l10n.s_nfc_dialog_fido_unlock_processing, + 'operationSuccess': l10n.s_nfc_dialog_fido_unlock_success, + 'operationFailure': l10n.s_nfc_dialog_fido_unlock_failure, + 'showSuccess': true + }); + + Future enableEnterpriseAttestation() async => + invoke('enableEnterpriseAttestation'); + + Future registerFingerprint(String? name) async => + invoke('registerFingerprint', { + 'callArgs': {'name': name} + }); + + Future cancelFingerprintRegistration() async => + invoke('cancelRegisterFingerprint'); + + Future renameFingerprint( + Fingerprint fingerprint, String name) async => + invoke('renameFingerprint', { + 'callArgs': {'templateId': fingerprint.templateId, 'name': name}, + }); + + Future deleteFingerprint(Fingerprint fingerprint) async => + invoke('deleteFingerprint', { + 'callArgs': {'templateId': fingerprint.templateId}, + }); +} diff --git a/lib/android/init.dart b/lib/android/init.dart index 6322acd0..607067ab 100644 --- a/lib/android/init.dart +++ b/lib/android/init.dart @@ -43,6 +43,7 @@ import 'oath/state.dart'; import 'qr_scanner/qr_scanner_provider.dart'; import 'state.dart'; import 'tap_request_dialog.dart'; +import 'views/nfc/nfc_activity_command_listener.dart'; import 'window_state_provider.dart'; Future initialize() async { @@ -106,6 +107,8 @@ Future initialize() async { child: DismissKeyboard( child: YubicoAuthenticatorApp(page: Consumer( builder: (context, ref, child) { + ref.read(nfcActivityCommandListener).startListener(context); + Timer.run(() { ref.read(featureFlagProvider.notifier) // TODO: Load feature flags from file/config? diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index 03b0bdcf..fbcc6c3e 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -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. @@ -35,11 +35,10 @@ import '../../exception/no_data_exception.dart'; import '../../exception/platform_exception_decoder.dart'; import '../../oath/models.dart'; import '../../oath/state.dart'; +import '../tap_request_dialog.dart'; final _log = Logger('android.oath.state'); -const _methods = MethodChannel('android.oath.methods'); - final androidOathStateProvider = AsyncNotifierProvider.autoDispose .family( _AndroidOathStateNotifier.new); @@ -47,6 +46,8 @@ final androidOathStateProvider = AsyncNotifierProvider.autoDispose class _AndroidOathStateNotifier extends OathStateNotifier { final _events = const EventChannel('android.oath.sessionState'); late StreamSubscription _sub; + late _OathMethodChannelNotifier oath = + ref.watch(_oathMethodsProvider.notifier); @override FutureOr build(DevicePath arg) { @@ -75,7 +76,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier { // await ref // .read(androidAppContextHandler) // .switchAppContext(Application.accounts); - await _methods.invokeMethod('reset'); + await oath.reset(); } catch (e) { _log.debug('Calling reset failed with exception: $e'); } @@ -84,8 +85,8 @@ class _AndroidOathStateNotifier extends OathStateNotifier { @override Future<(bool, bool)> unlock(String password, {bool remember = false}) async { try { - final unlockResponse = jsonDecode(await _methods.invokeMethod( - 'unlock', {'password': password, 'remember': remember})); + final unlockResponse = + jsonDecode(await oath.unlock(password, remember: remember)); _log.debug('applet unlocked'); final unlocked = unlockResponse['unlocked'] == true; @@ -106,8 +107,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier { @override Future setPassword(String? current, String password) async { try { - await _methods.invokeMethod( - 'setPassword', {'current': current, 'password': password}); + await oath.setPassword(current, password); return true; } on PlatformException catch (e) { _log.debug('Calling set password failed with exception: $e'); @@ -118,7 +118,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier { @override Future unsetPassword(String current) async { try { - await _methods.invokeMethod('unsetPassword', {'current': current}); + await oath.unsetPassword(current); return true; } on PlatformException catch (e) { _log.debug('Calling unset password failed with exception: $e'); @@ -129,7 +129,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier { @override Future forgetPassword() async { try { - await _methods.invokeMethod('forgetPassword'); + await oath.forgetPassword(); } on PlatformException catch (e) { _log.debug('Calling forgetPassword failed with exception: $e'); } @@ -161,12 +161,10 @@ Exception _decodeAddAccountException(PlatformException platformException) { final addCredentialToAnyProvider = Provider((ref) => (Uri credentialUri, {bool requireTouch = false}) async { + final oath = ref.watch(_oathMethodsProvider.notifier); try { - String resultString = await _methods.invokeMethod( - 'addAccountToAny', { - 'uri': credentialUri.toString(), - 'requireTouch': requireTouch - }); + String resultString = await oath.addAccountToAny(credentialUri, + requireTouch: requireTouch); var result = jsonDecode(resultString); return OathCredential.fromJson(result['credential']); @@ -177,17 +175,13 @@ final addCredentialToAnyProvider = final addCredentialsToAnyProvider = Provider( (ref) => (List credentialUris, List touchRequired) async { + final oath = ref.read(_oathMethodsProvider.notifier); try { _log.debug( 'Calling android with ${credentialUris.length} credentials to be added'); - String resultString = await _methods.invokeMethod( - 'addAccountsToAny', - { - 'uris': credentialUris, - 'requireTouch': touchRequired, - }, - ); + String resultString = + await oath.addAccounts(credentialUris, touchRequired); _log.debug('Call result: $resultString'); var result = jsonDecode(resultString); @@ -218,6 +212,8 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier { final WithContext _withContext; final Ref _ref; late StreamSubscription _sub; + late _OathMethodChannelNotifier oath = + _ref.read(_oathMethodsProvider.notifier); _AndroidCredentialListNotifier(this._withContext, this._ref) : super() { _sub = _events.receiveBroadcastStream().listen((event) { @@ -264,8 +260,7 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier { } try { - final resultJson = await _methods - .invokeMethod('calculate', {'credentialId': credential.id}); + final resultJson = await oath.calculate(credential); _log.debug('Calculate', resultJson); return OathCode.fromJson(jsonDecode(resultJson)); } on PlatformException catch (pe) { @@ -280,9 +275,8 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier { Future addAccount(Uri credentialUri, {bool requireTouch = false}) async { try { - String resultString = await _methods.invokeMethod('addAccount', - {'uri': credentialUri.toString(), 'requireTouch': requireTouch}); - + String resultString = + await oath.addAccount(credentialUri, requireTouch: requireTouch); var result = jsonDecode(resultString); return OathCredential.fromJson(result['credential']); } on PlatformException catch (pe) { @@ -294,9 +288,7 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier { Future renameAccount( OathCredential credential, String? issuer, String name) async { try { - final response = await _methods.invokeMethod('renameAccount', - {'credentialId': credential.id, 'name': name, 'issuer': issuer}); - + final response = await oath.renameAccount(credential, issuer, name); _log.debug('Rename response: $response'); var responseJson = jsonDecode(response); @@ -311,11 +303,149 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier { @override Future deleteAccount(OathCredential credential) async { try { - await _methods - .invokeMethod('deleteAccount', {'credentialId': credential.id}); + await oath.deleteAccount(credential); } on PlatformException catch (e) { - _log.debug('Received exception: $e'); - throw e.decode(); + var decoded = e.decode(); + if (decoded is CancellationException) { + _log.debug('Account delete was cancelled.'); + } else { + _log.debug('Received exception: $e'); + } + + throw decoded; } } } + +final _oathMethodsProvider = NotifierProvider<_OathMethodChannelNotifier, void>( + () => _OathMethodChannelNotifier()); + +class _OathMethodChannelNotifier extends MethodChannelNotifier { + _OathMethodChannelNotifier() + : super(const MethodChannel('android.oath.methods')); + late final l10n = ref.read(l10nProvider); + + @override + void build() {} + + Future reset() async => invoke('reset', { + 'operationName': l10n.s_nfc_dialog_oath_reset, + 'operationProcessing': l10n.s_nfc_dialog_oath_reset_processing, + 'operationSuccess': l10n.s_nfc_dialog_oath_reset_success, + 'operationFailure': l10n.s_nfc_dialog_oath_reset_failure + }); + + Future unlock(String password, {bool remember = false}) async => + invoke('unlock', { + 'callArgs': {'password': password, 'remember': remember}, + 'operationName': l10n.s_nfc_dialog_oath_unlock, + 'operationProcessing': l10n.s_nfc_dialog_oath_unlock_processing, + 'operationSuccess': l10n.s_nfc_dialog_oath_unlock_success, + 'operationFailure': l10n.s_nfc_dialog_oath_unlock_failure, + }); + + Future setPassword(String? current, String password) async => + invoke('setPassword', { + 'callArgs': {'current': current, 'password': password}, + 'operationName': current != null + ? l10n.s_nfc_dialog_oath_change_password + : l10n.s_nfc_dialog_oath_set_password, + 'operationProcessing': current != null + ? l10n.s_nfc_dialog_oath_change_password_processing + : l10n.s_nfc_dialog_oath_set_password_processing, + 'operationSuccess': current != null + ? l10n.s_nfc_dialog_oath_change_password_success + : l10n.s_nfc_dialog_oath_set_password_success, + 'operationFailure': current != null + ? l10n.s_nfc_dialog_oath_change_password_failure + : l10n.s_nfc_dialog_oath_set_password_failure, + }); + + Future unsetPassword(String current) async => + invoke('unsetPassword', { + 'callArgs': {'current': current}, + 'operationName': l10n.s_nfc_dialog_oath_remove_password, + 'operationProcessing': + l10n.s_nfc_dialog_oath_remove_password_processing, + 'operationSuccess': l10n.s_nfc_dialog_oath_remove_password_success, + 'operationFailure': l10n.s_nfc_dialog_oath_remove_password_failure, + }); + + Future forgetPassword() async => invoke('forgetPassword'); + + Future calculate(OathCredential credential) async => + invoke('calculate', { + 'callArgs': {'credentialId': credential.id}, + 'operationName': l10n.s_nfc_dialog_oath_calculate_code, + 'operationProcessing': l10n.s_nfc_dialog_oath_calculate_code_processing, + 'operationSuccess': l10n.s_nfc_dialog_oath_calculate_code_success, + 'operationFailure': l10n.s_nfc_dialog_oath_calculate_code_failure, + }); + + Future addAccount(Uri credentialUri, + {bool requireTouch = false}) async => + invoke('addAccount', { + 'callArgs': { + 'uri': credentialUri.toString(), + 'requireTouch': requireTouch + }, + 'operationName': l10n.s_nfc_dialog_oath_add_account, + 'operationProcessing': l10n.s_nfc_dialog_oath_add_account_processing, + 'operationSuccess': l10n.s_nfc_dialog_oath_add_account_success, + 'operationFailure': l10n.s_nfc_dialog_oath_add_account_failure, + 'showSuccess': true + }); + + Future addAccounts( + List credentialUris, List touchRequired) async => + invoke('addAccountsToAny', { + 'callArgs': { + 'uris': credentialUris, + 'requireTouch': touchRequired, + }, + 'operationName': l10n.s_nfc_dialog_oath_add_multiple_accounts, + 'operationProcessing': + l10n.s_nfc_dialog_oath_add_multiple_accounts_processing, + 'operationSuccess': + l10n.s_nfc_dialog_oath_add_multiple_accounts_success, + 'operationFailure': + l10n.s_nfc_dialog_oath_add_multiple_accounts_failure, + }); + + Future addAccountToAny(Uri credentialUri, + {bool requireTouch = false}) async => + invoke('addAccountToAny', { + 'callArgs': { + 'uri': credentialUri.toString(), + 'requireTouch': requireTouch + }, + 'operationName': l10n.s_nfc_dialog_oath_add_account, + 'operationProcessing': l10n.s_nfc_dialog_oath_add_account_processing, + 'operationSuccess': l10n.s_nfc_dialog_oath_add_account_success, + 'operationFailure': l10n.s_nfc_dialog_oath_add_account_failure, + }); + + Future deleteAccount(OathCredential credential) async => + invoke('deleteAccount', { + 'callArgs': {'credentialId': credential.id}, + 'operationName': l10n.s_nfc_dialog_oath_delete_account, + 'operationProcessing': l10n.s_nfc_dialog_oath_delete_account_processing, + 'operationSuccess': l10n.s_nfc_dialog_oath_delete_account_success, + 'operationFailure': l10n.s_nfc_dialog_oath_delete_account_failure, + 'showSuccess': true + }); + + Future renameAccount( + OathCredential credential, String? issuer, String name) async => + invoke('renameAccount', { + 'callArgs': { + 'credentialId': credential.id, + 'name': name, + 'issuer': issuer + }, + 'operationName': l10n.s_nfc_dialog_oath_rename_account, + 'operationProcessing': l10n.s_nfc_dialog_oath_rename_account_processing, + 'operationSuccess': l10n.s_nfc_dialog_oath_rename_account_success, + 'operationFailure': l10n.s_nfc_dialog_oath_rename_account_failure, + }); +} diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 66fc7fb0..ccc89aea 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -15,113 +15,132 @@ */ import 'dart:async'; -import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -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/models.dart'; import '../app/state.dart'; -import '../app/views/user_interaction.dart'; -import 'views/nfc/nfc_activity_widget.dart'; +import '../widgets/pulsing.dart'; +import 'state.dart'; +import 'views/nfc/nfc_activity_overlay.dart'; const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); -// _DDesc contains id of title resource for the dialog -enum _DTitle { - tapKey, - operationSuccessful, - operationFailed, - invalid; +final androidDialogProvider = + NotifierProvider<_DialogProvider, int>(_DialogProvider.new); - static _DTitle fromId(int? id) => - const { - 0: _DTitle.tapKey, - 1: _DTitle.operationSuccessful, - 2: _DTitle.operationFailed - }[id] ?? - _DTitle.invalid; -} +class _DialogProvider extends Notifier { + Timer? processingTimer; + bool explicitAction = false; -// _DDesc contains action description in the dialog -enum _DDesc { - // oath descriptions - oathResetApplet, - oathUnlockSession, - oathSetPassword, - oathUnsetPassword, - oathAddAccount, - oathRenameAccount, - oathDeleteAccount, - oathCalculateCode, - oathActionFailure, - oathAddMultipleAccounts, - // FIDO descriptions - fidoResetApplet, - fidoUnlockSession, - fidoSetPin, - fidoDeleteCredential, - fidoDeleteFingerprint, - fidoRenameFingerprint, - fidoRegisterFingerprint, - fidoEnableEnterpriseAttestation, - fidoActionFailure, - // Others - invalid; + @override + int build() { + final l10n = ref.read(l10nProvider); + ref.listen(androidNfcActivityProvider, (previous, current) { + final notifier = ref.read(nfcActivityCommandNotifier.notifier); - static const int dialogDescriptionOathIndex = 100; - static const int dialogDescriptionFidoIndex = 200; + if (!explicitAction) { + // setup properties for ad-hoc action + ref.read(nfcActivityWidgetNotifier.notifier).setDialogProperties( + operationProcessing: l10n.s_nfc_dialog_read_key, + operationFailure: l10n.s_nfc_dialog_read_key_failure, + showSuccess: false, + ); + } - static _DDesc fromId(int? id) => - const { - dialogDescriptionOathIndex + 0: oathResetApplet, - dialogDescriptionOathIndex + 1: oathUnlockSession, - dialogDescriptionOathIndex + 2: oathSetPassword, - dialogDescriptionOathIndex + 3: oathUnsetPassword, - dialogDescriptionOathIndex + 4: oathAddAccount, - dialogDescriptionOathIndex + 5: oathRenameAccount, - dialogDescriptionOathIndex + 6: oathDeleteAccount, - dialogDescriptionOathIndex + 7: oathCalculateCode, - dialogDescriptionOathIndex + 8: oathActionFailure, - dialogDescriptionOathIndex + 9: oathAddMultipleAccounts, - dialogDescriptionFidoIndex + 0: fidoResetApplet, - dialogDescriptionFidoIndex + 1: fidoUnlockSession, - dialogDescriptionFidoIndex + 2: fidoSetPin, - dialogDescriptionFidoIndex + 3: fidoDeleteCredential, - dialogDescriptionFidoIndex + 4: fidoDeleteFingerprint, - dialogDescriptionFidoIndex + 5: fidoRenameFingerprint, - dialogDescriptionFidoIndex + 6: fidoRegisterFingerprint, - dialogDescriptionFidoIndex + 7: fidoEnableEnterpriseAttestation, - dialogDescriptionFidoIndex + 8: fidoActionFailure, - }[id] ?? - _DDesc.invalid; -} + final properties = ref.read(nfcActivityWidgetNotifier); -final androidDialogProvider = Provider<_DialogProvider>( - (ref) { - return _DialogProvider(ref.watch(withContextProvider)); - }, -); + debugPrint('XXX now it is: $current'); + switch (current) { + case NfcActivity.processingStarted: + processingTimer?.cancel(); -class _DialogProvider { - final WithContext _withContext; - final Widget _icon = const NfcActivityWidget(width: 64, height: 64); - UserInteractionController? _controller; + debugPrint('XXX explicit action: $explicitAction'); + final timeout = explicitAction ? 300 : 200; + + processingTimer = Timer(Duration(milliseconds: timeout), () { + if (!explicitAction) { + // show the widget + notifier.update(NfcActivityWidgetCommand( + action: NfcActivityWidgetActionShowWidget( + child: _NfcActivityWidgetView( + title: properties.operationProcessing, + subtitle: '', + inProgress: true, + )))); + } else { + // the processing view will only be shown if the timer is still active + notifier.update(NfcActivityWidgetCommand( + action: NfcActivityWidgetActionSetWidgetData( + child: _NfcActivityWidgetView( + title: properties.operationProcessing, + subtitle: l10n.s_nfc_dialog_hold_key, + inProgress: true, + )))); + } + }); + break; + case NfcActivity.processingFinished: + explicitAction = false; // next action might not be explicit + processingTimer?.cancel(); + if (properties.showSuccess ?? false) { + notifier.update(NfcActivityWidgetCommand( + action: NfcActivityWidgetActionSetWidgetData( + child: NfcActivityClosingCountdownWidgetView( + closeInSec: 5, + child: _NfcActivityWidgetView( + title: properties.operationSuccess, + subtitle: l10n.s_nfc_dialog_remove_key, + inProgress: false, + ), + )))); + } else { + // directly hide + notifier.update(NfcActivityWidgetCommand( + action: const NfcActivityWidgetActionHideWidget(timeoutMs: 0))); + } + break; + case NfcActivity.processingInterrupted: + explicitAction = false; // next action might not be explicit + notifier.update(NfcActivityWidgetCommand( + action: NfcActivityWidgetActionSetWidgetData( + child: _NfcActivityWidgetView( + title: properties.operationFailure, + inProgress: false, + )))); + break; + case NfcActivity.notActive: + debugPrint('Received not handled notActive'); + break; + case NfcActivity.ready: + debugPrint('Received not handled ready'); + } + }); - _DialogProvider(this._withContext) { _channel.setMethodCallHandler((call) async { - final args = jsonDecode(call.arguments); + final notifier = ref.read(nfcActivityCommandNotifier.notifier); + final properties = ref.read(nfcActivityWidgetNotifier); switch (call.method) { - case 'close': - _closeDialog(); - break; case 'show': - await _showDialog(args['title'], args['description']); + explicitAction = true; + notifier.update(NfcActivityWidgetCommand( + action: NfcActivityWidgetActionShowWidget( + child: _NfcActivityWidgetView( + title: l10n.s_nfc_dialog_tap_for( + properties.operationName ?? '[OPERATION NAME MISSING]'), + subtitle: '', + inProgress: false, + )))); break; - case 'state': - await _updateDialogState(args['title'], args['description']); + + case 'close': + notifier.update(NfcActivityWidgetCommand( + action: const NfcActivityWidgetActionHideWidget(timeoutMs: 0))); break; + default: throw PlatformException( code: 'NotImplemented', @@ -129,71 +148,112 @@ class _DialogProvider { ); } }); + return 0; } - void _closeDialog() { - _controller?.close(); - _controller = null; + void cancelDialog() async { + debugPrint('Cancelled dialog'); + explicitAction = false; + await _channel.invokeMethod('cancel'); } - String _getTitle(BuildContext context, int? titleId) { - final l10n = AppLocalizations.of(context)!; - return switch (_DTitle.fromId(titleId)) { - _DTitle.tapKey => l10n.l_nfc_dialog_tap_key, - _DTitle.operationSuccessful => l10n.s_nfc_dialog_operation_success, - _DTitle.operationFailed => l10n.s_nfc_dialog_operation_failed, - _ => '' - }; - } + Future waitForDialogClosed() async { + final completer = Completer(); - String _getDialogDescription(BuildContext context, int? descriptionId) { - final l10n = AppLocalizations.of(context)!; - return switch (_DDesc.fromId(descriptionId)) { - _DDesc.oathResetApplet => l10n.s_nfc_dialog_oath_reset, - _DDesc.oathUnlockSession => l10n.s_nfc_dialog_oath_unlock, - _DDesc.oathSetPassword => l10n.s_nfc_dialog_oath_set_password, - _DDesc.oathUnsetPassword => l10n.s_nfc_dialog_oath_unset_password, - _DDesc.oathAddAccount => l10n.s_nfc_dialog_oath_add_account, - _DDesc.oathRenameAccount => l10n.s_nfc_dialog_oath_rename_account, - _DDesc.oathDeleteAccount => l10n.s_nfc_dialog_oath_delete_account, - _DDesc.oathCalculateCode => l10n.s_nfc_dialog_oath_calculate_code, - _DDesc.oathActionFailure => l10n.s_nfc_dialog_oath_failure, - _DDesc.oathAddMultipleAccounts => - l10n.s_nfc_dialog_oath_add_multiple_accounts, - _DDesc.fidoResetApplet => l10n.s_nfc_dialog_fido_reset, - _DDesc.fidoUnlockSession => l10n.s_nfc_dialog_fido_unlock, - _DDesc.fidoSetPin => l10n.l_nfc_dialog_fido_set_pin, - _DDesc.fidoDeleteCredential => l10n.s_nfc_dialog_fido_delete_credential, - _DDesc.fidoDeleteFingerprint => l10n.s_nfc_dialog_fido_delete_fingerprint, - _DDesc.fidoRenameFingerprint => l10n.s_nfc_dialog_fido_rename_fingerprint, - _DDesc.fidoActionFailure => l10n.s_nfc_dialog_fido_failure, - _ => '' - }; - } + Timer.periodic( + const Duration(milliseconds: 200), + (timer) { + if (!ref.read(nfcActivityWidgetNotifier.select((s) => s.isShowing))) { + timer.cancel(); + completer.complete(); + } + }, + ); - Future _updateDialogState(int? title, int? description) async { - await _withContext((context) async { - _controller?.updateContent( - title: _getTitle(context, title), - description: _getDialogDescription(context, description), - icon: (_DDesc.fromId(description) != _DDesc.oathActionFailure) - ? _icon - : const Icon(Icons.warning_amber_rounded, size: 64), - ); - }); - } - - Future _showDialog(int title, int description) async { - _controller = await _withContext((context) async { - return promptUserInteraction( - context, - title: _getTitle(context, title), - description: _getDialogDescription(context, description), - icon: _icon, - onCancel: () { - _channel.invokeMethod('cancel'); - }, - ); - }); + await completer.future; + } +} + +class _NfcActivityWidgetView extends StatelessWidget { + final bool inProgress; + final String? title; + final String? subtitle; + + const _NfcActivityWidgetView( + {required this.title, this.subtitle, this.inProgress = false}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + children: [ + Text(title ?? 'Missing title', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 8), + if (subtitle != null) + Text(subtitle!, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleSmall), + const SizedBox(height: 32), + inProgress + ? const Pulsing(child: Icon(Symbols.contactless, size: 64)) + : const Icon(Symbols.contactless, size: 64), + const SizedBox(height: 24) + ], + ), + ); + } +} + +class MethodChannelHelper { + final ProviderRef _ref; + final MethodChannel _channel; + + const MethodChannelHelper(this._ref, this._channel); + + Future invoke(String method, + {String? operationName, + String? operationSuccess, + String? operationProcessing, + String? operationFailure, + bool? showSuccess, + Map arguments = const {}}) async { + final notifier = _ref.read(nfcActivityWidgetNotifier.notifier); + notifier.setDialogProperties( + operationName: operationName, + operationProcessing: operationProcessing, + operationSuccess: operationSuccess, + operationFailure: operationFailure, + showSuccess: showSuccess); + + final result = await _channel.invokeMethod(method, arguments); + await _ref.read(androidDialogProvider.notifier).waitForDialogClosed(); + return result; + } +} + +class MethodChannelNotifier extends Notifier { + final MethodChannel _channel; + + MethodChannelNotifier(this._channel); + + @override + void build() {} + + Future invoke(String name, + [Map params = const {}]) async { + final notifier = ref.read(nfcActivityWidgetNotifier.notifier); + notifier.setDialogProperties( + operationName: params['operationName'], + operationProcessing: params['operationProcessing'], + operationSuccess: params['operationSuccess'], + operationFailure: params['operationFailure'], + showSuccess: params['showSuccess']); + + final result = await _channel.invokeMethod(name, params['callArgs']); + await ref.read(androidDialogProvider.notifier).waitForDialogClosed(); + return result; } } diff --git a/lib/android/views/nfc/nfc_activity_command_listener.dart b/lib/android/views/nfc/nfc_activity_command_listener.dart new file mode 100644 index 00000000..c25d2d3f --- /dev/null +++ b/lib/android/views/nfc/nfc_activity_command_listener.dart @@ -0,0 +1,84 @@ +/* + * 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. + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../app/models.dart'; +import '../../tap_request_dialog.dart'; +import 'nfc_activity_overlay.dart'; + +final nfcActivityCommandListener = Provider<_NfcActivityCommandListener>( + (ref) => _NfcActivityCommandListener(ref)); + +class _NfcActivityCommandListener { + final ProviderRef _ref; + ProviderSubscription? listener; + + _NfcActivityCommandListener(this._ref); + + void startListener(BuildContext context) { + debugPrint('XXX Started listener'); + listener?.close(); + listener = _ref.listen(nfcActivityCommandNotifier.select((c) => c.action), + (previous, action) { + debugPrint( + 'XXX Change in command for Overlay: $previous -> $action in context: $context'); + switch (action) { + case (NfcActivityWidgetActionShowWidget a): + _show(context, a.child); + break; + case (NfcActivityWidgetActionSetWidgetData a): + _ref.read(nfcActivityWidgetNotifier.notifier).update(a.child); + break; + case (NfcActivityWidgetActionHideWidget _): + _hide(context); + break; + case (NfcActivityWidgetActionCancelWidget _): + _ref.read(androidDialogProvider.notifier).cancelDialog(); + _hide(context); + break; + } + }); + } + + void _show(BuildContext context, Widget child) async { + final widgetNotifier = _ref.read(nfcActivityWidgetNotifier.notifier); + widgetNotifier.update(child); + if (!_ref.read(nfcActivityWidgetNotifier.select((s) => s.isShowing))) { + widgetNotifier.setShowing(true); + final result = await showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return const NfcBottomSheet(); + }); + + debugPrint('XXX result is: $result'); + if (result == null) { + // the modal sheet was cancelled by Back button, close button or dismiss + _ref.read(androidDialogProvider.notifier).cancelDialog(); + } + widgetNotifier.setShowing(false); + } + } + + void _hide(BuildContext context) { + if (_ref.read(nfcActivityWidgetNotifier.select((s) => s.isShowing))) { + Navigator.of(context).pop('AFTER OP'); + _ref.read(nfcActivityWidgetNotifier.notifier).setShowing(false); + } + } +} diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/views/nfc/nfc_activity_overlay.dart new file mode 100644 index 00000000..985b016e --- /dev/null +++ b/lib/android/views/nfc/nfc_activity_overlay.dart @@ -0,0 +1,172 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:material_symbols_icons/symbols.dart'; + +import '../../../app/models.dart'; +import '../../state.dart'; + +final nfcActivityCommandNotifier = NotifierProvider< + _NfcActivityWidgetCommandNotifier, + NfcActivityWidgetCommand>(_NfcActivityWidgetCommandNotifier.new); + +class _NfcActivityWidgetCommandNotifier + extends Notifier { + @override + NfcActivityWidgetCommand build() { + return NfcActivityWidgetCommand(action: const NfcActivityWidgetAction()); + } + + void update(NfcActivityWidgetCommand command) { + state = command; + } +} + +final nfcActivityWidgetNotifier = + NotifierProvider<_NfcActivityWidgetNotifier, NfcActivityWidgetState>( + _NfcActivityWidgetNotifier.new); + +class NfcActivityClosingCountdownWidgetView extends ConsumerStatefulWidget { + final int closeInSec; + final Widget child; + + const NfcActivityClosingCountdownWidgetView( + {super.key, required this.child, this.closeInSec = 3}); + + @override + ConsumerState createState() => + _NfcActivityClosingCountdownWidgetViewState(); +} + +class _NfcActivityClosingCountdownWidgetViewState + extends ConsumerState { + late int counter; + late Timer? timer; + bool shouldHide = false; + + @override + Widget build(BuildContext context) { + ref.listen(androidNfcActivityProvider, (previous, current) { + if (current == NfcActivity.ready) { + timer?.cancel(); + hideNow(); + } + }); + + return Stack( + fit: StackFit.loose, + children: [ + Center(child: widget.child), + Positioned( + bottom: 0, + right: 0, + child: counter > 0 + ? Padding( + padding: const EdgeInsets.all(8.0), + child: Text('Closing in $counter'), + ) + : const SizedBox(), + ) + ], + ); + } + + @override + void initState() { + super.initState(); + counter = widget.closeInSec; + timer = Timer(const Duration(seconds: 1), onTimer); + } + + @override + void dispose() { + timer?.cancel(); + super.dispose(); + } + + void onTimer() async { + timer?.cancel(); + setState(() { + counter--; + }); + + if (counter > 0) { + timer = Timer(const Duration(seconds: 1), onTimer); + } else { + hideNow(); + } + } + + void hideNow() { + debugPrint('XXX closing because have to!'); + ref.read(nfcActivityCommandNotifier.notifier).update( + NfcActivityWidgetCommand( + action: const NfcActivityWidgetActionHideWidget(timeoutMs: 0))); + } +} + +class _NfcActivityWidgetNotifier extends Notifier { + @override + NfcActivityWidgetState build() { + return NfcActivityWidgetState(isShowing: false, child: const SizedBox()); + } + + void update(Widget child) { + state = state.copyWith(child: child); + } + + void setShowing(bool value) { + state = state.copyWith(isShowing: value); + } + + void setDialogProperties( + {String? operationName, + String? operationProcessing, + String? operationSuccess, + String? operationFailure, + bool? showSuccess}) { + state = state.copyWith( + operationName: operationName, + operationProcessing: operationProcessing, + operationSuccess: operationSuccess, + operationFailure: operationFailure, + showSuccess: showSuccess); + } +} + +class NfcBottomSheet extends ConsumerWidget { + const NfcBottomSheet({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final widget = ref.watch(nfcActivityWidgetNotifier.select((s) => s.child)); + final showCloseButton = ref.watch( + nfcActivityWidgetNotifier.select((s) => s.showCloseButton ?? false)); + return Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (showCloseButton) const SizedBox(height: 8), + if (showCloseButton) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: const Icon(Symbols.close, fill: 1, size: 24)) + ], + ), + ), + if (showCloseButton) const SizedBox(height: 16), + if (!showCloseButton) const SizedBox(height: 48), + widget, + const SizedBox(height: 32), + ], + ); + } +} diff --git a/lib/app/models.dart b/lib/app/models.dart index 1f45ebab..433e3ab3 100755 --- a/lib/app/models.dart +++ b/lib/app/models.dart @@ -170,3 +170,46 @@ class _ColorConverter implements JsonConverter { @override int? toJson(Color? object) => object?.value; } + +class NfcActivityWidgetAction { + const NfcActivityWidgetAction(); +} + +class NfcActivityWidgetActionShowWidget extends NfcActivityWidgetAction { + final Widget child; + const NfcActivityWidgetActionShowWidget({required this.child}); +} + +class NfcActivityWidgetActionHideWidget extends NfcActivityWidgetAction { + final int timeoutMs; + const NfcActivityWidgetActionHideWidget({required this.timeoutMs}); +} + +class NfcActivityWidgetActionCancelWidget extends NfcActivityWidgetAction { + const NfcActivityWidgetActionCancelWidget(); +} + +class NfcActivityWidgetActionSetWidgetData extends NfcActivityWidgetAction { + final Widget child; + const NfcActivityWidgetActionSetWidgetData({required this.child}); +} + +@freezed +class NfcActivityWidgetState with _$NfcActivityWidgetState { + factory NfcActivityWidgetState( + {required bool isShowing, + required Widget child, + bool? showCloseButton, + bool? showSuccess, + String? operationName, + String? operationProcessing, + String? operationSuccess, + String? operationFailure}) = _NfcActivityWidgetState; +} + +@freezed +class NfcActivityWidgetCommand with _$NfcActivityWidgetCommand { + factory NfcActivityWidgetCommand({ + @Default(NfcActivityWidgetAction()) NfcActivityWidgetAction action, + }) = _NfcActivityWidgetCommand; +} diff --git a/lib/app/models.freezed.dart b/lib/app/models.freezed.dart index 37629fcb..52854fb3 100644 --- a/lib/app/models.freezed.dart +++ b/lib/app/models.freezed.dart @@ -1346,3 +1346,410 @@ abstract class _KeyCustomization implements KeyCustomization { _$$KeyCustomizationImplCopyWith<_$KeyCustomizationImpl> get copyWith => throw _privateConstructorUsedError; } + +/// @nodoc +mixin _$NfcActivityWidgetState { + bool get isShowing => throw _privateConstructorUsedError; + Widget get child => throw _privateConstructorUsedError; + bool? get showCloseButton => throw _privateConstructorUsedError; + bool? get showSuccess => throw _privateConstructorUsedError; + String? get operationName => throw _privateConstructorUsedError; + String? get operationProcessing => throw _privateConstructorUsedError; + String? get operationSuccess => throw _privateConstructorUsedError; + String? get operationFailure => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $NfcActivityWidgetStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $NfcActivityWidgetStateCopyWith<$Res> { + factory $NfcActivityWidgetStateCopyWith(NfcActivityWidgetState value, + $Res Function(NfcActivityWidgetState) then) = + _$NfcActivityWidgetStateCopyWithImpl<$Res, NfcActivityWidgetState>; + @useResult + $Res call( + {bool isShowing, + Widget child, + bool? showCloseButton, + bool? showSuccess, + String? operationName, + String? operationProcessing, + String? operationSuccess, + String? operationFailure}); +} + +/// @nodoc +class _$NfcActivityWidgetStateCopyWithImpl<$Res, + $Val extends NfcActivityWidgetState> + implements $NfcActivityWidgetStateCopyWith<$Res> { + _$NfcActivityWidgetStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isShowing = null, + Object? child = null, + Object? showCloseButton = freezed, + Object? showSuccess = freezed, + Object? operationName = freezed, + Object? operationProcessing = freezed, + Object? operationSuccess = freezed, + Object? operationFailure = freezed, + }) { + return _then(_value.copyWith( + isShowing: null == isShowing + ? _value.isShowing + : isShowing // ignore: cast_nullable_to_non_nullable + as bool, + child: null == child + ? _value.child + : child // ignore: cast_nullable_to_non_nullable + as Widget, + showCloseButton: freezed == showCloseButton + ? _value.showCloseButton + : showCloseButton // ignore: cast_nullable_to_non_nullable + as bool?, + showSuccess: freezed == showSuccess + ? _value.showSuccess + : showSuccess // ignore: cast_nullable_to_non_nullable + as bool?, + operationName: freezed == operationName + ? _value.operationName + : operationName // ignore: cast_nullable_to_non_nullable + as String?, + operationProcessing: freezed == operationProcessing + ? _value.operationProcessing + : operationProcessing // ignore: cast_nullable_to_non_nullable + as String?, + operationSuccess: freezed == operationSuccess + ? _value.operationSuccess + : operationSuccess // ignore: cast_nullable_to_non_nullable + as String?, + operationFailure: freezed == operationFailure + ? _value.operationFailure + : operationFailure // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$NfcActivityWidgetStateImplCopyWith<$Res> + implements $NfcActivityWidgetStateCopyWith<$Res> { + factory _$$NfcActivityWidgetStateImplCopyWith( + _$NfcActivityWidgetStateImpl value, + $Res Function(_$NfcActivityWidgetStateImpl) then) = + __$$NfcActivityWidgetStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {bool isShowing, + Widget child, + bool? showCloseButton, + bool? showSuccess, + String? operationName, + String? operationProcessing, + String? operationSuccess, + String? operationFailure}); +} + +/// @nodoc +class __$$NfcActivityWidgetStateImplCopyWithImpl<$Res> + extends _$NfcActivityWidgetStateCopyWithImpl<$Res, + _$NfcActivityWidgetStateImpl> + implements _$$NfcActivityWidgetStateImplCopyWith<$Res> { + __$$NfcActivityWidgetStateImplCopyWithImpl( + _$NfcActivityWidgetStateImpl _value, + $Res Function(_$NfcActivityWidgetStateImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isShowing = null, + Object? child = null, + Object? showCloseButton = freezed, + Object? showSuccess = freezed, + Object? operationName = freezed, + Object? operationProcessing = freezed, + Object? operationSuccess = freezed, + Object? operationFailure = freezed, + }) { + return _then(_$NfcActivityWidgetStateImpl( + isShowing: null == isShowing + ? _value.isShowing + : isShowing // ignore: cast_nullable_to_non_nullable + as bool, + child: null == child + ? _value.child + : child // ignore: cast_nullable_to_non_nullable + as Widget, + showCloseButton: freezed == showCloseButton + ? _value.showCloseButton + : showCloseButton // ignore: cast_nullable_to_non_nullable + as bool?, + showSuccess: freezed == showSuccess + ? _value.showSuccess + : showSuccess // ignore: cast_nullable_to_non_nullable + as bool?, + operationName: freezed == operationName + ? _value.operationName + : operationName // ignore: cast_nullable_to_non_nullable + as String?, + operationProcessing: freezed == operationProcessing + ? _value.operationProcessing + : operationProcessing // ignore: cast_nullable_to_non_nullable + as String?, + operationSuccess: freezed == operationSuccess + ? _value.operationSuccess + : operationSuccess // ignore: cast_nullable_to_non_nullable + as String?, + operationFailure: freezed == operationFailure + ? _value.operationFailure + : operationFailure // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$NfcActivityWidgetStateImpl implements _NfcActivityWidgetState { + _$NfcActivityWidgetStateImpl( + {required this.isShowing, + required this.child, + this.showCloseButton, + this.showSuccess, + this.operationName, + this.operationProcessing, + this.operationSuccess, + this.operationFailure}); + + @override + final bool isShowing; + @override + final Widget child; + @override + final bool? showCloseButton; + @override + final bool? showSuccess; + @override + final String? operationName; + @override + final String? operationProcessing; + @override + final String? operationSuccess; + @override + final String? operationFailure; + + @override + String toString() { + return 'NfcActivityWidgetState(isShowing: $isShowing, child: $child, showCloseButton: $showCloseButton, showSuccess: $showSuccess, operationName: $operationName, operationProcessing: $operationProcessing, operationSuccess: $operationSuccess, operationFailure: $operationFailure)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NfcActivityWidgetStateImpl && + (identical(other.isShowing, isShowing) || + other.isShowing == isShowing) && + (identical(other.child, child) || other.child == child) && + (identical(other.showCloseButton, showCloseButton) || + other.showCloseButton == showCloseButton) && + (identical(other.showSuccess, showSuccess) || + other.showSuccess == showSuccess) && + (identical(other.operationName, operationName) || + other.operationName == operationName) && + (identical(other.operationProcessing, operationProcessing) || + other.operationProcessing == operationProcessing) && + (identical(other.operationSuccess, operationSuccess) || + other.operationSuccess == operationSuccess) && + (identical(other.operationFailure, operationFailure) || + other.operationFailure == operationFailure)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + isShowing, + child, + showCloseButton, + showSuccess, + operationName, + operationProcessing, + operationSuccess, + operationFailure); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$NfcActivityWidgetStateImplCopyWith<_$NfcActivityWidgetStateImpl> + get copyWith => __$$NfcActivityWidgetStateImplCopyWithImpl< + _$NfcActivityWidgetStateImpl>(this, _$identity); +} + +abstract class _NfcActivityWidgetState implements NfcActivityWidgetState { + factory _NfcActivityWidgetState( + {required final bool isShowing, + required final Widget child, + final bool? showCloseButton, + final bool? showSuccess, + final String? operationName, + final String? operationProcessing, + final String? operationSuccess, + final String? operationFailure}) = _$NfcActivityWidgetStateImpl; + + @override + bool get isShowing; + @override + Widget get child; + @override + bool? get showCloseButton; + @override + bool? get showSuccess; + @override + String? get operationName; + @override + String? get operationProcessing; + @override + String? get operationSuccess; + @override + String? get operationFailure; + @override + @JsonKey(ignore: true) + _$$NfcActivityWidgetStateImplCopyWith<_$NfcActivityWidgetStateImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$NfcActivityWidgetCommand { + NfcActivityWidgetAction get action => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $NfcActivityWidgetCommandCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $NfcActivityWidgetCommandCopyWith<$Res> { + factory $NfcActivityWidgetCommandCopyWith(NfcActivityWidgetCommand value, + $Res Function(NfcActivityWidgetCommand) then) = + _$NfcActivityWidgetCommandCopyWithImpl<$Res, NfcActivityWidgetCommand>; + @useResult + $Res call({NfcActivityWidgetAction action}); +} + +/// @nodoc +class _$NfcActivityWidgetCommandCopyWithImpl<$Res, + $Val extends NfcActivityWidgetCommand> + implements $NfcActivityWidgetCommandCopyWith<$Res> { + _$NfcActivityWidgetCommandCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? action = null, + }) { + return _then(_value.copyWith( + action: null == action + ? _value.action + : action // ignore: cast_nullable_to_non_nullable + as NfcActivityWidgetAction, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$NfcActivityWidgetCommandImplCopyWith<$Res> + implements $NfcActivityWidgetCommandCopyWith<$Res> { + factory _$$NfcActivityWidgetCommandImplCopyWith( + _$NfcActivityWidgetCommandImpl value, + $Res Function(_$NfcActivityWidgetCommandImpl) then) = + __$$NfcActivityWidgetCommandImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({NfcActivityWidgetAction action}); +} + +/// @nodoc +class __$$NfcActivityWidgetCommandImplCopyWithImpl<$Res> + extends _$NfcActivityWidgetCommandCopyWithImpl<$Res, + _$NfcActivityWidgetCommandImpl> + implements _$$NfcActivityWidgetCommandImplCopyWith<$Res> { + __$$NfcActivityWidgetCommandImplCopyWithImpl( + _$NfcActivityWidgetCommandImpl _value, + $Res Function(_$NfcActivityWidgetCommandImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? action = null, + }) { + return _then(_$NfcActivityWidgetCommandImpl( + action: null == action + ? _value.action + : action // ignore: cast_nullable_to_non_nullable + as NfcActivityWidgetAction, + )); + } +} + +/// @nodoc + +class _$NfcActivityWidgetCommandImpl implements _NfcActivityWidgetCommand { + _$NfcActivityWidgetCommandImpl( + {this.action = const NfcActivityWidgetAction()}); + + @override + @JsonKey() + final NfcActivityWidgetAction action; + + @override + String toString() { + return 'NfcActivityWidgetCommand(action: $action)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NfcActivityWidgetCommandImpl && + (identical(other.action, action) || other.action == action)); + } + + @override + int get hashCode => Object.hash(runtimeType, action); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$NfcActivityWidgetCommandImplCopyWith<_$NfcActivityWidgetCommandImpl> + get copyWith => __$$NfcActivityWidgetCommandImplCopyWithImpl< + _$NfcActivityWidgetCommandImpl>(this, _$identity); +} + +abstract class _NfcActivityWidgetCommand implements NfcActivityWidgetCommand { + factory _NfcActivityWidgetCommand({final NfcActivityWidgetAction action}) = + _$NfcActivityWidgetCommandImpl; + + @override + NfcActivityWidgetAction get action; + @override + @JsonKey(ignore: true) + _$$NfcActivityWidgetCommandImplCopyWith<_$NfcActivityWidgetCommandImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9400d885..cf2314cb 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -885,28 +885,95 @@ "l_launch_app_on_usb_off": "Andere Anwendungen können den YubiKey über USB nutzen", "s_allow_screenshots": "Bildschirmfotos erlauben", - "l_nfc_dialog_tap_key": "Halten Sie Ihren Schlüssel dagegen", - "s_nfc_dialog_operation_success": "Erfolgreich", - "s_nfc_dialog_operation_failed": "Fehlgeschlagen", + "@_ndef_oath_actions": {}, + "s_nfc_dialog_oath_reset": null, + "s_nfc_dialog_oath_reset_processing": null, + "s_nfc_dialog_oath_reset_success": null, + "s_nfc_dialog_oath_reset_failure": null, - "s_nfc_dialog_oath_reset": "Aktion: OATH-Anwendung zurücksetzen", - "s_nfc_dialog_oath_unlock": "Aktion: OATH-Anwendung entsperren", - "s_nfc_dialog_oath_set_password": "Aktion: OATH-Passwort setzen", - "s_nfc_dialog_oath_unset_password": "Aktion: OATH-Passwort entfernen", - "s_nfc_dialog_oath_add_account": "Aktion: neues Konto hinzufügen", - "s_nfc_dialog_oath_rename_account": "Aktion: Konto umbenennen", - "s_nfc_dialog_oath_delete_account": "Aktion: Konto löschen", - "s_nfc_dialog_oath_calculate_code": "Aktion: OATH-Code berechnen", - "s_nfc_dialog_oath_failure": "OATH-Operation fehlgeschlagen", - "s_nfc_dialog_oath_add_multiple_accounts": "Aktion: mehrere Konten hinzufügen", + "s_nfc_dialog_oath_unlock": null, + "s_nfc_dialog_oath_unlock_processing": null, + "s_nfc_dialog_oath_unlock_success": null, + "s_nfc_dialog_oath_unlock_failure": null, - "s_nfc_dialog_fido_reset": "Aktion: FIDO-Anwendung zurücksetzen", - "s_nfc_dialog_fido_unlock": "Aktion: FIDO-Anwendung entsperren", - "l_nfc_dialog_fido_set_pin": "Aktion: FIDO-PIN setzen oder ändern", - "s_nfc_dialog_fido_delete_credential": "Aktion: Passkey löschen", - "s_nfc_dialog_fido_delete_fingerprint": "Aktion: Fingerabdruck löschen", - "s_nfc_dialog_fido_rename_fingerprint": "Aktion: Fingerabdruck umbenennen", - "s_nfc_dialog_fido_failure": "FIDO-Operation fehlgeschlagen", + "s_nfc_dialog_oath_set_password": null, + "s_nfc_dialog_oath_change_password": null, + "s_nfc_dialog_oath_set_password_processing": null, + "s_nfc_dialog_oath_change_password_processing": null, + "s_nfc_dialog_oath_set_password_success": null, + "s_nfc_dialog_oath_change_password_success": null, + "s_nfc_dialog_oath_set_password_failure": null, + "s_nfc_dialog_oath_change_password_failure": null, + + "s_nfc_dialog_oath_remove_password": null, + "s_nfc_dialog_oath_remove_password_processing": null, + "s_nfc_dialog_oath_remove_password_success": null, + "s_nfc_dialog_oath_remove_password_failure": null, + + "s_nfc_dialog_oath_add_account": null, + "s_nfc_dialog_oath_add_account_processing": null, + "s_nfc_dialog_oath_add_account_success": null, + "s_nfc_dialog_oath_add_account_failure": null, + + "s_nfc_dialog_oath_rename_account": null, + "s_nfc_dialog_oath_rename_account_processing": null, + "s_nfc_dialog_oath_rename_account_success": null, + "s_nfc_dialog_oath_rename_account_failure": null, + + "s_nfc_dialog_oath_delete_account": null, + "s_nfc_dialog_oath_delete_account_processing": null, + "s_nfc_dialog_oath_delete_account_success": null, + "s_nfc_dialog_oath_delete_account_failure": null, + + "s_nfc_dialog_oath_calculate_code": null, + "s_nfc_dialog_oath_calculate_code_processing": null, + "s_nfc_dialog_oath_calculate_code_success": null, + "s_nfc_dialog_oath_calculate_code_failure": null, + + "s_nfc_dialog_oath_add_multiple_accounts": null, + "s_nfc_dialog_oath_add_multiple_accounts_processing": null, + "s_nfc_dialog_oath_add_multiple_accounts_success": null, + "s_nfc_dialog_oath_add_multiple_accounts_failure": null, + + "@_ndef_fido_actions": {}, + "s_nfc_dialog_fido_reset": null, + "s_nfc_dialog_fido_reset_processing": null, + "s_nfc_dialog_fido_reset_success": null, + "s_nfc_dialog_fido_reset_failure": null, + + "s_nfc_dialog_fido_unlock": null, + "s_nfc_dialog_fido_unlock_processing": null, + "s_nfc_dialog_fido_unlock_success": null, + "s_nfc_dialog_fido_unlock_failure": null, + + "s_nfc_dialog_fido_set_pin": null, + "s_nfc_dialog_fido_set_pin_processing": null, + "s_nfc_dialog_fido_set_pin_success": null, + "s_nfc_dialog_fido_set_pin_failure": null, + + "s_nfc_dialog_fido_change_pin": null, + "s_nfc_dialog_fido_change_pin_processing": null, + "s_nfc_dialog_fido_change_pin_success": null, + "s_nfc_dialog_fido_change_pin_failure": null, + + "s_nfc_dialog_fido_delete_credential": null, + "s_nfc_dialog_fido_delete_credential_processing": null, + "s_nfc_dialog_fido_delete_credential_success": null, + "s_nfc_dialog_fido_delete_credential_failure": null, + + "@_ndef_operations": {}, + "s_nfc_dialog_tap_for": null, + "@s_nfc_dialog_tap_for": { + "placeholders": { + "operation": {} + } + }, + + "s_nfc_dialog_read_key": null, + "s_nfc_dialog_read_key_failure": null, + + "s_nfc_dialog_hold_key": null, + "s_nfc_dialog_remove_key": null, "@_ndef": {}, "p_ndef_set_otp": "OTP-Code wurde erfolgreich von Ihrem YubiKey in die Zwischenablage kopiert.", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9954b87e..a3e534d7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -885,28 +885,95 @@ "l_launch_app_on_usb_off": "Other apps can use the YubiKey over USB", "s_allow_screenshots": "Allow screenshots", - "l_nfc_dialog_tap_key": "Tap and hold your key", - "s_nfc_dialog_operation_success": "Success", - "s_nfc_dialog_operation_failed": "Failed", + "@_ndef_oath_actions": {}, + "s_nfc_dialog_oath_reset": "reset Accounts", + "s_nfc_dialog_oath_reset_processing": "Reset in progress", + "s_nfc_dialog_oath_reset_success": "Accounts reset", + "s_nfc_dialog_oath_reset_failure": "Failed to reset accounts", - "s_nfc_dialog_oath_reset": "Action: reset OATH application", - "s_nfc_dialog_oath_unlock": "Action: unlock OATH application", - "s_nfc_dialog_oath_set_password": "Action: set OATH password", - "s_nfc_dialog_oath_unset_password": "Action: remove OATH password", - "s_nfc_dialog_oath_add_account": "Action: add new account", - "s_nfc_dialog_oath_rename_account": "Action: rename account", - "s_nfc_dialog_oath_delete_account": "Action: delete account", - "s_nfc_dialog_oath_calculate_code": "Action: calculate OATH code", - "s_nfc_dialog_oath_failure": "OATH operation failed", - "s_nfc_dialog_oath_add_multiple_accounts": "Action: add multiple accounts", + "s_nfc_dialog_oath_unlock": "unlock", + "s_nfc_dialog_oath_unlock_processing": "Unlocking", + "s_nfc_dialog_oath_unlock_success": "Accounts unlocked", + "s_nfc_dialog_oath_unlock_failure": "Failed to unlock", - "s_nfc_dialog_fido_reset": "Action: reset FIDO application", - "s_nfc_dialog_fido_unlock": "Action: unlock FIDO application", - "l_nfc_dialog_fido_set_pin": "Action: set or change the FIDO PIN", - "s_nfc_dialog_fido_delete_credential": "Action: delete Passkey", - "s_nfc_dialog_fido_delete_fingerprint": "Action: delete fingerprint", - "s_nfc_dialog_fido_rename_fingerprint": "Action: rename fingerprint", - "s_nfc_dialog_fido_failure": "FIDO operation failed", + "s_nfc_dialog_oath_set_password": "set password", + "s_nfc_dialog_oath_change_password": "change password", + "s_nfc_dialog_oath_set_password_processing": "Setting password", + "s_nfc_dialog_oath_change_password_processing": "Changing password", + "s_nfc_dialog_oath_set_password_success": "Password set", + "s_nfc_dialog_oath_change_password_success": "Password changed", + "s_nfc_dialog_oath_set_password_failure": "Failed to set password", + "s_nfc_dialog_oath_change_password_failure": "Failed to change password", + + "s_nfc_dialog_oath_remove_password": "remove password", + "s_nfc_dialog_oath_remove_password_processing": "Removing password", + "s_nfc_dialog_oath_remove_password_success": "Password removed", + "s_nfc_dialog_oath_remove_password_failure": "Failed to remove password", + + "s_nfc_dialog_oath_add_account": "add account", + "s_nfc_dialog_oath_add_account_processing": "Adding account", + "s_nfc_dialog_oath_add_account_success": "Account added", + "s_nfc_dialog_oath_add_account_failure": "Failed to add account", + + "s_nfc_dialog_oath_rename_account": "rename account", + "s_nfc_dialog_oath_rename_account_processing": "Renaming account", + "s_nfc_dialog_oath_rename_account_success": "Account renamed", + "s_nfc_dialog_oath_rename_account_failure": "Failed to rename account", + + "s_nfc_dialog_oath_delete_account": "delete account", + "s_nfc_dialog_oath_delete_account_processing": "Deleting account", + "s_nfc_dialog_oath_delete_account_success": "Account deleted", + "s_nfc_dialog_oath_delete_account_failure": "Failed to delete account", + + "s_nfc_dialog_oath_calculate_code": "calculate code", + "s_nfc_dialog_oath_calculate_code_processing": "Calculating", + "s_nfc_dialog_oath_calculate_code_success": "Code calculated", + "s_nfc_dialog_oath_calculate_code_failure": "Failed to calculate code", + + "s_nfc_dialog_oath_add_multiple_accounts": "add selected accounts", + "s_nfc_dialog_oath_add_multiple_accounts_processing": "Adding accounts", + "s_nfc_dialog_oath_add_multiple_accounts_success": "Accounts added", + "s_nfc_dialog_oath_add_multiple_accounts_failure": "Failed to add accounts", + + "@_ndef_fido_actions": {}, + "s_nfc_dialog_fido_reset": "reset FIDO application", + "s_nfc_dialog_fido_reset_processing": "Resetting FIDO", + "s_nfc_dialog_fido_reset_success": "FIDO reset", + "s_nfc_dialog_fido_reset_failure": "FIDO reset failed", + + "s_nfc_dialog_fido_unlock": "unlock", + "s_nfc_dialog_fido_unlock_processing": "Unlocking", + "s_nfc_dialog_fido_unlock_success": "unlocked", + "s_nfc_dialog_fido_unlock_failure": "Failed to unlock", + + "s_nfc_dialog_fido_set_pin": "set PIN", + "s_nfc_dialog_fido_set_pin_processing": "Setting PIN", + "s_nfc_dialog_fido_set_pin_success": "PIN set", + "s_nfc_dialog_fido_set_pin_failure": "Failure setting PIN", + + "s_nfc_dialog_fido_change_pin": "change PIN", + "s_nfc_dialog_fido_change_pin_processing": "Changing PIN", + "s_nfc_dialog_fido_change_pin_success": "PIN changed", + "s_nfc_dialog_fido_change_pin_failure": "Failure changing PIN", + + "s_nfc_dialog_fido_delete_credential": "delete passkey", + "s_nfc_dialog_fido_delete_credential_processing": "Deleting passkey", + "s_nfc_dialog_fido_delete_credential_success": "Passkey deleted", + "s_nfc_dialog_fido_delete_credential_failure": "Failed to delete passkey", + + "@_ndef_operations": {}, + "s_nfc_dialog_tap_for": "Tap YubiKey to {operation}", + "@s_nfc_dialog_tap_for": { + "placeholders": { + "operation": {} + } + }, + + "s_nfc_dialog_read_key": "Reading YubiKey", + "s_nfc_dialog_read_key_failure": "Failed to read YubiKey, try again", + + "s_nfc_dialog_hold_key": "Hold YubiKey", + "s_nfc_dialog_remove_key": "You can remove YubiKey", "@_ndef": {}, "p_ndef_set_otp": "Successfully copied OTP code from YubiKey to clipboard.", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 9e43e088..5089233d 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -885,28 +885,95 @@ "l_launch_app_on_usb_off": "D'autres applications peuvent utiliser la YubiKey en USB", "s_allow_screenshots": "Autoriser captures d'écran", - "l_nfc_dialog_tap_key": "Appuyez et maintenez votre clé", - "s_nfc_dialog_operation_success": "Succès", - "s_nfc_dialog_operation_failed": "Échec", + "@_ndef_oath_actions": {}, + "s_nfc_dialog_oath_reset": null, + "s_nfc_dialog_oath_reset_processing": null, + "s_nfc_dialog_oath_reset_success": null, + "s_nfc_dialog_oath_reset_failure": null, - "s_nfc_dialog_oath_reset": "Action\u00a0: réinitialiser applet OATH", - "s_nfc_dialog_oath_unlock": "Action\u00a0: débloquer applet OATH", - "s_nfc_dialog_oath_set_password": "Action\u00a0: définir mot de passe OATH", - "s_nfc_dialog_oath_unset_password": "Action\u00a0: supprimer mot de passe OATH", - "s_nfc_dialog_oath_add_account": "Action\u00a0: ajouter nouveau compte", - "s_nfc_dialog_oath_rename_account": "Action\u00a0: renommer compte", - "s_nfc_dialog_oath_delete_account": "Action\u00a0: supprimer compte", - "s_nfc_dialog_oath_calculate_code": "Action\u00a0: calculer code OATH", - "s_nfc_dialog_oath_failure": "Opération OATH impossible", - "s_nfc_dialog_oath_add_multiple_accounts": "Action\u00a0: ajouter plusieurs comptes", + "s_nfc_dialog_oath_unlock": null, + "s_nfc_dialog_oath_unlock_processing": null, + "s_nfc_dialog_oath_unlock_success": null, + "s_nfc_dialog_oath_unlock_failure": null, - "s_nfc_dialog_fido_reset": "Action : réinitialiser l'application FIDO", - "s_nfc_dialog_fido_unlock": "Action : déverrouiller l'application FIDO", - "l_nfc_dialog_fido_set_pin": "Action : définir ou modifier le code PIN FIDO", - "s_nfc_dialog_fido_delete_credential": "Action : supprimer le Passkey", - "s_nfc_dialog_fido_delete_fingerprint": "Action : supprimer l'empreinte digitale", - "s_nfc_dialog_fido_rename_fingerprint": "Action : renommer l'empreinte digitale", - "s_nfc_dialog_fido_failure": "Échec de l'opération FIDO", + "s_nfc_dialog_oath_set_password": null, + "s_nfc_dialog_oath_change_password": null, + "s_nfc_dialog_oath_set_password_processing": null, + "s_nfc_dialog_oath_change_password_processing": null, + "s_nfc_dialog_oath_set_password_success": null, + "s_nfc_dialog_oath_change_password_success": null, + "s_nfc_dialog_oath_set_password_failure": null, + "s_nfc_dialog_oath_change_password_failure": null, + + "s_nfc_dialog_oath_remove_password": null, + "s_nfc_dialog_oath_remove_password_processing": null, + "s_nfc_dialog_oath_remove_password_success": null, + "s_nfc_dialog_oath_remove_password_failure": null, + + "s_nfc_dialog_oath_add_account": null, + "s_nfc_dialog_oath_add_account_processing": null, + "s_nfc_dialog_oath_add_account_success": null, + "s_nfc_dialog_oath_add_account_failure": null, + + "s_nfc_dialog_oath_rename_account": null, + "s_nfc_dialog_oath_rename_account_processing": null, + "s_nfc_dialog_oath_rename_account_success": null, + "s_nfc_dialog_oath_rename_account_failure": null, + + "s_nfc_dialog_oath_delete_account": null, + "s_nfc_dialog_oath_delete_account_processing": null, + "s_nfc_dialog_oath_delete_account_success": null, + "s_nfc_dialog_oath_delete_account_failure": null, + + "s_nfc_dialog_oath_calculate_code": null, + "s_nfc_dialog_oath_calculate_code_processing": null, + "s_nfc_dialog_oath_calculate_code_success": null, + "s_nfc_dialog_oath_calculate_code_failure": null, + + "s_nfc_dialog_oath_add_multiple_accounts": null, + "s_nfc_dialog_oath_add_multiple_accounts_processing": null, + "s_nfc_dialog_oath_add_multiple_accounts_success": null, + "s_nfc_dialog_oath_add_multiple_accounts_failure": null, + + "@_ndef_fido_actions": {}, + "s_nfc_dialog_fido_reset": null, + "s_nfc_dialog_fido_reset_processing": null, + "s_nfc_dialog_fido_reset_success": null, + "s_nfc_dialog_fido_reset_failure": null, + + "s_nfc_dialog_fido_unlock": null, + "s_nfc_dialog_fido_unlock_processing": null, + "s_nfc_dialog_fido_unlock_success": null, + "s_nfc_dialog_fido_unlock_failure": null, + + "s_nfc_dialog_fido_set_pin": null, + "s_nfc_dialog_fido_set_pin_processing": null, + "s_nfc_dialog_fido_set_pin_success": null, + "s_nfc_dialog_fido_set_pin_failure": null, + + "s_nfc_dialog_fido_change_pin": null, + "s_nfc_dialog_fido_change_pin_processing": null, + "s_nfc_dialog_fido_change_pin_success": null, + "s_nfc_dialog_fido_change_pin_failure": null, + + "s_nfc_dialog_fido_delete_credential": null, + "s_nfc_dialog_fido_delete_credential_processing": null, + "s_nfc_dialog_fido_delete_credential_success": null, + "s_nfc_dialog_fido_delete_credential_failure": null, + + "@_ndef_operations": {}, + "s_nfc_dialog_tap_for": null, + "@s_nfc_dialog_tap_for": { + "placeholders": { + "operation": {} + } + }, + + "s_nfc_dialog_read_key": null, + "s_nfc_dialog_read_key_failure": null, + + "s_nfc_dialog_hold_key": null, + "s_nfc_dialog_remove_key": null, "@_ndef": {}, "p_ndef_set_otp": "Code OTP copié de la YubiKey dans le presse-papiers.", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 09ac00a0..2675abf7 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -885,28 +885,95 @@ "l_launch_app_on_usb_off": "他のアプリがUSB経由でYubiKeyを使用できます", "s_allow_screenshots": "スクリーンショットを許可", - "l_nfc_dialog_tap_key": "キーをタップして長押しします", - "s_nfc_dialog_operation_success": "成功", - "s_nfc_dialog_operation_failed": "失敗", - + "@_ndef_oath_actions": {}, "s_nfc_dialog_oath_reset": "アクション:OATHアプレットをリセット", - "s_nfc_dialog_oath_unlock": "アクション:OATHアプレットをロック解除", - "s_nfc_dialog_oath_set_password": "アクション:OATHパスワードを設定", - "s_nfc_dialog_oath_unset_password": "アクション:OATHパスワードを削除", - "s_nfc_dialog_oath_add_account": "アクション:新しいアカウントを追加", - "s_nfc_dialog_oath_rename_account": "アクション:アカウント名を変更", - "s_nfc_dialog_oath_delete_account": "アクション:アカウントを削除", - "s_nfc_dialog_oath_calculate_code": "アクション:OATHコードを計算", - "s_nfc_dialog_oath_failure": "OATH操作が失敗しました", - "s_nfc_dialog_oath_add_multiple_accounts": "アクション:複数アカウントを追加", + "s_nfc_dialog_oath_reset_processing": null, + "s_nfc_dialog_oath_reset_success": null, + "s_nfc_dialog_oath_reset_failure": null, + "s_nfc_dialog_oath_unlock": "アクション:OATHアプレットをロック解除", + "s_nfc_dialog_oath_unlock_processing": null, + "s_nfc_dialog_oath_unlock_success": null, + "s_nfc_dialog_oath_unlock_failure": null, + + "s_nfc_dialog_oath_set_password": "アクション:OATHパスワードを設定", + "s_nfc_dialog_oath_change_password": null, + "s_nfc_dialog_oath_set_password_processing": null, + "s_nfc_dialog_oath_change_password_processing": null, + "s_nfc_dialog_oath_set_password_success": null, + "s_nfc_dialog_oath_change_password_success": null, + "s_nfc_dialog_oath_set_password_failure": null, + "s_nfc_dialog_oath_change_password_failure": null, + + "s_nfc_dialog_oath_remove_password": null, + "s_nfc_dialog_oath_remove_password_processing": null, + "s_nfc_dialog_oath_remove_password_success": null, + "s_nfc_dialog_oath_remove_password_failure": null, + + "s_nfc_dialog_oath_add_account": "アクション:新しいアカウントを追加", + "s_nfc_dialog_oath_add_account_processing": null, + "s_nfc_dialog_oath_add_account_success": null, + "s_nfc_dialog_oath_add_account_failure": null, + + "s_nfc_dialog_oath_rename_account": "アクション:アカウント名を変更", + "s_nfc_dialog_oath_rename_account_processing": null, + "s_nfc_dialog_oath_rename_account_success": null, + "s_nfc_dialog_oath_rename_account_failure": null, + + "s_nfc_dialog_oath_delete_account": "アクション:アカウントを削除", + "s_nfc_dialog_oath_delete_account_processing": null, + "s_nfc_dialog_oath_delete_account_success": null, + "s_nfc_dialog_oath_delete_account_failure": null, + + "s_nfc_dialog_oath_calculate_code": "アクション:OATHコードを計算", + "s_nfc_dialog_oath_calculate_code_processing": null, + "s_nfc_dialog_oath_calculate_code_success": null, + "s_nfc_dialog_oath_calculate_code_failure": null, + + "s_nfc_dialog_oath_add_multiple_accounts": "アクション:複数アカウントを追加", + "s_nfc_dialog_oath_add_multiple_accounts_processing": null, + "s_nfc_dialog_oath_add_multiple_accounts_success": null, + "s_nfc_dialog_oath_add_multiple_accounts_failure": null, + + "@_ndef_fido_actions": {}, "s_nfc_dialog_fido_reset": "アクション: FIDOアプリケーションをリセット", + "s_nfc_dialog_fido_reset_processing": null, + "s_nfc_dialog_fido_reset_success": null, + "s_nfc_dialog_fido_reset_failure": null, + "s_nfc_dialog_fido_unlock": "アクション:FIDOアプリケーションのロックを解除する", - "l_nfc_dialog_fido_set_pin": "アクション:FIDOのPINの設定または変更", + "s_nfc_dialog_fido_unlock_processing": null, + "s_nfc_dialog_fido_unlock_success": null, + "s_nfc_dialog_fido_unlock_failure": null, + + "s_nfc_dialog_fido_set_pin": null, + "s_nfc_dialog_fido_set_pin_processing": null, + "s_nfc_dialog_fido_set_pin_success": null, + "s_nfc_dialog_fido_set_pin_failure": null, + + "s_nfc_dialog_fido_change_pin": null, + "s_nfc_dialog_fido_change_pin_processing": null, + "s_nfc_dialog_fido_change_pin_success": null, + "s_nfc_dialog_fido_change_pin_failure": null, + "s_nfc_dialog_fido_delete_credential": "アクション: パスキーを削除", - "s_nfc_dialog_fido_delete_fingerprint": "アクション: 指紋の削除", - "s_nfc_dialog_fido_rename_fingerprint": "アクション: 指紋の名前を変更する", - "s_nfc_dialog_fido_failure": "FIDO操作に失敗しました", + "s_nfc_dialog_fido_delete_credential_processing": null, + "s_nfc_dialog_fido_delete_credential_success": null, + "s_nfc_dialog_fido_delete_credential_failure": null, + + "@_ndef_operations": {}, + "s_nfc_dialog_tap_for": null, + "@s_nfc_dialog_tap_for": { + "placeholders": { + "operation": {} + } + }, + + "s_nfc_dialog_read_key": null, + "s_nfc_dialog_read_key_failure": null, + + "s_nfc_dialog_hold_key": null, + "s_nfc_dialog_remove_key": null, "@_ndef": {}, "p_ndef_set_otp": "OTPコードがYubiKeyからクリップボードに正常にコピーされました。", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 50623db1..ad1fa188 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -885,28 +885,95 @@ "l_launch_app_on_usb_off": "Inne aplikacje mogą korzystać z YubiKey przez USB", "s_allow_screenshots": "Zezwalaj na zrzuty ekranu", - "l_nfc_dialog_tap_key": null, - "s_nfc_dialog_operation_success": "Powodzenie", - "s_nfc_dialog_operation_failed": "Niepowodzenie", + "@_ndef_oath_actions": {}, + "s_nfc_dialog_oath_reset": null, + "s_nfc_dialog_oath_reset_processing": null, + "s_nfc_dialog_oath_reset_success": null, + "s_nfc_dialog_oath_reset_failure": null, - "s_nfc_dialog_oath_reset": "Działanie: resetuj aplet OATH", - "s_nfc_dialog_oath_unlock": "Działanie: odblokuj aplet OATH", - "s_nfc_dialog_oath_set_password": "Działanie: ustaw hasło OATH", - "s_nfc_dialog_oath_unset_password": "Działanie: usuń hasło OATH", - "s_nfc_dialog_oath_add_account": "Działanie: dodaj nowe konto", - "s_nfc_dialog_oath_rename_account": "Działanie: zmień nazwę konta", - "s_nfc_dialog_oath_delete_account": "Działanie: usuń konto", - "s_nfc_dialog_oath_calculate_code": "Działanie: oblicz kod OATH", - "s_nfc_dialog_oath_failure": "Operacja OATH nie powiodła się", - "s_nfc_dialog_oath_add_multiple_accounts": "Działanie: dodawanie wielu kont", + "s_nfc_dialog_oath_unlock": null, + "s_nfc_dialog_oath_unlock_processing": null, + "s_nfc_dialog_oath_unlock_success": null, + "s_nfc_dialog_oath_unlock_failure": null, + "s_nfc_dialog_oath_set_password": null, + "s_nfc_dialog_oath_change_password": null, + "s_nfc_dialog_oath_set_password_processing": null, + "s_nfc_dialog_oath_change_password_processing": null, + "s_nfc_dialog_oath_set_password_success": null, + "s_nfc_dialog_oath_change_password_success": null, + "s_nfc_dialog_oath_set_password_failure": null, + "s_nfc_dialog_oath_change_password_failure": null, + + "s_nfc_dialog_oath_remove_password": null, + "s_nfc_dialog_oath_remove_password_processing": null, + "s_nfc_dialog_oath_remove_password_success": null, + "s_nfc_dialog_oath_remove_password_failure": null, + + "s_nfc_dialog_oath_add_account": null, + "s_nfc_dialog_oath_add_account_processing": null, + "s_nfc_dialog_oath_add_account_success": null, + "s_nfc_dialog_oath_add_account_failure": null, + + "s_nfc_dialog_oath_rename_account": null, + "s_nfc_dialog_oath_rename_account_processing": null, + "s_nfc_dialog_oath_rename_account_success": null, + "s_nfc_dialog_oath_rename_account_failure": null, + + "s_nfc_dialog_oath_delete_account": null, + "s_nfc_dialog_oath_delete_account_processing": null, + "s_nfc_dialog_oath_delete_account_success": null, + "s_nfc_dialog_oath_delete_account_failure": null, + + "s_nfc_dialog_oath_calculate_code": null, + "s_nfc_dialog_oath_calculate_code_processing": null, + "s_nfc_dialog_oath_calculate_code_success": null, + "s_nfc_dialog_oath_calculate_code_failure": null, + + "s_nfc_dialog_oath_add_multiple_accounts": null, + "s_nfc_dialog_oath_add_multiple_accounts_processing": null, + "s_nfc_dialog_oath_add_multiple_accounts_success": null, + "s_nfc_dialog_oath_add_multiple_accounts_failure": null, + + "@_ndef_fido_actions": {}, "s_nfc_dialog_fido_reset": null, + "s_nfc_dialog_fido_reset_processing": null, + "s_nfc_dialog_fido_reset_success": null, + "s_nfc_dialog_fido_reset_failure": null, + "s_nfc_dialog_fido_unlock": null, - "l_nfc_dialog_fido_set_pin": null, + "s_nfc_dialog_fido_unlock_processing": null, + "s_nfc_dialog_fido_unlock_success": null, + "s_nfc_dialog_fido_unlock_failure": null, + + "s_nfc_dialog_fido_set_pin": null, + "s_nfc_dialog_fido_set_pin_processing": null, + "s_nfc_dialog_fido_set_pin_success": null, + "s_nfc_dialog_fido_set_pin_failure": null, + + "s_nfc_dialog_fido_change_pin": null, + "s_nfc_dialog_fido_change_pin_processing": null, + "s_nfc_dialog_fido_change_pin_success": null, + "s_nfc_dialog_fido_change_pin_failure": null, + "s_nfc_dialog_fido_delete_credential": null, - "s_nfc_dialog_fido_delete_fingerprint": null, - "s_nfc_dialog_fido_rename_fingerprint": null, - "s_nfc_dialog_fido_failure": null, + "s_nfc_dialog_fido_delete_credential_processing": null, + "s_nfc_dialog_fido_delete_credential_success": null, + "s_nfc_dialog_fido_delete_credential_failure": null, + + "@_ndef_operations": {}, + "s_nfc_dialog_tap_for": null, + "@s_nfc_dialog_tap_for": { + "placeholders": { + "operation": {} + } + }, + + "s_nfc_dialog_read_key": null, + "s_nfc_dialog_read_key_failure": null, + + "s_nfc_dialog_hold_key": null, + "s_nfc_dialog_remove_key": null, "@_ndef": {}, "p_ndef_set_otp": "OTP zostało skopiowane do schowka.", diff --git a/lib/theme.dart b/lib/theme.dart index d929fa31..b2d82e13 100755 --- a/lib/theme.dart +++ b/lib/theme.dart @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Yubico. + * Copyright (C) 2021-2024 Yubico. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; const defaultPrimaryColor = Colors.lightGreen; @@ -50,6 +51,9 @@ class AppTheme { fontFamily: 'Roboto', appBarTheme: const AppBarTheme( color: Colors.transparent, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarIconBrightness: Brightness.dark, + statusBarColor: Colors.transparent), ), listTileTheme: const ListTileThemeData( // For alignment under menu button @@ -81,6 +85,9 @@ class AppTheme { scaffoldBackgroundColor: colorScheme.surface, appBarTheme: const AppBarTheme( color: Colors.transparent, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarIconBrightness: Brightness.light, + statusBarColor: Colors.transparent), ), listTileTheme: const ListTileThemeData( // For alignment under menu button diff --git a/lib/widgets/pulsing.dart b/lib/widgets/pulsing.dart new file mode 100644 index 00000000..9e941b1a --- /dev/null +++ b/lib/widgets/pulsing.dart @@ -0,0 +1,68 @@ +/* + * 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. + */ + +import 'package:flutter/material.dart'; + +class Pulsing extends StatefulWidget { + final Widget child; + + const Pulsing({super.key, required this.child}); + + @override + State createState() => _PulsingState(); +} + +class _PulsingState extends State with SingleTickerProviderStateMixin { + late final AnimationController controller; + late final Animation animationScale; + + late final CurvedAnimation curvedAnimation; + + static const _duration = Duration(milliseconds: 400); + + @override + Widget build(BuildContext context) { + return SizedBox( + child: Transform.scale(scale: animationScale.value, child: widget.child), + ); + } + + @override + void initState() { + super.initState(); + controller = AnimationController( + duration: _duration, + vsync: this, + ); + curvedAnimation = CurvedAnimation( + parent: controller, curve: Curves.easeIn, reverseCurve: Curves.easeOut); + animationScale = Tween( + begin: 1.0, + end: 1.2, + ).animate(curvedAnimation) + ..addListener(() { + setState(() {}); + }); + + controller.repeat(reverse: true); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } +} From 0cf46fdd3d088922aa97e1d43e250dbcd81bc5ce Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 28 Aug 2024 16:41:32 +0200 Subject: [PATCH 07/50] shorten localization key names --- lib/android/fido/state.dart | 41 ++++----- lib/android/oath/state.dart | 92 +++++++++---------- lib/android/tap_request_dialog.dart | 10 +- lib/l10n/app_de.arb | 138 ++++++++++++++-------------- lib/l10n/app_en.arb | 138 ++++++++++++++-------------- lib/l10n/app_fr.arb | 138 ++++++++++++++-------------- lib/l10n/app_ja.arb | 138 ++++++++++++++-------------- lib/l10n/app_pl.arb | 138 ++++++++++++++-------------- 8 files changed, 414 insertions(+), 419 deletions(-) diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index b7cc7c88..a4de36fc 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -384,21 +384,20 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { 'rpId': credential.rpId, 'credentialId': credential.credentialId }, - 'operationName': l10n.s_nfc_dialog_fido_delete_credential, - 'operationProcessing': - l10n.s_nfc_dialog_fido_delete_credential_processing, - 'operationSuccess': l10n.s_nfc_dialog_fido_delete_credential_success, - 'operationFailure': l10n.s_nfc_dialog_fido_delete_credential_failure, + 'operationName': l10n.s_nfc_fido_delete_passkey, + 'operationProcessing': l10n.s_nfc_fido_delete_passkey_processing, + 'operationSuccess': l10n.s_nfc_fido_delete_passkey_success, + 'operationFailure': l10n.s_nfc_fido_delete_passkey_failure, 'showSuccess': true }); Future cancelReset() async => invoke('cancelReset'); Future reset() async => invoke('reset', { - 'operationName': l10n.s_nfc_dialog_fido_reset, - 'operationProcessing': l10n.s_nfc_dialog_fido_reset_processing, - 'operationSuccess': l10n.s_nfc_dialog_fido_reset_success, - 'operationFailure': l10n.s_nfc_dialog_fido_reset_failure, + 'operationName': l10n.s_nfc_fido_reset, + 'operationProcessing': l10n.s_nfc_fido_reset_processing, + 'operationSuccess': l10n.s_nfc_fido_reset_success, + 'operationFailure': l10n.s_nfc_fido_reset_failure, 'showSuccess': true }); @@ -406,26 +405,26 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { invoke('setPin', { 'callArgs': {'pin': oldPin, 'newPin': newPin}, 'operationName': oldPin != null - ? l10n.s_nfc_dialog_fido_change_pin - : l10n.s_nfc_dialog_fido_set_pin, + ? l10n.s_nfc_fido_change_pin + : l10n.s_nfc_fido_set_pin, 'operationProcessing': oldPin != null - ? l10n.s_nfc_dialog_fido_change_pin_processing - : l10n.s_nfc_dialog_fido_set_pin_processing, + ? l10n.s_nfc_fido_change_pin_processing + : l10n.s_nfc_fido_set_pin_processing, 'operationSuccess': oldPin != null - ? l10n.s_nfc_dialog_fido_change_pin_success - : l10n.s_nfc_dialog_fido_set_pin_success, + ? l10n.s_nfc_fido_change_pin_success + : l10n.s_nfc_fido_set_pin_success, 'operationFailure': oldPin != null - ? l10n.s_nfc_dialog_fido_change_pin_failure - : l10n.s_nfc_dialog_fido_set_pin_failure, + ? l10n.s_nfc_fido_change_pin_failure + : l10n.s_nfc_fido_set_pin_failure, 'showSuccess': true }); Future unlock(String pin) async => invoke('unlock', { 'callArgs': {'pin': pin}, - 'operationName': l10n.s_nfc_dialog_fido_unlock, - 'operationProcessing': l10n.s_nfc_dialog_fido_unlock_processing, - 'operationSuccess': l10n.s_nfc_dialog_fido_unlock_success, - 'operationFailure': l10n.s_nfc_dialog_fido_unlock_failure, + 'operationName': l10n.s_nfc_fido_unlock, + 'operationProcessing': l10n.s_nfc_fido_unlock_processing, + 'operationSuccess': l10n.s_nfc_fido_unlock_success, + 'operationFailure': l10n.s_nfc_fido_unlock_failure, 'showSuccess': true }); diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index fbcc6c3e..33937b6b 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -329,46 +329,45 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { void build() {} Future reset() async => invoke('reset', { - 'operationName': l10n.s_nfc_dialog_oath_reset, - 'operationProcessing': l10n.s_nfc_dialog_oath_reset_processing, - 'operationSuccess': l10n.s_nfc_dialog_oath_reset_success, - 'operationFailure': l10n.s_nfc_dialog_oath_reset_failure + 'operationName': l10n.s_nfc_oath_reset, + 'operationProcessing': l10n.s_nfc_oath_reset_processing, + 'operationSuccess': l10n.s_nfc_oath_reset_success, + 'operationFailure': l10n.s_nfc_oath_reset_failure }); Future unlock(String password, {bool remember = false}) async => invoke('unlock', { 'callArgs': {'password': password, 'remember': remember}, - 'operationName': l10n.s_nfc_dialog_oath_unlock, - 'operationProcessing': l10n.s_nfc_dialog_oath_unlock_processing, - 'operationSuccess': l10n.s_nfc_dialog_oath_unlock_success, - 'operationFailure': l10n.s_nfc_dialog_oath_unlock_failure, + 'operationName': l10n.s_nfc_oath_unlock, + 'operationProcessing': l10n.s_nfc_oath_unlock_processing, + 'operationSuccess': l10n.s_nfc_oath_unlock_success, + 'operationFailure': l10n.s_nfc_oath_unlock_failure, }); Future setPassword(String? current, String password) async => invoke('setPassword', { 'callArgs': {'current': current, 'password': password}, 'operationName': current != null - ? l10n.s_nfc_dialog_oath_change_password - : l10n.s_nfc_dialog_oath_set_password, + ? l10n.s_nfc_oath_change_password + : l10n.s_nfc_oath_set_password, 'operationProcessing': current != null - ? l10n.s_nfc_dialog_oath_change_password_processing - : l10n.s_nfc_dialog_oath_set_password_processing, + ? l10n.s_nfc_oath_change_password_processing + : l10n.s_nfc_oath_set_password_processing, 'operationSuccess': current != null - ? l10n.s_nfc_dialog_oath_change_password_success - : l10n.s_nfc_dialog_oath_set_password_success, + ? l10n.s_nfc_oath_change_password_success + : l10n.s_nfc_oath_set_password_success, 'operationFailure': current != null - ? l10n.s_nfc_dialog_oath_change_password_failure - : l10n.s_nfc_dialog_oath_set_password_failure, + ? l10n.s_nfc_oath_change_password_failure + : l10n.s_nfc_oath_set_password_failure, }); Future unsetPassword(String current) async => invoke('unsetPassword', { 'callArgs': {'current': current}, - 'operationName': l10n.s_nfc_dialog_oath_remove_password, - 'operationProcessing': - l10n.s_nfc_dialog_oath_remove_password_processing, - 'operationSuccess': l10n.s_nfc_dialog_oath_remove_password_success, - 'operationFailure': l10n.s_nfc_dialog_oath_remove_password_failure, + 'operationName': l10n.s_nfc_oath_remove_password, + 'operationProcessing': l10n.s_nfc_oath_remove_password_processing, + 'operationSuccess': l10n.s_nfc_oath_remove_password_success, + 'operationFailure': l10n.s_nfc_oath_remove_password_failure, }); Future forgetPassword() async => invoke('forgetPassword'); @@ -376,10 +375,10 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future calculate(OathCredential credential) async => invoke('calculate', { 'callArgs': {'credentialId': credential.id}, - 'operationName': l10n.s_nfc_dialog_oath_calculate_code, - 'operationProcessing': l10n.s_nfc_dialog_oath_calculate_code_processing, - 'operationSuccess': l10n.s_nfc_dialog_oath_calculate_code_success, - 'operationFailure': l10n.s_nfc_dialog_oath_calculate_code_failure, + 'operationName': l10n.s_nfc_oath_calculate_code, + 'operationProcessing': l10n.s_nfc_oath_calculate_code_processing, + 'operationSuccess': l10n.s_nfc_oath_calculate_code_success, + 'operationFailure': l10n.s_nfc_oath_calculate_code_failure, }); Future addAccount(Uri credentialUri, @@ -389,10 +388,10 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uri': credentialUri.toString(), 'requireTouch': requireTouch }, - 'operationName': l10n.s_nfc_dialog_oath_add_account, - 'operationProcessing': l10n.s_nfc_dialog_oath_add_account_processing, - 'operationSuccess': l10n.s_nfc_dialog_oath_add_account_success, - 'operationFailure': l10n.s_nfc_dialog_oath_add_account_failure, + 'operationName': l10n.s_nfc_oath_add_account, + 'operationProcessing': l10n.s_nfc_oath_add_account_processing, + 'operationSuccess': l10n.s_nfc_oath_add_account_success, + 'operationFailure': l10n.s_nfc_oath_add_account_failure, 'showSuccess': true }); @@ -403,13 +402,10 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uris': credentialUris, 'requireTouch': touchRequired, }, - 'operationName': l10n.s_nfc_dialog_oath_add_multiple_accounts, - 'operationProcessing': - l10n.s_nfc_dialog_oath_add_multiple_accounts_processing, - 'operationSuccess': - l10n.s_nfc_dialog_oath_add_multiple_accounts_success, - 'operationFailure': - l10n.s_nfc_dialog_oath_add_multiple_accounts_failure, + 'operationName': l10n.s_nfc_oath_add_multiple_accounts, + 'operationProcessing': l10n.s_nfc_oath_add_multiple_accounts_processing, + 'operationSuccess': l10n.s_nfc_oath_add_multiple_accounts_success, + 'operationFailure': l10n.s_nfc_oath_add_multiple_accounts_failure, }); Future addAccountToAny(Uri credentialUri, @@ -419,19 +415,19 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uri': credentialUri.toString(), 'requireTouch': requireTouch }, - 'operationName': l10n.s_nfc_dialog_oath_add_account, - 'operationProcessing': l10n.s_nfc_dialog_oath_add_account_processing, - 'operationSuccess': l10n.s_nfc_dialog_oath_add_account_success, - 'operationFailure': l10n.s_nfc_dialog_oath_add_account_failure, + 'operationName': l10n.s_nfc_oath_add_account, + 'operationProcessing': l10n.s_nfc_oath_add_account_processing, + 'operationSuccess': l10n.s_nfc_oath_add_account_success, + 'operationFailure': l10n.s_nfc_oath_add_account_failure, }); Future deleteAccount(OathCredential credential) async => invoke('deleteAccount', { 'callArgs': {'credentialId': credential.id}, - 'operationName': l10n.s_nfc_dialog_oath_delete_account, - 'operationProcessing': l10n.s_nfc_dialog_oath_delete_account_processing, - 'operationSuccess': l10n.s_nfc_dialog_oath_delete_account_success, - 'operationFailure': l10n.s_nfc_dialog_oath_delete_account_failure, + 'operationName': l10n.s_nfc_oath_delete_account, + 'operationProcessing': l10n.s_nfc_oath_delete_account_processing, + 'operationSuccess': l10n.s_nfc_oath_delete_account_success, + 'operationFailure': l10n.s_nfc_oath_delete_account_failure, 'showSuccess': true }); @@ -443,9 +439,9 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'name': name, 'issuer': issuer }, - 'operationName': l10n.s_nfc_dialog_oath_rename_account, - 'operationProcessing': l10n.s_nfc_dialog_oath_rename_account_processing, - 'operationSuccess': l10n.s_nfc_dialog_oath_rename_account_success, - 'operationFailure': l10n.s_nfc_dialog_oath_rename_account_failure, + 'operationName': l10n.s_nfc_oath_rename_account, + 'operationProcessing': l10n.s_nfc_oath_rename_account_processing, + 'operationSuccess': l10n.s_nfc_oath_rename_account_success, + 'operationFailure': l10n.s_nfc_oath_rename_account_failure, }); } diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index ccc89aea..52b9ad13 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -45,8 +45,8 @@ class _DialogProvider extends Notifier { if (!explicitAction) { // setup properties for ad-hoc action ref.read(nfcActivityWidgetNotifier.notifier).setDialogProperties( - operationProcessing: l10n.s_nfc_dialog_read_key, - operationFailure: l10n.s_nfc_dialog_read_key_failure, + operationProcessing: l10n.s_nfc_read_key, + operationFailure: l10n.s_nfc_read_key_failure, showSuccess: false, ); } @@ -77,7 +77,7 @@ class _DialogProvider extends Notifier { action: NfcActivityWidgetActionSetWidgetData( child: _NfcActivityWidgetView( title: properties.operationProcessing, - subtitle: l10n.s_nfc_dialog_hold_key, + subtitle: l10n.s_nfc_hold_key, inProgress: true, )))); } @@ -93,7 +93,7 @@ class _DialogProvider extends Notifier { closeInSec: 5, child: _NfcActivityWidgetView( title: properties.operationSuccess, - subtitle: l10n.s_nfc_dialog_remove_key, + subtitle: l10n.s_nfc_remove_key, inProgress: false, ), )))); @@ -129,7 +129,7 @@ class _DialogProvider extends Notifier { notifier.update(NfcActivityWidgetCommand( action: NfcActivityWidgetActionShowWidget( child: _NfcActivityWidgetView( - title: l10n.s_nfc_dialog_tap_for( + title: l10n.s_nfc_tap_for( properties.operationName ?? '[OPERATION NAME MISSING]'), subtitle: '', inProgress: false, diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index cf2314cb..5ea92ade 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -885,95 +885,95 @@ "l_launch_app_on_usb_off": "Andere Anwendungen können den YubiKey über USB nutzen", "s_allow_screenshots": "Bildschirmfotos erlauben", - "@_ndef_oath_actions": {}, - "s_nfc_dialog_oath_reset": null, - "s_nfc_dialog_oath_reset_processing": null, - "s_nfc_dialog_oath_reset_success": null, - "s_nfc_dialog_oath_reset_failure": null, + "@_nfc_oath_actions": {}, + "s_nfc_oath_reset": null, + "s_nfc_oath_reset_processing": null, + "s_nfc_oath_reset_success": null, + "s_nfc_oath_reset_failure": null, - "s_nfc_dialog_oath_unlock": null, - "s_nfc_dialog_oath_unlock_processing": null, - "s_nfc_dialog_oath_unlock_success": null, - "s_nfc_dialog_oath_unlock_failure": null, + "s_nfc_oath_unlock": null, + "s_nfc_oath_unlock_processing": null, + "s_nfc_oath_unlock_success": null, + "s_nfc_oath_unlock_failure": null, - "s_nfc_dialog_oath_set_password": null, - "s_nfc_dialog_oath_change_password": null, - "s_nfc_dialog_oath_set_password_processing": null, - "s_nfc_dialog_oath_change_password_processing": null, - "s_nfc_dialog_oath_set_password_success": null, - "s_nfc_dialog_oath_change_password_success": null, - "s_nfc_dialog_oath_set_password_failure": null, - "s_nfc_dialog_oath_change_password_failure": null, + "s_nfc_oath_set_password": null, + "s_nfc_oath_change_password": null, + "s_nfc_oath_set_password_processing": null, + "s_nfc_oath_change_password_processing": null, + "s_nfc_oath_set_password_success": null, + "s_nfc_oath_change_password_success": null, + "s_nfc_oath_set_password_failure": null, + "s_nfc_oath_change_password_failure": null, - "s_nfc_dialog_oath_remove_password": null, - "s_nfc_dialog_oath_remove_password_processing": null, - "s_nfc_dialog_oath_remove_password_success": null, - "s_nfc_dialog_oath_remove_password_failure": null, + "s_nfc_oath_remove_password": null, + "s_nfc_oath_remove_password_processing": null, + "s_nfc_oath_remove_password_success": null, + "s_nfc_oath_remove_password_failure": null, - "s_nfc_dialog_oath_add_account": null, - "s_nfc_dialog_oath_add_account_processing": null, - "s_nfc_dialog_oath_add_account_success": null, - "s_nfc_dialog_oath_add_account_failure": null, + "s_nfc_oath_add_account": null, + "s_nfc_oath_add_account_processing": null, + "s_nfc_oath_add_account_success": null, + "s_nfc_oath_add_account_failure": null, - "s_nfc_dialog_oath_rename_account": null, - "s_nfc_dialog_oath_rename_account_processing": null, - "s_nfc_dialog_oath_rename_account_success": null, - "s_nfc_dialog_oath_rename_account_failure": null, + "s_nfc_oath_rename_account": null, + "s_nfc_oath_rename_account_processing": null, + "s_nfc_oath_rename_account_success": null, + "s_nfc_oath_rename_account_failure": null, - "s_nfc_dialog_oath_delete_account": null, - "s_nfc_dialog_oath_delete_account_processing": null, - "s_nfc_dialog_oath_delete_account_success": null, - "s_nfc_dialog_oath_delete_account_failure": null, + "s_nfc_oath_delete_account": null, + "s_nfc_oath_delete_account_processing": null, + "s_nfc_oath_delete_account_success": null, + "s_nfc_oath_delete_account_failure": null, - "s_nfc_dialog_oath_calculate_code": null, - "s_nfc_dialog_oath_calculate_code_processing": null, - "s_nfc_dialog_oath_calculate_code_success": null, - "s_nfc_dialog_oath_calculate_code_failure": null, + "s_nfc_oath_calculate_code": null, + "s_nfc_oath_calculate_code_processing": null, + "s_nfc_oath_calculate_code_success": null, + "s_nfc_oath_calculate_code_failure": null, - "s_nfc_dialog_oath_add_multiple_accounts": null, - "s_nfc_dialog_oath_add_multiple_accounts_processing": null, - "s_nfc_dialog_oath_add_multiple_accounts_success": null, - "s_nfc_dialog_oath_add_multiple_accounts_failure": null, + "s_nfc_oath_add_multiple_accounts": null, + "s_nfc_oath_add_multiple_accounts_processing": null, + "s_nfc_oath_add_multiple_accounts_success": null, + "s_nfc_oath_add_multiple_accounts_failure": null, - "@_ndef_fido_actions": {}, - "s_nfc_dialog_fido_reset": null, - "s_nfc_dialog_fido_reset_processing": null, - "s_nfc_dialog_fido_reset_success": null, - "s_nfc_dialog_fido_reset_failure": null, + "@_nfc_fido_actions": {}, + "s_nfc_fido_reset": null, + "s_nfc_fido_reset_processing": null, + "s_nfc_fido_reset_success": null, + "s_nfc_fido_reset_failure": null, - "s_nfc_dialog_fido_unlock": null, - "s_nfc_dialog_fido_unlock_processing": null, - "s_nfc_dialog_fido_unlock_success": null, - "s_nfc_dialog_fido_unlock_failure": null, + "s_nfc_fido_unlock": null, + "s_nfc_fido_unlock_processing": null, + "s_nfc_fido_unlock_success": null, + "s_nfc_fido_unlock_failure": null, - "s_nfc_dialog_fido_set_pin": null, - "s_nfc_dialog_fido_set_pin_processing": null, - "s_nfc_dialog_fido_set_pin_success": null, - "s_nfc_dialog_fido_set_pin_failure": null, + "s_nfc_fido_set_pin": null, + "s_nfc_fido_set_pin_processing": null, + "s_nfc_fido_set_pin_success": null, + "s_nfc_fido_set_pin_failure": null, - "s_nfc_dialog_fido_change_pin": null, - "s_nfc_dialog_fido_change_pin_processing": null, - "s_nfc_dialog_fido_change_pin_success": null, - "s_nfc_dialog_fido_change_pin_failure": null, + "s_nfc_fido_change_pin": null, + "s_nfc_fido_change_pin_processing": null, + "s_nfc_fido_change_pin_success": null, + "s_nfc_fido_change_pin_failure": null, - "s_nfc_dialog_fido_delete_credential": null, - "s_nfc_dialog_fido_delete_credential_processing": null, - "s_nfc_dialog_fido_delete_credential_success": null, - "s_nfc_dialog_fido_delete_credential_failure": null, + "s_nfc_fido_delete_passkey": null, + "s_nfc_fido_delete_passkey_processing": null, + "s_nfc_fido_delete_passkey_success": null, + "s_nfc_fido_delete_passkey_failure": null, - "@_ndef_operations": {}, - "s_nfc_dialog_tap_for": null, - "@s_nfc_dialog_tap_for": { + "@_nfc_actions": {}, + "s_nfc_tap_for": null, + "@s_nfc_tap_for": { "placeholders": { "operation": {} } }, - "s_nfc_dialog_read_key": null, - "s_nfc_dialog_read_key_failure": null, + "s_nfc_read_key": null, + "s_nfc_read_key_failure": null, - "s_nfc_dialog_hold_key": null, - "s_nfc_dialog_remove_key": null, + "s_nfc_hold_key": null, + "s_nfc_remove_key": null, "@_ndef": {}, "p_ndef_set_otp": "OTP-Code wurde erfolgreich von Ihrem YubiKey in die Zwischenablage kopiert.", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index a3e534d7..70f7966f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -885,95 +885,95 @@ "l_launch_app_on_usb_off": "Other apps can use the YubiKey over USB", "s_allow_screenshots": "Allow screenshots", - "@_ndef_oath_actions": {}, - "s_nfc_dialog_oath_reset": "reset Accounts", - "s_nfc_dialog_oath_reset_processing": "Reset in progress", - "s_nfc_dialog_oath_reset_success": "Accounts reset", - "s_nfc_dialog_oath_reset_failure": "Failed to reset accounts", + "@_nfc_oath_actions": {}, + "s_nfc_oath_reset": "reset Accounts", + "s_nfc_oath_reset_processing": "Reset in progress", + "s_nfc_oath_reset_success": "Accounts reset", + "s_nfc_oath_reset_failure": "Failed to reset accounts", - "s_nfc_dialog_oath_unlock": "unlock", - "s_nfc_dialog_oath_unlock_processing": "Unlocking", - "s_nfc_dialog_oath_unlock_success": "Accounts unlocked", - "s_nfc_dialog_oath_unlock_failure": "Failed to unlock", + "s_nfc_oath_unlock": "unlock", + "s_nfc_oath_unlock_processing": "Unlocking", + "s_nfc_oath_unlock_success": "Accounts unlocked", + "s_nfc_oath_unlock_failure": "Failed to unlock", - "s_nfc_dialog_oath_set_password": "set password", - "s_nfc_dialog_oath_change_password": "change password", - "s_nfc_dialog_oath_set_password_processing": "Setting password", - "s_nfc_dialog_oath_change_password_processing": "Changing password", - "s_nfc_dialog_oath_set_password_success": "Password set", - "s_nfc_dialog_oath_change_password_success": "Password changed", - "s_nfc_dialog_oath_set_password_failure": "Failed to set password", - "s_nfc_dialog_oath_change_password_failure": "Failed to change password", + "s_nfc_oath_set_password": "set password", + "s_nfc_oath_change_password": "change password", + "s_nfc_oath_set_password_processing": "Setting password", + "s_nfc_oath_change_password_processing": "Changing password", + "s_nfc_oath_set_password_success": "Password set", + "s_nfc_oath_change_password_success": "Password changed", + "s_nfc_oath_set_password_failure": "Failed to set password", + "s_nfc_oath_change_password_failure": "Failed to change password", - "s_nfc_dialog_oath_remove_password": "remove password", - "s_nfc_dialog_oath_remove_password_processing": "Removing password", - "s_nfc_dialog_oath_remove_password_success": "Password removed", - "s_nfc_dialog_oath_remove_password_failure": "Failed to remove password", + "s_nfc_oath_remove_password": "remove password", + "s_nfc_oath_remove_password_processing": "Removing password", + "s_nfc_oath_remove_password_success": "Password removed", + "s_nfc_oath_remove_password_failure": "Failed to remove password", - "s_nfc_dialog_oath_add_account": "add account", - "s_nfc_dialog_oath_add_account_processing": "Adding account", - "s_nfc_dialog_oath_add_account_success": "Account added", - "s_nfc_dialog_oath_add_account_failure": "Failed to add account", + "s_nfc_oath_add_account": "add account", + "s_nfc_oath_add_account_processing": "Adding account", + "s_nfc_oath_add_account_success": "Account added", + "s_nfc_oath_add_account_failure": "Failed to add account", - "s_nfc_dialog_oath_rename_account": "rename account", - "s_nfc_dialog_oath_rename_account_processing": "Renaming account", - "s_nfc_dialog_oath_rename_account_success": "Account renamed", - "s_nfc_dialog_oath_rename_account_failure": "Failed to rename account", + "s_nfc_oath_rename_account": "rename account", + "s_nfc_oath_rename_account_processing": "Renaming account", + "s_nfc_oath_rename_account_success": "Account renamed", + "s_nfc_oath_rename_account_failure": "Failed to rename account", - "s_nfc_dialog_oath_delete_account": "delete account", - "s_nfc_dialog_oath_delete_account_processing": "Deleting account", - "s_nfc_dialog_oath_delete_account_success": "Account deleted", - "s_nfc_dialog_oath_delete_account_failure": "Failed to delete account", + "s_nfc_oath_delete_account": "delete account", + "s_nfc_oath_delete_account_processing": "Deleting account", + "s_nfc_oath_delete_account_success": "Account deleted", + "s_nfc_oath_delete_account_failure": "Failed to delete account", - "s_nfc_dialog_oath_calculate_code": "calculate code", - "s_nfc_dialog_oath_calculate_code_processing": "Calculating", - "s_nfc_dialog_oath_calculate_code_success": "Code calculated", - "s_nfc_dialog_oath_calculate_code_failure": "Failed to calculate code", + "s_nfc_oath_calculate_code": "calculate code", + "s_nfc_oath_calculate_code_processing": "Calculating", + "s_nfc_oath_calculate_code_success": "Code calculated", + "s_nfc_oath_calculate_code_failure": "Failed to calculate code", - "s_nfc_dialog_oath_add_multiple_accounts": "add selected accounts", - "s_nfc_dialog_oath_add_multiple_accounts_processing": "Adding accounts", - "s_nfc_dialog_oath_add_multiple_accounts_success": "Accounts added", - "s_nfc_dialog_oath_add_multiple_accounts_failure": "Failed to add accounts", + "s_nfc_oath_add_multiple_accounts": "add selected accounts", + "s_nfc_oath_add_multiple_accounts_processing": "Adding accounts", + "s_nfc_oath_add_multiple_accounts_success": "Accounts added", + "s_nfc_oath_add_multiple_accounts_failure": "Failed to add accounts", - "@_ndef_fido_actions": {}, - "s_nfc_dialog_fido_reset": "reset FIDO application", - "s_nfc_dialog_fido_reset_processing": "Resetting FIDO", - "s_nfc_dialog_fido_reset_success": "FIDO reset", - "s_nfc_dialog_fido_reset_failure": "FIDO reset failed", + "@_nfc_fido_actions": {}, + "s_nfc_fido_reset": "reset FIDO application", + "s_nfc_fido_reset_processing": "Resetting FIDO", + "s_nfc_fido_reset_success": "FIDO reset", + "s_nfc_fido_reset_failure": "FIDO reset failed", - "s_nfc_dialog_fido_unlock": "unlock", - "s_nfc_dialog_fido_unlock_processing": "Unlocking", - "s_nfc_dialog_fido_unlock_success": "unlocked", - "s_nfc_dialog_fido_unlock_failure": "Failed to unlock", + "s_nfc_fido_unlock": "unlock", + "s_nfc_fido_unlock_processing": "Unlocking", + "s_nfc_fido_unlock_success": "unlocked", + "s_nfc_fido_unlock_failure": "Failed to unlock", - "s_nfc_dialog_fido_set_pin": "set PIN", - "s_nfc_dialog_fido_set_pin_processing": "Setting PIN", - "s_nfc_dialog_fido_set_pin_success": "PIN set", - "s_nfc_dialog_fido_set_pin_failure": "Failure setting PIN", + "s_nfc_fido_set_pin": "set PIN", + "s_nfc_fido_set_pin_processing": "Setting PIN", + "s_nfc_fido_set_pin_success": "PIN set", + "s_nfc_fido_set_pin_failure": "Failure setting PIN", - "s_nfc_dialog_fido_change_pin": "change PIN", - "s_nfc_dialog_fido_change_pin_processing": "Changing PIN", - "s_nfc_dialog_fido_change_pin_success": "PIN changed", - "s_nfc_dialog_fido_change_pin_failure": "Failure changing PIN", + "s_nfc_fido_change_pin": "change PIN", + "s_nfc_fido_change_pin_processing": "Changing PIN", + "s_nfc_fido_change_pin_success": "PIN changed", + "s_nfc_fido_change_pin_failure": "Failure changing PIN", - "s_nfc_dialog_fido_delete_credential": "delete passkey", - "s_nfc_dialog_fido_delete_credential_processing": "Deleting passkey", - "s_nfc_dialog_fido_delete_credential_success": "Passkey deleted", - "s_nfc_dialog_fido_delete_credential_failure": "Failed to delete passkey", + "s_nfc_fido_delete_passkey": "delete passkey", + "s_nfc_fido_delete_passkey_processing": "Deleting passkey", + "s_nfc_fido_delete_passkey_success": "Passkey deleted", + "s_nfc_fido_delete_passkey_failure": "Failed to delete passkey", - "@_ndef_operations": {}, - "s_nfc_dialog_tap_for": "Tap YubiKey to {operation}", - "@s_nfc_dialog_tap_for": { + "@_nfc_actions": {}, + "s_nfc_tap_for": "Tap YubiKey to {operation}", + "@s_nfc_tap_for": { "placeholders": { "operation": {} } }, - "s_nfc_dialog_read_key": "Reading YubiKey", - "s_nfc_dialog_read_key_failure": "Failed to read YubiKey, try again", + "s_nfc_read_key": "Reading YubiKey", + "s_nfc_read_key_failure": "Failed to read YubiKey, try again", - "s_nfc_dialog_hold_key": "Hold YubiKey", - "s_nfc_dialog_remove_key": "You can remove YubiKey", + "s_nfc_hold_key": "Hold YubiKey", + "s_nfc_remove_key": "You can remove YubiKey", "@_ndef": {}, "p_ndef_set_otp": "Successfully copied OTP code from YubiKey to clipboard.", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 5089233d..57d8662a 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -885,95 +885,95 @@ "l_launch_app_on_usb_off": "D'autres applications peuvent utiliser la YubiKey en USB", "s_allow_screenshots": "Autoriser captures d'écran", - "@_ndef_oath_actions": {}, - "s_nfc_dialog_oath_reset": null, - "s_nfc_dialog_oath_reset_processing": null, - "s_nfc_dialog_oath_reset_success": null, - "s_nfc_dialog_oath_reset_failure": null, + "@_nfc_oath_actions": {}, + "s_nfc_oath_reset": null, + "s_nfc_oath_reset_processing": null, + "s_nfc_oath_reset_success": null, + "s_nfc_oath_reset_failure": null, - "s_nfc_dialog_oath_unlock": null, - "s_nfc_dialog_oath_unlock_processing": null, - "s_nfc_dialog_oath_unlock_success": null, - "s_nfc_dialog_oath_unlock_failure": null, + "s_nfc_oath_unlock": null, + "s_nfc_oath_unlock_processing": null, + "s_nfc_oath_unlock_success": null, + "s_nfc_oath_unlock_failure": null, - "s_nfc_dialog_oath_set_password": null, - "s_nfc_dialog_oath_change_password": null, - "s_nfc_dialog_oath_set_password_processing": null, - "s_nfc_dialog_oath_change_password_processing": null, - "s_nfc_dialog_oath_set_password_success": null, - "s_nfc_dialog_oath_change_password_success": null, - "s_nfc_dialog_oath_set_password_failure": null, - "s_nfc_dialog_oath_change_password_failure": null, + "s_nfc_oath_set_password": null, + "s_nfc_oath_change_password": null, + "s_nfc_oath_set_password_processing": null, + "s_nfc_oath_change_password_processing": null, + "s_nfc_oath_set_password_success": null, + "s_nfc_oath_change_password_success": null, + "s_nfc_oath_set_password_failure": null, + "s_nfc_oath_change_password_failure": null, - "s_nfc_dialog_oath_remove_password": null, - "s_nfc_dialog_oath_remove_password_processing": null, - "s_nfc_dialog_oath_remove_password_success": null, - "s_nfc_dialog_oath_remove_password_failure": null, + "s_nfc_oath_remove_password": null, + "s_nfc_oath_remove_password_processing": null, + "s_nfc_oath_remove_password_success": null, + "s_nfc_oath_remove_password_failure": null, - "s_nfc_dialog_oath_add_account": null, - "s_nfc_dialog_oath_add_account_processing": null, - "s_nfc_dialog_oath_add_account_success": null, - "s_nfc_dialog_oath_add_account_failure": null, + "s_nfc_oath_add_account": null, + "s_nfc_oath_add_account_processing": null, + "s_nfc_oath_add_account_success": null, + "s_nfc_oath_add_account_failure": null, - "s_nfc_dialog_oath_rename_account": null, - "s_nfc_dialog_oath_rename_account_processing": null, - "s_nfc_dialog_oath_rename_account_success": null, - "s_nfc_dialog_oath_rename_account_failure": null, + "s_nfc_oath_rename_account": null, + "s_nfc_oath_rename_account_processing": null, + "s_nfc_oath_rename_account_success": null, + "s_nfc_oath_rename_account_failure": null, - "s_nfc_dialog_oath_delete_account": null, - "s_nfc_dialog_oath_delete_account_processing": null, - "s_nfc_dialog_oath_delete_account_success": null, - "s_nfc_dialog_oath_delete_account_failure": null, + "s_nfc_oath_delete_account": null, + "s_nfc_oath_delete_account_processing": null, + "s_nfc_oath_delete_account_success": null, + "s_nfc_oath_delete_account_failure": null, - "s_nfc_dialog_oath_calculate_code": null, - "s_nfc_dialog_oath_calculate_code_processing": null, - "s_nfc_dialog_oath_calculate_code_success": null, - "s_nfc_dialog_oath_calculate_code_failure": null, + "s_nfc_oath_calculate_code": null, + "s_nfc_oath_calculate_code_processing": null, + "s_nfc_oath_calculate_code_success": null, + "s_nfc_oath_calculate_code_failure": null, - "s_nfc_dialog_oath_add_multiple_accounts": null, - "s_nfc_dialog_oath_add_multiple_accounts_processing": null, - "s_nfc_dialog_oath_add_multiple_accounts_success": null, - "s_nfc_dialog_oath_add_multiple_accounts_failure": null, + "s_nfc_oath_add_multiple_accounts": null, + "s_nfc_oath_add_multiple_accounts_processing": null, + "s_nfc_oath_add_multiple_accounts_success": null, + "s_nfc_oath_add_multiple_accounts_failure": null, - "@_ndef_fido_actions": {}, - "s_nfc_dialog_fido_reset": null, - "s_nfc_dialog_fido_reset_processing": null, - "s_nfc_dialog_fido_reset_success": null, - "s_nfc_dialog_fido_reset_failure": null, + "@_nfc_fido_actions": {}, + "s_nfc_fido_reset": null, + "s_nfc_fido_reset_processing": null, + "s_nfc_fido_reset_success": null, + "s_nfc_fido_reset_failure": null, - "s_nfc_dialog_fido_unlock": null, - "s_nfc_dialog_fido_unlock_processing": null, - "s_nfc_dialog_fido_unlock_success": null, - "s_nfc_dialog_fido_unlock_failure": null, + "s_nfc_fido_unlock": null, + "s_nfc_fido_unlock_processing": null, + "s_nfc_fido_unlock_success": null, + "s_nfc_fido_unlock_failure": null, - "s_nfc_dialog_fido_set_pin": null, - "s_nfc_dialog_fido_set_pin_processing": null, - "s_nfc_dialog_fido_set_pin_success": null, - "s_nfc_dialog_fido_set_pin_failure": null, + "s_nfc_fido_set_pin": null, + "s_nfc_fido_set_pin_processing": null, + "s_nfc_fido_set_pin_success": null, + "s_nfc_fido_set_pin_failure": null, - "s_nfc_dialog_fido_change_pin": null, - "s_nfc_dialog_fido_change_pin_processing": null, - "s_nfc_dialog_fido_change_pin_success": null, - "s_nfc_dialog_fido_change_pin_failure": null, + "s_nfc_fido_change_pin": null, + "s_nfc_fido_change_pin_processing": null, + "s_nfc_fido_change_pin_success": null, + "s_nfc_fido_change_pin_failure": null, - "s_nfc_dialog_fido_delete_credential": null, - "s_nfc_dialog_fido_delete_credential_processing": null, - "s_nfc_dialog_fido_delete_credential_success": null, - "s_nfc_dialog_fido_delete_credential_failure": null, + "s_nfc_fido_delete_passkey": null, + "s_nfc_fido_delete_passkey_processing": null, + "s_nfc_fido_delete_passkey_success": null, + "s_nfc_fido_delete_passkey_failure": null, - "@_ndef_operations": {}, - "s_nfc_dialog_tap_for": null, - "@s_nfc_dialog_tap_for": { + "@_nfc_actions": {}, + "s_nfc_tap_for": null, + "@s_nfc_tap_for": { "placeholders": { "operation": {} } }, - "s_nfc_dialog_read_key": null, - "s_nfc_dialog_read_key_failure": null, + "s_nfc_read_key": null, + "s_nfc_read_key_failure": null, - "s_nfc_dialog_hold_key": null, - "s_nfc_dialog_remove_key": null, + "s_nfc_hold_key": null, + "s_nfc_remove_key": null, "@_ndef": {}, "p_ndef_set_otp": "Code OTP copié de la YubiKey dans le presse-papiers.", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 2675abf7..6c3a180e 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -885,95 +885,95 @@ "l_launch_app_on_usb_off": "他のアプリがUSB経由でYubiKeyを使用できます", "s_allow_screenshots": "スクリーンショットを許可", - "@_ndef_oath_actions": {}, - "s_nfc_dialog_oath_reset": "アクション:OATHアプレットをリセット", - "s_nfc_dialog_oath_reset_processing": null, - "s_nfc_dialog_oath_reset_success": null, - "s_nfc_dialog_oath_reset_failure": null, + "@_nfc_oath_actions": {}, + "s_nfc_oath_reset": "アクション:OATHアプレットをリセット", + "s_nfc_oath_reset_processing": null, + "s_nfc_oath_reset_success": null, + "s_nfc_oath_reset_failure": null, - "s_nfc_dialog_oath_unlock": "アクション:OATHアプレットをロック解除", - "s_nfc_dialog_oath_unlock_processing": null, - "s_nfc_dialog_oath_unlock_success": null, - "s_nfc_dialog_oath_unlock_failure": null, + "s_nfc_oath_unlock": "アクション:OATHアプレットをロック解除", + "s_nfc_oath_unlock_processing": null, + "s_nfc_oath_unlock_success": null, + "s_nfc_oath_unlock_failure": null, - "s_nfc_dialog_oath_set_password": "アクション:OATHパスワードを設定", - "s_nfc_dialog_oath_change_password": null, - "s_nfc_dialog_oath_set_password_processing": null, - "s_nfc_dialog_oath_change_password_processing": null, - "s_nfc_dialog_oath_set_password_success": null, - "s_nfc_dialog_oath_change_password_success": null, - "s_nfc_dialog_oath_set_password_failure": null, - "s_nfc_dialog_oath_change_password_failure": null, + "s_nfc_oath_set_password": "アクション:OATHパスワードを設定", + "s_nfc_oath_change_password": null, + "s_nfc_oath_set_password_processing": null, + "s_nfc_oath_change_password_processing": null, + "s_nfc_oath_set_password_success": null, + "s_nfc_oath_change_password_success": null, + "s_nfc_oath_set_password_failure": null, + "s_nfc_oath_change_password_failure": null, - "s_nfc_dialog_oath_remove_password": null, - "s_nfc_dialog_oath_remove_password_processing": null, - "s_nfc_dialog_oath_remove_password_success": null, - "s_nfc_dialog_oath_remove_password_failure": null, + "s_nfc_oath_remove_password": null, + "s_nfc_oath_remove_password_processing": null, + "s_nfc_oath_remove_password_success": null, + "s_nfc_oath_remove_password_failure": null, - "s_nfc_dialog_oath_add_account": "アクション:新しいアカウントを追加", - "s_nfc_dialog_oath_add_account_processing": null, - "s_nfc_dialog_oath_add_account_success": null, - "s_nfc_dialog_oath_add_account_failure": null, + "s_nfc_oath_add_account": "アクション:新しいアカウントを追加", + "s_nfc_oath_add_account_processing": null, + "s_nfc_oath_add_account_success": null, + "s_nfc_oath_add_account_failure": null, - "s_nfc_dialog_oath_rename_account": "アクション:アカウント名を変更", - "s_nfc_dialog_oath_rename_account_processing": null, - "s_nfc_dialog_oath_rename_account_success": null, - "s_nfc_dialog_oath_rename_account_failure": null, + "s_nfc_oath_rename_account": "アクション:アカウント名を変更", + "s_nfc_oath_rename_account_processing": null, + "s_nfc_oath_rename_account_success": null, + "s_nfc_oath_rename_account_failure": null, - "s_nfc_dialog_oath_delete_account": "アクション:アカウントを削除", - "s_nfc_dialog_oath_delete_account_processing": null, - "s_nfc_dialog_oath_delete_account_success": null, - "s_nfc_dialog_oath_delete_account_failure": null, + "s_nfc_oath_delete_account": "アクション:アカウントを削除", + "s_nfc_oath_delete_account_processing": null, + "s_nfc_oath_delete_account_success": null, + "s_nfc_oath_delete_account_failure": null, - "s_nfc_dialog_oath_calculate_code": "アクション:OATHコードを計算", - "s_nfc_dialog_oath_calculate_code_processing": null, - "s_nfc_dialog_oath_calculate_code_success": null, - "s_nfc_dialog_oath_calculate_code_failure": null, + "s_nfc_oath_calculate_code": "アクション:OATHコードを計算", + "s_nfc_oath_calculate_code_processing": null, + "s_nfc_oath_calculate_code_success": null, + "s_nfc_oath_calculate_code_failure": null, - "s_nfc_dialog_oath_add_multiple_accounts": "アクション:複数アカウントを追加", - "s_nfc_dialog_oath_add_multiple_accounts_processing": null, - "s_nfc_dialog_oath_add_multiple_accounts_success": null, - "s_nfc_dialog_oath_add_multiple_accounts_failure": null, + "s_nfc_oath_add_multiple_accounts": "アクション:複数アカウントを追加", + "s_nfc_oath_add_multiple_accounts_processing": null, + "s_nfc_oath_add_multiple_accounts_success": null, + "s_nfc_oath_add_multiple_accounts_failure": null, - "@_ndef_fido_actions": {}, - "s_nfc_dialog_fido_reset": "アクション: FIDOアプリケーションをリセット", - "s_nfc_dialog_fido_reset_processing": null, - "s_nfc_dialog_fido_reset_success": null, - "s_nfc_dialog_fido_reset_failure": null, + "@_nfc_fido_actions": {}, + "s_nfc_fido_reset": "アクション: FIDOアプリケーションをリセット", + "s_nfc_fido_reset_processing": null, + "s_nfc_fido_reset_success": null, + "s_nfc_fido_reset_failure": null, - "s_nfc_dialog_fido_unlock": "アクション:FIDOアプリケーションのロックを解除する", - "s_nfc_dialog_fido_unlock_processing": null, - "s_nfc_dialog_fido_unlock_success": null, - "s_nfc_dialog_fido_unlock_failure": null, + "s_nfc_fido_unlock": "アクション:FIDOアプリケーションのロックを解除する", + "s_nfc_fido_unlock_processing": null, + "s_nfc_fido_unlock_success": null, + "s_nfc_fido_unlock_failure": null, - "s_nfc_dialog_fido_set_pin": null, - "s_nfc_dialog_fido_set_pin_processing": null, - "s_nfc_dialog_fido_set_pin_success": null, - "s_nfc_dialog_fido_set_pin_failure": null, + "s_nfc_fido_set_pin": null, + "s_nfc_fido_set_pin_processing": null, + "s_nfc_fido_set_pin_success": null, + "s_nfc_fido_set_pin_failure": null, - "s_nfc_dialog_fido_change_pin": null, - "s_nfc_dialog_fido_change_pin_processing": null, - "s_nfc_dialog_fido_change_pin_success": null, - "s_nfc_dialog_fido_change_pin_failure": null, + "s_nfc_fido_change_pin": null, + "s_nfc_fido_change_pin_processing": null, + "s_nfc_fido_change_pin_success": null, + "s_nfc_fido_change_pin_failure": null, - "s_nfc_dialog_fido_delete_credential": "アクション: パスキーを削除", - "s_nfc_dialog_fido_delete_credential_processing": null, - "s_nfc_dialog_fido_delete_credential_success": null, - "s_nfc_dialog_fido_delete_credential_failure": null, + "s_nfc_fido_delete_passkey": null, + "s_nfc_fido_delete_passkey_processing": null, + "s_nfc_fido_delete_passkey_success": null, + "s_nfc_fido_delete_passkey_failure": null, - "@_ndef_operations": {}, - "s_nfc_dialog_tap_for": null, - "@s_nfc_dialog_tap_for": { + "@_nfc_actions": {}, + "s_nfc_tap_for": null, + "@s_nfc_tap_for": { "placeholders": { "operation": {} } }, - "s_nfc_dialog_read_key": null, - "s_nfc_dialog_read_key_failure": null, + "s_nfc_read_key": null, + "s_nfc_read_key_failure": null, - "s_nfc_dialog_hold_key": null, - "s_nfc_dialog_remove_key": null, + "s_nfc_hold_key": null, + "s_nfc_remove_key": null, "@_ndef": {}, "p_ndef_set_otp": "OTPコードがYubiKeyからクリップボードに正常にコピーされました。", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index ad1fa188..1d3f2477 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -885,95 +885,95 @@ "l_launch_app_on_usb_off": "Inne aplikacje mogą korzystać z YubiKey przez USB", "s_allow_screenshots": "Zezwalaj na zrzuty ekranu", - "@_ndef_oath_actions": {}, - "s_nfc_dialog_oath_reset": null, - "s_nfc_dialog_oath_reset_processing": null, - "s_nfc_dialog_oath_reset_success": null, - "s_nfc_dialog_oath_reset_failure": null, + "@_nfc_oath_actions": {}, + "s_nfc_oath_reset": null, + "s_nfc_oath_reset_processing": null, + "s_nfc_oath_reset_success": null, + "s_nfc_oath_reset_failure": null, - "s_nfc_dialog_oath_unlock": null, - "s_nfc_dialog_oath_unlock_processing": null, - "s_nfc_dialog_oath_unlock_success": null, - "s_nfc_dialog_oath_unlock_failure": null, + "s_nfc_oath_unlock": null, + "s_nfc_oath_unlock_processing": null, + "s_nfc_oath_unlock_success": null, + "s_nfc_oath_unlock_failure": null, - "s_nfc_dialog_oath_set_password": null, - "s_nfc_dialog_oath_change_password": null, - "s_nfc_dialog_oath_set_password_processing": null, - "s_nfc_dialog_oath_change_password_processing": null, - "s_nfc_dialog_oath_set_password_success": null, - "s_nfc_dialog_oath_change_password_success": null, - "s_nfc_dialog_oath_set_password_failure": null, - "s_nfc_dialog_oath_change_password_failure": null, + "s_nfc_oath_set_password": null, + "s_nfc_oath_change_password": null, + "s_nfc_oath_set_password_processing": null, + "s_nfc_oath_change_password_processing": null, + "s_nfc_oath_set_password_success": null, + "s_nfc_oath_change_password_success": null, + "s_nfc_oath_set_password_failure": null, + "s_nfc_oath_change_password_failure": null, - "s_nfc_dialog_oath_remove_password": null, - "s_nfc_dialog_oath_remove_password_processing": null, - "s_nfc_dialog_oath_remove_password_success": null, - "s_nfc_dialog_oath_remove_password_failure": null, + "s_nfc_oath_remove_password": null, + "s_nfc_oath_remove_password_processing": null, + "s_nfc_oath_remove_password_success": null, + "s_nfc_oath_remove_password_failure": null, - "s_nfc_dialog_oath_add_account": null, - "s_nfc_dialog_oath_add_account_processing": null, - "s_nfc_dialog_oath_add_account_success": null, - "s_nfc_dialog_oath_add_account_failure": null, + "s_nfc_oath_add_account": null, + "s_nfc_oath_add_account_processing": null, + "s_nfc_oath_add_account_success": null, + "s_nfc_oath_add_account_failure": null, - "s_nfc_dialog_oath_rename_account": null, - "s_nfc_dialog_oath_rename_account_processing": null, - "s_nfc_dialog_oath_rename_account_success": null, - "s_nfc_dialog_oath_rename_account_failure": null, + "s_nfc_oath_rename_account": null, + "s_nfc_oath_rename_account_processing": null, + "s_nfc_oath_rename_account_success": null, + "s_nfc_oath_rename_account_failure": null, - "s_nfc_dialog_oath_delete_account": null, - "s_nfc_dialog_oath_delete_account_processing": null, - "s_nfc_dialog_oath_delete_account_success": null, - "s_nfc_dialog_oath_delete_account_failure": null, + "s_nfc_oath_delete_account": null, + "s_nfc_oath_delete_account_processing": null, + "s_nfc_oath_delete_account_success": null, + "s_nfc_oath_delete_account_failure": null, - "s_nfc_dialog_oath_calculate_code": null, - "s_nfc_dialog_oath_calculate_code_processing": null, - "s_nfc_dialog_oath_calculate_code_success": null, - "s_nfc_dialog_oath_calculate_code_failure": null, + "s_nfc_oath_calculate_code": null, + "s_nfc_oath_calculate_code_processing": null, + "s_nfc_oath_calculate_code_success": null, + "s_nfc_oath_calculate_code_failure": null, - "s_nfc_dialog_oath_add_multiple_accounts": null, - "s_nfc_dialog_oath_add_multiple_accounts_processing": null, - "s_nfc_dialog_oath_add_multiple_accounts_success": null, - "s_nfc_dialog_oath_add_multiple_accounts_failure": null, + "s_nfc_oath_add_multiple_accounts": null, + "s_nfc_oath_add_multiple_accounts_processing": null, + "s_nfc_oath_add_multiple_accounts_success": null, + "s_nfc_oath_add_multiple_accounts_failure": null, - "@_ndef_fido_actions": {}, - "s_nfc_dialog_fido_reset": null, - "s_nfc_dialog_fido_reset_processing": null, - "s_nfc_dialog_fido_reset_success": null, - "s_nfc_dialog_fido_reset_failure": null, + "@_nfc_fido_actions": {}, + "s_nfc_fido_reset": null, + "s_nfc_fido_reset_processing": null, + "s_nfc_fido_reset_success": null, + "s_nfc_fido_reset_failure": null, - "s_nfc_dialog_fido_unlock": null, - "s_nfc_dialog_fido_unlock_processing": null, - "s_nfc_dialog_fido_unlock_success": null, - "s_nfc_dialog_fido_unlock_failure": null, + "s_nfc_fido_unlock": null, + "s_nfc_fido_unlock_processing": null, + "s_nfc_fido_unlock_success": null, + "s_nfc_fido_unlock_failure": null, - "s_nfc_dialog_fido_set_pin": null, - "s_nfc_dialog_fido_set_pin_processing": null, - "s_nfc_dialog_fido_set_pin_success": null, - "s_nfc_dialog_fido_set_pin_failure": null, + "s_nfc_fido_set_pin": null, + "s_nfc_fido_set_pin_processing": null, + "s_nfc_fido_set_pin_success": null, + "s_nfc_fido_set_pin_failure": null, - "s_nfc_dialog_fido_change_pin": null, - "s_nfc_dialog_fido_change_pin_processing": null, - "s_nfc_dialog_fido_change_pin_success": null, - "s_nfc_dialog_fido_change_pin_failure": null, + "s_nfc_fido_change_pin": null, + "s_nfc_fido_change_pin_processing": null, + "s_nfc_fido_change_pin_success": null, + "s_nfc_fido_change_pin_failure": null, - "s_nfc_dialog_fido_delete_credential": null, - "s_nfc_dialog_fido_delete_credential_processing": null, - "s_nfc_dialog_fido_delete_credential_success": null, - "s_nfc_dialog_fido_delete_credential_failure": null, + "s_nfc_fido_delete_passkey": null, + "s_nfc_fido_delete_passkey_processing": null, + "s_nfc_fido_delete_passkey_success": null, + "s_nfc_fido_delete_passkey_failure": null, - "@_ndef_operations": {}, - "s_nfc_dialog_tap_for": null, - "@s_nfc_dialog_tap_for": { + "@_nfc_actions": {}, + "s_nfc_tap_for": null, + "@s_nfc_tap_for": { "placeholders": { "operation": {} } }, - "s_nfc_dialog_read_key": null, - "s_nfc_dialog_read_key_failure": null, + "s_nfc_read_key": null, + "s_nfc_read_key_failure": null, - "s_nfc_dialog_hold_key": null, - "s_nfc_dialog_remove_key": null, + "s_nfc_hold_key": null, + "s_nfc_remove_key": null, "@_ndef": {}, "p_ndef_set_otp": "OTP zostało skopiowane do schowka.", From 3ef1276108dd9a90fef77eab2c27d5d27d6edc80 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 29 Aug 2024 09:45:48 +0200 Subject: [PATCH 08/50] fix strings errors --- check_strings.py | 2 +- lib/android/fido/state.dart | 20 +++++------ lib/android/oath/state.dart | 40 +++++++++++----------- lib/android/tap_request_dialog.dart | 2 +- lib/l10n/app_de.arb | 53 ++++++++++++----------------- lib/l10n/app_en.arb | 53 ++++++++++++----------------- lib/l10n/app_fr.arb | 53 ++++++++++++----------------- lib/l10n/app_ja.arb | 53 ++++++++++++----------------- lib/l10n/app_pl.arb | 53 ++++++++++++----------------- 9 files changed, 137 insertions(+), 192 deletions(-) diff --git a/check_strings.py b/check_strings.py index cb06fd9a..2e9f0f0d 100755 --- a/check_strings.py +++ b/check_strings.py @@ -68,7 +68,7 @@ def check_misc(k, v): errs = [] if "..." in v: errs.append("'...' should be replaced with '\\u2026'") - if v[0].upper() != v[0]: + if v[0].upper() != v[0] and not k.startswith("c_"): errs.append("Starts with lowercase letter") return errs diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index a4de36fc..fb9b9178 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -384,9 +384,9 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { 'rpId': credential.rpId, 'credentialId': credential.credentialId }, - 'operationName': l10n.s_nfc_fido_delete_passkey, + 'operationName': l10n.c_nfc_fido_delete_passkey, 'operationProcessing': l10n.s_nfc_fido_delete_passkey_processing, - 'operationSuccess': l10n.s_nfc_fido_delete_passkey_success, + 'operationSuccess': l10n.s_passkey_deleted, 'operationFailure': l10n.s_nfc_fido_delete_passkey_failure, 'showSuccess': true }); @@ -394,7 +394,7 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { Future cancelReset() async => invoke('cancelReset'); Future reset() async => invoke('reset', { - 'operationName': l10n.s_nfc_fido_reset, + 'operationName': l10n.c_nfc_fido_reset, 'operationProcessing': l10n.s_nfc_fido_reset_processing, 'operationSuccess': l10n.s_nfc_fido_reset_success, 'operationFailure': l10n.s_nfc_fido_reset_failure, @@ -405,14 +405,14 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { invoke('setPin', { 'callArgs': {'pin': oldPin, 'newPin': newPin}, 'operationName': oldPin != null - ? l10n.s_nfc_fido_change_pin - : l10n.s_nfc_fido_set_pin, + ? l10n.c_nfc_fido_change_pin + : l10n.c_nfc_fido_set_pin, 'operationProcessing': oldPin != null ? l10n.s_nfc_fido_change_pin_processing : l10n.s_nfc_fido_set_pin_processing, 'operationSuccess': oldPin != null ? l10n.s_nfc_fido_change_pin_success - : l10n.s_nfc_fido_set_pin_success, + : l10n.s_pin_set, 'operationFailure': oldPin != null ? l10n.s_nfc_fido_change_pin_failure : l10n.s_nfc_fido_set_pin_failure, @@ -421,10 +421,10 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { Future unlock(String pin) async => invoke('unlock', { 'callArgs': {'pin': pin}, - 'operationName': l10n.s_nfc_fido_unlock, - 'operationProcessing': l10n.s_nfc_fido_unlock_processing, - 'operationSuccess': l10n.s_nfc_fido_unlock_success, - 'operationFailure': l10n.s_nfc_fido_unlock_failure, + 'operationName': l10n.c_nfc_unlock, + 'operationProcessing': l10n.s_nfc_unlock_processing, + 'operationSuccess': l10n.s_nfc_unlock_success, + 'operationFailure': l10n.s_nfc_unlock_failure, 'showSuccess': true }); diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index 33937b6b..f4c0b6f5 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -329,7 +329,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { void build() {} Future reset() async => invoke('reset', { - 'operationName': l10n.s_nfc_oath_reset, + 'operationName': l10n.c_nfc_oath_reset, 'operationProcessing': l10n.s_nfc_oath_reset_processing, 'operationSuccess': l10n.s_nfc_oath_reset_success, 'operationFailure': l10n.s_nfc_oath_reset_failure @@ -338,24 +338,24 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future unlock(String password, {bool remember = false}) async => invoke('unlock', { 'callArgs': {'password': password, 'remember': remember}, - 'operationName': l10n.s_nfc_oath_unlock, - 'operationProcessing': l10n.s_nfc_oath_unlock_processing, - 'operationSuccess': l10n.s_nfc_oath_unlock_success, - 'operationFailure': l10n.s_nfc_oath_unlock_failure, + 'operationName': l10n.c_nfc_unlock, + 'operationProcessing': l10n.s_nfc_unlock_processing, + 'operationSuccess': l10n.s_nfc_unlock_success, + 'operationFailure': l10n.s_nfc_unlock_failure, }); Future setPassword(String? current, String password) async => invoke('setPassword', { 'callArgs': {'current': current, 'password': password}, 'operationName': current != null - ? l10n.s_nfc_oath_change_password - : l10n.s_nfc_oath_set_password, + ? l10n.c_nfc_oath_change_password + : l10n.c_nfc_oath_set_password, 'operationProcessing': current != null ? l10n.s_nfc_oath_change_password_processing : l10n.s_nfc_oath_set_password_processing, 'operationSuccess': current != null ? l10n.s_nfc_oath_change_password_success - : l10n.s_nfc_oath_set_password_success, + : l10n.s_password_set, 'operationFailure': current != null ? l10n.s_nfc_oath_change_password_failure : l10n.s_nfc_oath_set_password_failure, @@ -364,9 +364,9 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future unsetPassword(String current) async => invoke('unsetPassword', { 'callArgs': {'current': current}, - 'operationName': l10n.s_nfc_oath_remove_password, + 'operationName': l10n.c_nfc_oath_remove_password, 'operationProcessing': l10n.s_nfc_oath_remove_password_processing, - 'operationSuccess': l10n.s_nfc_oath_remove_password_success, + 'operationSuccess': l10n.s_password_removed, 'operationFailure': l10n.s_nfc_oath_remove_password_failure, }); @@ -375,7 +375,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future calculate(OathCredential credential) async => invoke('calculate', { 'callArgs': {'credentialId': credential.id}, - 'operationName': l10n.s_nfc_oath_calculate_code, + 'operationName': l10n.c_nfc_oath_calculate_code, 'operationProcessing': l10n.s_nfc_oath_calculate_code_processing, 'operationSuccess': l10n.s_nfc_oath_calculate_code_success, 'operationFailure': l10n.s_nfc_oath_calculate_code_failure, @@ -388,9 +388,9 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uri': credentialUri.toString(), 'requireTouch': requireTouch }, - 'operationName': l10n.s_nfc_oath_add_account, + 'operationName': l10n.c_nfc_oath_add_account, 'operationProcessing': l10n.s_nfc_oath_add_account_processing, - 'operationSuccess': l10n.s_nfc_oath_add_account_success, + 'operationSuccess': l10n.s_account_added, 'operationFailure': l10n.s_nfc_oath_add_account_failure, 'showSuccess': true }); @@ -402,7 +402,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uris': credentialUris, 'requireTouch': touchRequired, }, - 'operationName': l10n.s_nfc_oath_add_multiple_accounts, + 'operationName': l10n.c_nfc_oath_add_multiple_accounts, 'operationProcessing': l10n.s_nfc_oath_add_multiple_accounts_processing, 'operationSuccess': l10n.s_nfc_oath_add_multiple_accounts_success, 'operationFailure': l10n.s_nfc_oath_add_multiple_accounts_failure, @@ -415,18 +415,18 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uri': credentialUri.toString(), 'requireTouch': requireTouch }, - 'operationName': l10n.s_nfc_oath_add_account, + 'operationName': l10n.c_nfc_oath_add_account, 'operationProcessing': l10n.s_nfc_oath_add_account_processing, - 'operationSuccess': l10n.s_nfc_oath_add_account_success, + 'operationSuccess': l10n.s_account_added, 'operationFailure': l10n.s_nfc_oath_add_account_failure, }); Future deleteAccount(OathCredential credential) async => invoke('deleteAccount', { 'callArgs': {'credentialId': credential.id}, - 'operationName': l10n.s_nfc_oath_delete_account, + 'operationName': l10n.c_nfc_oath_delete_account, 'operationProcessing': l10n.s_nfc_oath_delete_account_processing, - 'operationSuccess': l10n.s_nfc_oath_delete_account_success, + 'operationSuccess': l10n.s_account_deleted, 'operationFailure': l10n.s_nfc_oath_delete_account_failure, 'showSuccess': true }); @@ -439,9 +439,9 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'name': name, 'issuer': issuer }, - 'operationName': l10n.s_nfc_oath_rename_account, + 'operationName': l10n.c_nfc_oath_rename_account, 'operationProcessing': l10n.s_nfc_oath_rename_account_processing, - 'operationSuccess': l10n.s_nfc_oath_rename_account_success, + 'operationSuccess': l10n.s_account_renamed, 'operationFailure': l10n.s_nfc_oath_rename_account_failure, }); } diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 52b9ad13..fc5ef4f0 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -46,7 +46,7 @@ class _DialogProvider extends Notifier { // setup properties for ad-hoc action ref.read(nfcActivityWidgetNotifier.notifier).setDialogProperties( operationProcessing: l10n.s_nfc_read_key, - operationFailure: l10n.s_nfc_read_key_failure, + operationFailure: l10n.l_nfc_read_key_failure, showSuccess: false, ); } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 5ea92ade..803b9f45 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -11,7 +11,8 @@ "s_": "Ein einzelnes Wort oder wenige Wörter. Sollte kurz genug sein, um auf einer Schaltfläche oder einer Überschrift angezeigt zu werden.", "l_": "Eine einzelne Zeile, kann umbgebrochen werden. Sollte nicht mehr als einen Satz umfassen und nicht mit einem Punkt enden.", "p_": "Ein oder mehrere ganze Sätze mit allen Satzzeichen.", - "q_": "Eine Frage, die mit einem Fragezeichen endet." + "q_": "Eine Frage, die mit einem Fragezeichen endet.", + "c_": null } }, @@ -886,79 +887,62 @@ "s_allow_screenshots": "Bildschirmfotos erlauben", "@_nfc_oath_actions": {}, - "s_nfc_oath_reset": null, + "c_nfc_oath_reset": null, "s_nfc_oath_reset_processing": null, "s_nfc_oath_reset_success": null, "s_nfc_oath_reset_failure": null, - "s_nfc_oath_unlock": null, - "s_nfc_oath_unlock_processing": null, - "s_nfc_oath_unlock_success": null, - "s_nfc_oath_unlock_failure": null, - - "s_nfc_oath_set_password": null, - "s_nfc_oath_change_password": null, + "c_nfc_oath_set_password": null, + "c_nfc_oath_change_password": null, "s_nfc_oath_set_password_processing": null, "s_nfc_oath_change_password_processing": null, - "s_nfc_oath_set_password_success": null, "s_nfc_oath_change_password_success": null, "s_nfc_oath_set_password_failure": null, "s_nfc_oath_change_password_failure": null, - "s_nfc_oath_remove_password": null, + "c_nfc_oath_remove_password": null, "s_nfc_oath_remove_password_processing": null, - "s_nfc_oath_remove_password_success": null, "s_nfc_oath_remove_password_failure": null, - "s_nfc_oath_add_account": null, + "c_nfc_oath_add_account": null, "s_nfc_oath_add_account_processing": null, - "s_nfc_oath_add_account_success": null, "s_nfc_oath_add_account_failure": null, - "s_nfc_oath_rename_account": null, + "c_nfc_oath_rename_account": null, "s_nfc_oath_rename_account_processing": null, - "s_nfc_oath_rename_account_success": null, "s_nfc_oath_rename_account_failure": null, - "s_nfc_oath_delete_account": null, + "c_nfc_oath_delete_account": null, "s_nfc_oath_delete_account_processing": null, - "s_nfc_oath_delete_account_success": null, "s_nfc_oath_delete_account_failure": null, - "s_nfc_oath_calculate_code": null, + "c_nfc_oath_calculate_code": null, "s_nfc_oath_calculate_code_processing": null, "s_nfc_oath_calculate_code_success": null, "s_nfc_oath_calculate_code_failure": null, - "s_nfc_oath_add_multiple_accounts": null, + "c_nfc_oath_add_multiple_accounts": null, "s_nfc_oath_add_multiple_accounts_processing": null, "s_nfc_oath_add_multiple_accounts_success": null, "s_nfc_oath_add_multiple_accounts_failure": null, "@_nfc_fido_actions": {}, - "s_nfc_fido_reset": null, + "c_nfc_fido_reset": null, "s_nfc_fido_reset_processing": null, "s_nfc_fido_reset_success": null, "s_nfc_fido_reset_failure": null, - "s_nfc_fido_unlock": null, - "s_nfc_fido_unlock_processing": null, - "s_nfc_fido_unlock_success": null, - "s_nfc_fido_unlock_failure": null, - - "s_nfc_fido_set_pin": null, + "c_nfc_fido_set_pin": null, "s_nfc_fido_set_pin_processing": null, - "s_nfc_fido_set_pin_success": null, "s_nfc_fido_set_pin_failure": null, - "s_nfc_fido_change_pin": null, + "c_nfc_fido_change_pin": null, "s_nfc_fido_change_pin_processing": null, "s_nfc_fido_change_pin_success": null, "s_nfc_fido_change_pin_failure": null, - "s_nfc_fido_delete_passkey": null, + "c_nfc_fido_delete_passkey": null, "s_nfc_fido_delete_passkey_processing": null, - "s_nfc_fido_delete_passkey_success": null, "s_nfc_fido_delete_passkey_failure": null, "@_nfc_actions": {}, @@ -970,11 +954,16 @@ }, "s_nfc_read_key": null, - "s_nfc_read_key_failure": null, + "l_nfc_read_key_failure": null, "s_nfc_hold_key": null, "s_nfc_remove_key": null, + "c_nfc_unlock": null, + "s_nfc_unlock_processing": null, + "s_nfc_unlock_success": null, + "s_nfc_unlock_failure": null, + "@_ndef": {}, "p_ndef_set_otp": "OTP-Code wurde erfolgreich von Ihrem YubiKey in die Zwischenablage kopiert.", "p_ndef_set_password": "Passwort wurde erfolgreich von Ihrem YubiKey in die Zwischenablage kopiert.", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 70f7966f..03ef0c5e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -11,7 +11,8 @@ "s_": "A single, or few words. Should be short enough to display on a button, or a header.", "l_": "A single line, can be wrapped. Should not be more than one sentence, and not end with a period.", "p_": "One or more full sentences, with proper punctuation.", - "q_": "A question, ending in question mark." + "q_": "A question, ending in question mark.", + "c_": "Composable, used in substitutions" } }, @@ -886,79 +887,62 @@ "s_allow_screenshots": "Allow screenshots", "@_nfc_oath_actions": {}, - "s_nfc_oath_reset": "reset Accounts", + "c_nfc_oath_reset": "reset Accounts", "s_nfc_oath_reset_processing": "Reset in progress", "s_nfc_oath_reset_success": "Accounts reset", "s_nfc_oath_reset_failure": "Failed to reset accounts", - "s_nfc_oath_unlock": "unlock", - "s_nfc_oath_unlock_processing": "Unlocking", - "s_nfc_oath_unlock_success": "Accounts unlocked", - "s_nfc_oath_unlock_failure": "Failed to unlock", - - "s_nfc_oath_set_password": "set password", - "s_nfc_oath_change_password": "change password", + "c_nfc_oath_set_password": "set password", + "c_nfc_oath_change_password": "change password", "s_nfc_oath_set_password_processing": "Setting password", "s_nfc_oath_change_password_processing": "Changing password", - "s_nfc_oath_set_password_success": "Password set", "s_nfc_oath_change_password_success": "Password changed", "s_nfc_oath_set_password_failure": "Failed to set password", "s_nfc_oath_change_password_failure": "Failed to change password", - "s_nfc_oath_remove_password": "remove password", + "c_nfc_oath_remove_password": "remove password", "s_nfc_oath_remove_password_processing": "Removing password", - "s_nfc_oath_remove_password_success": "Password removed", "s_nfc_oath_remove_password_failure": "Failed to remove password", - "s_nfc_oath_add_account": "add account", + "c_nfc_oath_add_account": "add account", "s_nfc_oath_add_account_processing": "Adding account", - "s_nfc_oath_add_account_success": "Account added", "s_nfc_oath_add_account_failure": "Failed to add account", - "s_nfc_oath_rename_account": "rename account", + "c_nfc_oath_rename_account": "rename account", "s_nfc_oath_rename_account_processing": "Renaming account", - "s_nfc_oath_rename_account_success": "Account renamed", "s_nfc_oath_rename_account_failure": "Failed to rename account", - "s_nfc_oath_delete_account": "delete account", + "c_nfc_oath_delete_account": "delete account", "s_nfc_oath_delete_account_processing": "Deleting account", - "s_nfc_oath_delete_account_success": "Account deleted", "s_nfc_oath_delete_account_failure": "Failed to delete account", - "s_nfc_oath_calculate_code": "calculate code", + "c_nfc_oath_calculate_code": "calculate code", "s_nfc_oath_calculate_code_processing": "Calculating", "s_nfc_oath_calculate_code_success": "Code calculated", "s_nfc_oath_calculate_code_failure": "Failed to calculate code", - "s_nfc_oath_add_multiple_accounts": "add selected accounts", + "c_nfc_oath_add_multiple_accounts": "add selected accounts", "s_nfc_oath_add_multiple_accounts_processing": "Adding accounts", "s_nfc_oath_add_multiple_accounts_success": "Accounts added", "s_nfc_oath_add_multiple_accounts_failure": "Failed to add accounts", "@_nfc_fido_actions": {}, - "s_nfc_fido_reset": "reset FIDO application", + "c_nfc_fido_reset": "reset FIDO application", "s_nfc_fido_reset_processing": "Resetting FIDO", "s_nfc_fido_reset_success": "FIDO reset", "s_nfc_fido_reset_failure": "FIDO reset failed", - "s_nfc_fido_unlock": "unlock", - "s_nfc_fido_unlock_processing": "Unlocking", - "s_nfc_fido_unlock_success": "unlocked", - "s_nfc_fido_unlock_failure": "Failed to unlock", - - "s_nfc_fido_set_pin": "set PIN", + "c_nfc_fido_set_pin": "set PIN", "s_nfc_fido_set_pin_processing": "Setting PIN", - "s_nfc_fido_set_pin_success": "PIN set", "s_nfc_fido_set_pin_failure": "Failure setting PIN", - "s_nfc_fido_change_pin": "change PIN", + "c_nfc_fido_change_pin": "change PIN", "s_nfc_fido_change_pin_processing": "Changing PIN", "s_nfc_fido_change_pin_success": "PIN changed", "s_nfc_fido_change_pin_failure": "Failure changing PIN", - "s_nfc_fido_delete_passkey": "delete passkey", + "c_nfc_fido_delete_passkey": "delete passkey", "s_nfc_fido_delete_passkey_processing": "Deleting passkey", - "s_nfc_fido_delete_passkey_success": "Passkey deleted", "s_nfc_fido_delete_passkey_failure": "Failed to delete passkey", "@_nfc_actions": {}, @@ -970,11 +954,16 @@ }, "s_nfc_read_key": "Reading YubiKey", - "s_nfc_read_key_failure": "Failed to read YubiKey, try again", + "l_nfc_read_key_failure": "Failed to read YubiKey, try again", "s_nfc_hold_key": "Hold YubiKey", "s_nfc_remove_key": "You can remove YubiKey", + "c_nfc_unlock": "unlock", + "s_nfc_unlock_processing": "Unlocking", + "s_nfc_unlock_success": "Accounts unlocked", + "s_nfc_unlock_failure": "Failed to unlock", + "@_ndef": {}, "p_ndef_set_otp": "Successfully copied OTP code from YubiKey to clipboard.", "p_ndef_set_password": "Successfully copied password from YubiKey to clipboard.", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 57d8662a..63bdf407 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -11,7 +11,8 @@ "s_": "A single, or few words. Should be short enough to display on a button, or a header.", "l_": "A single line, can be wrapped. Should not be more than one sentence, and not end with a period.", "p_": "One or more full sentences, with proper punctuation.", - "q_": "A question, ending in question mark." + "q_": "A question, ending in question mark.", + "c_": null } }, @@ -886,79 +887,62 @@ "s_allow_screenshots": "Autoriser captures d'écran", "@_nfc_oath_actions": {}, - "s_nfc_oath_reset": null, + "c_nfc_oath_reset": null, "s_nfc_oath_reset_processing": null, "s_nfc_oath_reset_success": null, "s_nfc_oath_reset_failure": null, - "s_nfc_oath_unlock": null, - "s_nfc_oath_unlock_processing": null, - "s_nfc_oath_unlock_success": null, - "s_nfc_oath_unlock_failure": null, - - "s_nfc_oath_set_password": null, - "s_nfc_oath_change_password": null, + "c_nfc_oath_set_password": null, + "c_nfc_oath_change_password": null, "s_nfc_oath_set_password_processing": null, "s_nfc_oath_change_password_processing": null, - "s_nfc_oath_set_password_success": null, "s_nfc_oath_change_password_success": null, "s_nfc_oath_set_password_failure": null, "s_nfc_oath_change_password_failure": null, - "s_nfc_oath_remove_password": null, + "c_nfc_oath_remove_password": null, "s_nfc_oath_remove_password_processing": null, - "s_nfc_oath_remove_password_success": null, "s_nfc_oath_remove_password_failure": null, - "s_nfc_oath_add_account": null, + "c_nfc_oath_add_account": null, "s_nfc_oath_add_account_processing": null, - "s_nfc_oath_add_account_success": null, "s_nfc_oath_add_account_failure": null, - "s_nfc_oath_rename_account": null, + "c_nfc_oath_rename_account": null, "s_nfc_oath_rename_account_processing": null, - "s_nfc_oath_rename_account_success": null, "s_nfc_oath_rename_account_failure": null, - "s_nfc_oath_delete_account": null, + "c_nfc_oath_delete_account": null, "s_nfc_oath_delete_account_processing": null, - "s_nfc_oath_delete_account_success": null, "s_nfc_oath_delete_account_failure": null, - "s_nfc_oath_calculate_code": null, + "c_nfc_oath_calculate_code": null, "s_nfc_oath_calculate_code_processing": null, "s_nfc_oath_calculate_code_success": null, "s_nfc_oath_calculate_code_failure": null, - "s_nfc_oath_add_multiple_accounts": null, + "c_nfc_oath_add_multiple_accounts": null, "s_nfc_oath_add_multiple_accounts_processing": null, "s_nfc_oath_add_multiple_accounts_success": null, "s_nfc_oath_add_multiple_accounts_failure": null, "@_nfc_fido_actions": {}, - "s_nfc_fido_reset": null, + "c_nfc_fido_reset": null, "s_nfc_fido_reset_processing": null, "s_nfc_fido_reset_success": null, "s_nfc_fido_reset_failure": null, - "s_nfc_fido_unlock": null, - "s_nfc_fido_unlock_processing": null, - "s_nfc_fido_unlock_success": null, - "s_nfc_fido_unlock_failure": null, - - "s_nfc_fido_set_pin": null, + "c_nfc_fido_set_pin": null, "s_nfc_fido_set_pin_processing": null, - "s_nfc_fido_set_pin_success": null, "s_nfc_fido_set_pin_failure": null, - "s_nfc_fido_change_pin": null, + "c_nfc_fido_change_pin": null, "s_nfc_fido_change_pin_processing": null, "s_nfc_fido_change_pin_success": null, "s_nfc_fido_change_pin_failure": null, - "s_nfc_fido_delete_passkey": null, + "c_nfc_fido_delete_passkey": null, "s_nfc_fido_delete_passkey_processing": null, - "s_nfc_fido_delete_passkey_success": null, "s_nfc_fido_delete_passkey_failure": null, "@_nfc_actions": {}, @@ -970,11 +954,16 @@ }, "s_nfc_read_key": null, - "s_nfc_read_key_failure": null, + "l_nfc_read_key_failure": null, "s_nfc_hold_key": null, "s_nfc_remove_key": null, + "c_nfc_unlock": null, + "s_nfc_unlock_processing": null, + "s_nfc_unlock_success": null, + "s_nfc_unlock_failure": null, + "@_ndef": {}, "p_ndef_set_otp": "Code OTP copié de la YubiKey dans le presse-papiers.", "p_ndef_set_password": "Mot de passe copié de la YubiKey dans le presse-papiers.", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 6c3a180e..f857f236 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -11,7 +11,8 @@ "s_": "A single, or few words. Should be short enough to display on a button, or a header.", "l_": "A single line, can be wrapped. Should not be more than one sentence, and not end with a period.", "p_": "One or more full sentences, with proper punctuation.", - "q_": "A question, ending in question mark." + "q_": "A question, ending in question mark.", + "c_": null } }, @@ -886,79 +887,62 @@ "s_allow_screenshots": "スクリーンショットを許可", "@_nfc_oath_actions": {}, - "s_nfc_oath_reset": "アクション:OATHアプレットをリセット", + "c_nfc_oath_reset": null, "s_nfc_oath_reset_processing": null, "s_nfc_oath_reset_success": null, "s_nfc_oath_reset_failure": null, - "s_nfc_oath_unlock": "アクション:OATHアプレットをロック解除", - "s_nfc_oath_unlock_processing": null, - "s_nfc_oath_unlock_success": null, - "s_nfc_oath_unlock_failure": null, - - "s_nfc_oath_set_password": "アクション:OATHパスワードを設定", - "s_nfc_oath_change_password": null, + "c_nfc_oath_set_password": null, + "c_nfc_oath_change_password": null, "s_nfc_oath_set_password_processing": null, "s_nfc_oath_change_password_processing": null, - "s_nfc_oath_set_password_success": null, "s_nfc_oath_change_password_success": null, "s_nfc_oath_set_password_failure": null, "s_nfc_oath_change_password_failure": null, - "s_nfc_oath_remove_password": null, + "c_nfc_oath_remove_password": null, "s_nfc_oath_remove_password_processing": null, - "s_nfc_oath_remove_password_success": null, "s_nfc_oath_remove_password_failure": null, - "s_nfc_oath_add_account": "アクション:新しいアカウントを追加", + "c_nfc_oath_add_account": null, "s_nfc_oath_add_account_processing": null, - "s_nfc_oath_add_account_success": null, "s_nfc_oath_add_account_failure": null, - "s_nfc_oath_rename_account": "アクション:アカウント名を変更", + "c_nfc_oath_rename_account": null, "s_nfc_oath_rename_account_processing": null, - "s_nfc_oath_rename_account_success": null, "s_nfc_oath_rename_account_failure": null, - "s_nfc_oath_delete_account": "アクション:アカウントを削除", + "c_nfc_oath_delete_account": null, "s_nfc_oath_delete_account_processing": null, - "s_nfc_oath_delete_account_success": null, "s_nfc_oath_delete_account_failure": null, - "s_nfc_oath_calculate_code": "アクション:OATHコードを計算", + "c_nfc_oath_calculate_code": null, "s_nfc_oath_calculate_code_processing": null, "s_nfc_oath_calculate_code_success": null, "s_nfc_oath_calculate_code_failure": null, - "s_nfc_oath_add_multiple_accounts": "アクション:複数アカウントを追加", + "c_nfc_oath_add_multiple_accounts": null, "s_nfc_oath_add_multiple_accounts_processing": null, "s_nfc_oath_add_multiple_accounts_success": null, "s_nfc_oath_add_multiple_accounts_failure": null, "@_nfc_fido_actions": {}, - "s_nfc_fido_reset": "アクション: FIDOアプリケーションをリセット", + "c_nfc_fido_reset": null, "s_nfc_fido_reset_processing": null, "s_nfc_fido_reset_success": null, "s_nfc_fido_reset_failure": null, - "s_nfc_fido_unlock": "アクション:FIDOアプリケーションのロックを解除する", - "s_nfc_fido_unlock_processing": null, - "s_nfc_fido_unlock_success": null, - "s_nfc_fido_unlock_failure": null, - - "s_nfc_fido_set_pin": null, + "c_nfc_fido_set_pin": null, "s_nfc_fido_set_pin_processing": null, - "s_nfc_fido_set_pin_success": null, "s_nfc_fido_set_pin_failure": null, - "s_nfc_fido_change_pin": null, + "c_nfc_fido_change_pin": null, "s_nfc_fido_change_pin_processing": null, "s_nfc_fido_change_pin_success": null, "s_nfc_fido_change_pin_failure": null, - "s_nfc_fido_delete_passkey": null, + "c_nfc_fido_delete_passkey": null, "s_nfc_fido_delete_passkey_processing": null, - "s_nfc_fido_delete_passkey_success": null, "s_nfc_fido_delete_passkey_failure": null, "@_nfc_actions": {}, @@ -970,11 +954,16 @@ }, "s_nfc_read_key": null, - "s_nfc_read_key_failure": null, + "l_nfc_read_key_failure": null, "s_nfc_hold_key": null, "s_nfc_remove_key": null, + "c_nfc_unlock": null, + "s_nfc_unlock_processing": null, + "s_nfc_unlock_success": null, + "s_nfc_unlock_failure": null, + "@_ndef": {}, "p_ndef_set_otp": "OTPコードがYubiKeyからクリップボードに正常にコピーされました。", "p_ndef_set_password": "パスワードがYubiKeyからクリップボードに正常にコピーされました。", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 1d3f2477..9cbdc4b5 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -11,7 +11,8 @@ "s_": "A single, or few words. Should be short enough to display on a button, or a header.", "l_": "A single line, can be wrapped. Should not be more than one sentence, and not end with a period.", "p_": "One or more full sentences, with proper punctuation.", - "q_": "A question, ending in question mark." + "q_": "A question, ending in question mark.", + "c_": null } }, @@ -886,79 +887,62 @@ "s_allow_screenshots": "Zezwalaj na zrzuty ekranu", "@_nfc_oath_actions": {}, - "s_nfc_oath_reset": null, + "c_nfc_oath_reset": null, "s_nfc_oath_reset_processing": null, "s_nfc_oath_reset_success": null, "s_nfc_oath_reset_failure": null, - "s_nfc_oath_unlock": null, - "s_nfc_oath_unlock_processing": null, - "s_nfc_oath_unlock_success": null, - "s_nfc_oath_unlock_failure": null, - - "s_nfc_oath_set_password": null, - "s_nfc_oath_change_password": null, + "c_nfc_oath_set_password": null, + "c_nfc_oath_change_password": null, "s_nfc_oath_set_password_processing": null, "s_nfc_oath_change_password_processing": null, - "s_nfc_oath_set_password_success": null, "s_nfc_oath_change_password_success": null, "s_nfc_oath_set_password_failure": null, "s_nfc_oath_change_password_failure": null, - "s_nfc_oath_remove_password": null, + "c_nfc_oath_remove_password": null, "s_nfc_oath_remove_password_processing": null, - "s_nfc_oath_remove_password_success": null, "s_nfc_oath_remove_password_failure": null, - "s_nfc_oath_add_account": null, + "c_nfc_oath_add_account": null, "s_nfc_oath_add_account_processing": null, - "s_nfc_oath_add_account_success": null, "s_nfc_oath_add_account_failure": null, - "s_nfc_oath_rename_account": null, + "c_nfc_oath_rename_account": null, "s_nfc_oath_rename_account_processing": null, - "s_nfc_oath_rename_account_success": null, "s_nfc_oath_rename_account_failure": null, - "s_nfc_oath_delete_account": null, + "c_nfc_oath_delete_account": null, "s_nfc_oath_delete_account_processing": null, - "s_nfc_oath_delete_account_success": null, "s_nfc_oath_delete_account_failure": null, - "s_nfc_oath_calculate_code": null, + "c_nfc_oath_calculate_code": null, "s_nfc_oath_calculate_code_processing": null, "s_nfc_oath_calculate_code_success": null, "s_nfc_oath_calculate_code_failure": null, - "s_nfc_oath_add_multiple_accounts": null, + "c_nfc_oath_add_multiple_accounts": null, "s_nfc_oath_add_multiple_accounts_processing": null, "s_nfc_oath_add_multiple_accounts_success": null, "s_nfc_oath_add_multiple_accounts_failure": null, "@_nfc_fido_actions": {}, - "s_nfc_fido_reset": null, + "c_nfc_fido_reset": null, "s_nfc_fido_reset_processing": null, "s_nfc_fido_reset_success": null, "s_nfc_fido_reset_failure": null, - "s_nfc_fido_unlock": null, - "s_nfc_fido_unlock_processing": null, - "s_nfc_fido_unlock_success": null, - "s_nfc_fido_unlock_failure": null, - - "s_nfc_fido_set_pin": null, + "c_nfc_fido_set_pin": null, "s_nfc_fido_set_pin_processing": null, - "s_nfc_fido_set_pin_success": null, "s_nfc_fido_set_pin_failure": null, - "s_nfc_fido_change_pin": null, + "c_nfc_fido_change_pin": null, "s_nfc_fido_change_pin_processing": null, "s_nfc_fido_change_pin_success": null, "s_nfc_fido_change_pin_failure": null, - "s_nfc_fido_delete_passkey": null, + "c_nfc_fido_delete_passkey": null, "s_nfc_fido_delete_passkey_processing": null, - "s_nfc_fido_delete_passkey_success": null, "s_nfc_fido_delete_passkey_failure": null, "@_nfc_actions": {}, @@ -970,11 +954,16 @@ }, "s_nfc_read_key": null, - "s_nfc_read_key_failure": null, + "l_nfc_read_key_failure": null, "s_nfc_hold_key": null, "s_nfc_remove_key": null, + "c_nfc_unlock": null, + "s_nfc_unlock_processing": null, + "s_nfc_unlock_success": null, + "s_nfc_unlock_failure": null, + "@_ndef": {}, "p_ndef_set_otp": "OTP zostało skopiowane do schowka.", "p_ndef_set_password": "Hasło statyczne zostało skopiowane do schowka.", From 8b2126d16ec7a203a596e423565955f2bc193bd8 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 29 Aug 2024 09:56:58 +0200 Subject: [PATCH 09/50] unfocus correctly to hide sw keyboard --- lib/oath/views/add_account_page.dart | 37 ++++++++++++++++------ lib/oath/views/manage_password_dialog.dart | 11 +++++-- lib/oath/views/rename_account_dialog.dart | 9 ++++-- 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/lib/oath/views/add_account_page.dart b/lib/oath/views/add_account_page.dart index 4a88bcfb..b742fd61 100755 --- a/lib/oath/views/add_account_page.dart +++ b/lib/oath/views/add_account_page.dart @@ -40,7 +40,6 @@ import '../../widgets/app_text_field.dart'; import '../../widgets/choice_filter_chip.dart'; import '../../widgets/file_drop_overlay.dart'; import '../../widgets/file_drop_target.dart'; -import '../../widgets/focus_utils.dart'; import '../../widgets/responsive_dialog.dart'; import '../../widgets/utf8_utils.dart'; import '../keys.dart' as keys; @@ -72,8 +71,11 @@ class OathAddAccountPage extends ConsumerStatefulWidget { class _OathAddAccountPageState extends ConsumerState { final _issuerController = TextEditingController(); + final _issuerFocusNode = FocusNode(); final _accountController = TextEditingController(); + final _accountFocusNode = FocusNode(); final _secretController = TextEditingController(); + final _secretFocusNode = FocusNode(); final _periodController = TextEditingController(text: '$defaultPeriod'); UserInteractionController? _promptController; Uri? _otpauthUri; @@ -88,6 +90,7 @@ class _OathAddAccountPageState extends ConsumerState { List _periodValues = [20, 30, 45, 60]; List _digitsValues = [6, 8]; List? _credentials; + bool _submitting = false; @override void dispose() { @@ -121,6 +124,7 @@ class _OathAddAccountPageState extends ConsumerState { _counter = data.counter; _isObscure = true; _dataLoaded = true; + _submitting = false; }); } @@ -128,8 +132,6 @@ class _OathAddAccountPageState extends ConsumerState { {DevicePath? devicePath, required Uri credUri}) async { final l10n = AppLocalizations.of(context)!; try { - FocusUtils.unfocus(context); - if (devicePath == null) { assert(isAndroid, 'devicePath is only optional for Android'); await ref @@ -272,6 +274,14 @@ class _OathAddAccountPageState extends ConsumerState { void submit() async { if (secretLengthValid && secretFormatValid) { + _issuerFocusNode.unfocus(); + _accountFocusNode.unfocus(); + _secretFocusNode.unfocus(); + + setState(() { + _submitting = true; + }); + final cred = CredentialData( issuer: issuerText.isEmpty ? null : issuerText, name: nameText, @@ -302,6 +312,10 @@ class _OathAddAccountPageState extends ConsumerState { }, ); } + + setState(() { + _submitting = false; + }); } else { setState(() { _validateSecret = true; @@ -382,6 +396,7 @@ class _OathAddAccountPageState extends ConsumerState { prefixIcon: const Icon(Symbols.business), ), textInputAction: TextInputAction.next, + focusNode: _issuerFocusNode, onChanged: (value) { setState(() { // Update maxlengths @@ -402,17 +417,20 @@ class _OathAddAccountPageState extends ConsumerState { labelText: l10n.s_account_name, helperText: '', // Prevents dialog resizing when disabled - errorText: (byteLength(nameText) > nameMaxLength) - ? '' // needs empty string to render as error - : isUnique - ? null - : l10n.l_name_already_exists, + errorText: _submitting + ? null + : (byteLength(nameText) > nameMaxLength) + ? '' // needs empty string to render as error + : isUnique + ? null + : l10n.l_name_already_exists, prefixIcon: const Icon(Symbols.person), ), textInputAction: TextInputAction.next, + focusNode: _accountFocusNode, onChanged: (value) { setState(() { - // Update maxlengths + // Update max lengths }); }, onSubmitted: (_) { @@ -452,6 +470,7 @@ class _OathAddAccountPageState extends ConsumerState { )), readOnly: _dataLoaded, textInputAction: TextInputAction.done, + focusNode: _secretFocusNode, onChanged: (value) { setState(() { _validateSecret = false; diff --git a/lib/oath/views/manage_password_dialog.dart b/lib/oath/views/manage_password_dialog.dart index cc77e308..14c34773 100755 --- a/lib/oath/views/manage_password_dialog.dart +++ b/lib/oath/views/manage_password_dialog.dart @@ -25,7 +25,6 @@ import '../../app/state.dart'; import '../../management/models.dart'; import '../../widgets/app_input_decoration.dart'; import '../../widgets/app_text_field.dart'; -import '../../widgets/focus_utils.dart'; import '../../widgets/responsive_dialog.dart'; import '../keys.dart' as keys; import '../models.dart'; @@ -63,8 +62,14 @@ class _ManagePasswordDialogState extends ConsumerState { super.dispose(); } + void _removeFocus() { + _currentPasswordFocus.unfocus(); + _newPasswordFocus.unfocus(); + _confirmPasswordFocus.unfocus(); + } + _submit() async { - FocusUtils.unfocus(context); + _removeFocus(); final result = await ref .read(oathStateProvider(widget.path).notifier) @@ -171,6 +176,8 @@ class _ManagePasswordDialogState extends ConsumerState { onPressed: _currentPasswordController.text.isNotEmpty && !_currentIsWrong ? () async { + _removeFocus(); + final result = await ref .read(oathStateProvider(widget.path).notifier) .unsetPassword( diff --git a/lib/oath/views/rename_account_dialog.dart b/lib/oath/views/rename_account_dialog.dart index bf6a26ce..be281cb7 100755 --- a/lib/oath/views/rename_account_dialog.dart +++ b/lib/oath/views/rename_account_dialog.dart @@ -28,7 +28,6 @@ import '../../desktop/models.dart'; import '../../exception/cancellation_exception.dart'; import '../../widgets/app_input_decoration.dart'; import '../../widgets/app_text_form_field.dart'; -import '../../widgets/focus_utils.dart'; import '../../widgets/responsive_dialog.dart'; import '../../widgets/utf8_utils.dart'; import '../keys.dart' as keys; @@ -118,6 +117,9 @@ class _RenameAccountDialogState extends ConsumerState { late String _issuer; late String _name; + final _issuerFocusNode = FocusNode(); + final _nameFocusNode = FocusNode(); + @override void initState() { super.initState(); @@ -126,7 +128,8 @@ class _RenameAccountDialogState extends ConsumerState { } void _submit() async { - FocusUtils.unfocus(context); + _issuerFocusNode.unfocus(); + _nameFocusNode.unfocus(); final nav = Navigator.of(context); final renamed = await widget.rename(_issuer.isNotEmpty ? _issuer : null, _name); @@ -188,6 +191,7 @@ class _RenameAccountDialogState extends ConsumerState { prefixIcon: const Icon(Symbols.business), ), textInputAction: TextInputAction.next, + focusNode: _issuerFocusNode, onChanged: (value) { setState(() { _issuer = value.trim(); @@ -212,6 +216,7 @@ class _RenameAccountDialogState extends ConsumerState { prefixIcon: const Icon(Symbols.people_alt), ), textInputAction: TextInputAction.done, + focusNode: _nameFocusNode, onChanged: (value) { setState(() { _name = value.trim(); From 7924a3cac3a5b05e7b944868c2ac48c23042b821 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 29 Aug 2024 10:17:18 +0200 Subject: [PATCH 10/50] review and improve unfocus changes --- lib/fido/views/pin_dialog.dart | 4 ++++ lib/fido/views/pin_entry_form.dart | 3 +++ lib/oath/views/add_account_page.dart | 25 +++++++++++++---------- lib/oath/views/rename_account_dialog.dart | 22 +++++++++++++------- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/lib/fido/views/pin_dialog.dart b/lib/fido/views/pin_dialog.dart index bb1db268..fdda7bde 100755 --- a/lib/fido/views/pin_dialog.dart +++ b/lib/fido/views/pin_dialog.dart @@ -280,6 +280,10 @@ class _FidoPinDialogState extends ConsumerState { } void _submit() async { + _currentPinFocus.unfocus(); + _newPinFocus.unfocus(); + _confirmPinFocus.unfocus(); + final l10n = AppLocalizations.of(context)!; final oldPin = _currentPinController.text.isNotEmpty ? _currentPinController.text diff --git a/lib/fido/views/pin_entry_form.dart b/lib/fido/views/pin_entry_form.dart index 371e077c..2e7acdea 100644 --- a/lib/fido/views/pin_entry_form.dart +++ b/lib/fido/views/pin_entry_form.dart @@ -30,6 +30,7 @@ import '../state.dart'; class PinEntryForm extends ConsumerStatefulWidget { final FidoState _state; final DeviceNode _deviceNode; + const PinEntryForm(this._state, this._deviceNode, {super.key}); @override @@ -58,6 +59,8 @@ class _PinEntryFormState extends ConsumerState { } void _submit() async { + _pinFocus.unfocus(); + setState(() { _pinIsWrong = false; _isObscure = true; diff --git a/lib/oath/views/add_account_page.dart b/lib/oath/views/add_account_page.dart index b742fd61..036765cd 100755 --- a/lib/oath/views/add_account_page.dart +++ b/lib/oath/views/add_account_page.dart @@ -71,11 +71,11 @@ class OathAddAccountPage extends ConsumerStatefulWidget { class _OathAddAccountPageState extends ConsumerState { final _issuerController = TextEditingController(); - final _issuerFocusNode = FocusNode(); final _accountController = TextEditingController(); - final _accountFocusNode = FocusNode(); final _secretController = TextEditingController(); - final _secretFocusNode = FocusNode(); + final _issuerFocus = FocusNode(); + final _accountFocus = FocusNode(); + final _secretFocus = FocusNode(); final _periodController = TextEditingController(text: '$defaultPeriod'); UserInteractionController? _promptController; Uri? _otpauthUri; @@ -98,6 +98,9 @@ class _OathAddAccountPageState extends ConsumerState { _accountController.dispose(); _secretController.dispose(); _periodController.dispose(); + _issuerFocus.dispose(); + _accountFocus.dispose(); + _secretFocus.dispose(); super.dispose(); } @@ -274,9 +277,9 @@ class _OathAddAccountPageState extends ConsumerState { void submit() async { if (secretLengthValid && secretFormatValid) { - _issuerFocusNode.unfocus(); - _accountFocusNode.unfocus(); - _secretFocusNode.unfocus(); + _issuerFocus.unfocus(); + _accountFocus.unfocus(); + _secretFocus.unfocus(); setState(() { _submitting = true; @@ -386,8 +389,8 @@ class _OathAddAccountPageState extends ConsumerState { decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_issuer_optional, - helperText: - '', // Prevents dialog resizing when disabled + helperText: '', + // Prevents dialog resizing when disabled errorText: (byteLength(issuerText) > issuerMaxLength) ? '' // needs empty string to render as error : issuerNoColon @@ -396,7 +399,7 @@ class _OathAddAccountPageState extends ConsumerState { prefixIcon: const Icon(Symbols.business), ), textInputAction: TextInputAction.next, - focusNode: _issuerFocusNode, + focusNode: _issuerFocus, onChanged: (value) { setState(() { // Update maxlengths @@ -427,7 +430,7 @@ class _OathAddAccountPageState extends ConsumerState { prefixIcon: const Icon(Symbols.person), ), textInputAction: TextInputAction.next, - focusNode: _accountFocusNode, + focusNode: _accountFocus, onChanged: (value) { setState(() { // Update max lengths @@ -470,7 +473,7 @@ class _OathAddAccountPageState extends ConsumerState { )), readOnly: _dataLoaded, textInputAction: TextInputAction.done, - focusNode: _secretFocusNode, + focusNode: _secretFocus, onChanged: (value) { setState(() { _validateSecret = false; diff --git a/lib/oath/views/rename_account_dialog.dart b/lib/oath/views/rename_account_dialog.dart index be281cb7..ad5bdf54 100755 --- a/lib/oath/views/rename_account_dialog.dart +++ b/lib/oath/views/rename_account_dialog.dart @@ -117,8 +117,8 @@ class _RenameAccountDialogState extends ConsumerState { late String _issuer; late String _name; - final _issuerFocusNode = FocusNode(); - final _nameFocusNode = FocusNode(); + final _issuerFocus = FocusNode(); + final _nameFocus = FocusNode(); @override void initState() { @@ -127,9 +127,16 @@ class _RenameAccountDialogState extends ConsumerState { _name = widget.name.trim(); } + @override + void dispose() { + _issuerFocus.dispose(); + _nameFocus.dispose(); + super.dispose(); + } + void _submit() async { - _issuerFocusNode.unfocus(); - _nameFocusNode.unfocus(); + _issuerFocus.unfocus(); + _nameFocus.unfocus(); final nav = Navigator.of(context); final renamed = await widget.rename(_issuer.isNotEmpty ? _issuer : null, _name); @@ -191,7 +198,7 @@ class _RenameAccountDialogState extends ConsumerState { prefixIcon: const Icon(Symbols.business), ), textInputAction: TextInputAction.next, - focusNode: _issuerFocusNode, + focusNode: _issuerFocus, onChanged: (value) { setState(() { _issuer = value.trim(); @@ -207,7 +214,8 @@ class _RenameAccountDialogState extends ConsumerState { decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_account_name, - helperText: '', // Prevents dialog resizing when disabled + helperText: '', + // Prevents dialog resizing when disabled errorText: !nameNotEmpty ? l10n.l_account_name_required : !isUnique @@ -216,7 +224,7 @@ class _RenameAccountDialogState extends ConsumerState { prefixIcon: const Icon(Symbols.people_alt), ), textInputAction: TextInputAction.done, - focusNode: _nameFocusNode, + focusNode: _nameFocus, onChanged: (value) { setState(() { _name = value.trim(); From a21691c585e7c9da6d85c6b030a8f4b39a1b1dd7 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 30 Aug 2024 13:36:03 +0200 Subject: [PATCH 11/50] update class names, cleanup --- lib/android/tap_request_dialog.dart | 46 +- lib/android/views/nfc/models.dart | 66 +++ lib/android/views/nfc/models.freezed.dart | 430 ++++++++++++++++++ .../nfc/nfc_activity_command_listener.dart | 29 +- .../views/nfc/nfc_activity_overlay.dart | 39 +- lib/app/models.dart | 43 -- lib/app/models.freezed.dart | 407 ----------------- 7 files changed, 550 insertions(+), 510 deletions(-) create mode 100644 lib/android/views/nfc/models.dart create mode 100644 lib/android/views/nfc/models.freezed.dart diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index fc5ef4f0..9b7991f3 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -21,10 +21,10 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:material_symbols_icons/symbols.dart'; -import '../app/models.dart'; import '../app/state.dart'; import '../widgets/pulsing.dart'; import 'state.dart'; +import 'views/nfc/models.dart'; import 'views/nfc/nfc_activity_overlay.dart'; const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); @@ -40,18 +40,18 @@ class _DialogProvider extends Notifier { int build() { final l10n = ref.read(l10nProvider); ref.listen(androidNfcActivityProvider, (previous, current) { - final notifier = ref.read(nfcActivityCommandNotifier.notifier); + final notifier = ref.read(nfcEventNotifier.notifier); if (!explicitAction) { // setup properties for ad-hoc action - ref.read(nfcActivityWidgetNotifier.notifier).setDialogProperties( + ref.read(nfcViewNotifier.notifier).setDialogProperties( operationProcessing: l10n.s_nfc_read_key, operationFailure: l10n.l_nfc_read_key_failure, showSuccess: false, ); } - final properties = ref.read(nfcActivityWidgetNotifier); + final properties = ref.read(nfcViewNotifier); debugPrint('XXX now it is: $current'); switch (current) { @@ -64,8 +64,8 @@ class _DialogProvider extends Notifier { processingTimer = Timer(Duration(milliseconds: timeout), () { if (!explicitAction) { // show the widget - notifier.update(NfcActivityWidgetCommand( - action: NfcActivityWidgetActionShowWidget( + notifier.sendCommand(NfcEventCommand( + event: NfcShowViewEvent( child: _NfcActivityWidgetView( title: properties.operationProcessing, subtitle: '', @@ -73,8 +73,8 @@ class _DialogProvider extends Notifier { )))); } else { // the processing view will only be shown if the timer is still active - notifier.update(NfcActivityWidgetCommand( - action: NfcActivityWidgetActionSetWidgetData( + notifier.sendCommand(NfcEventCommand( + event: NfcUpdateViewEvent( child: _NfcActivityWidgetView( title: properties.operationProcessing, subtitle: l10n.s_nfc_hold_key, @@ -87,8 +87,8 @@ class _DialogProvider extends Notifier { explicitAction = false; // next action might not be explicit processingTimer?.cancel(); if (properties.showSuccess ?? false) { - notifier.update(NfcActivityWidgetCommand( - action: NfcActivityWidgetActionSetWidgetData( + notifier.sendCommand(NfcEventCommand( + event: NfcUpdateViewEvent( child: NfcActivityClosingCountdownWidgetView( closeInSec: 5, child: _NfcActivityWidgetView( @@ -99,14 +99,14 @@ class _DialogProvider extends Notifier { )))); } else { // directly hide - notifier.update(NfcActivityWidgetCommand( - action: const NfcActivityWidgetActionHideWidget(timeoutMs: 0))); + notifier.sendCommand( + NfcEventCommand(event: const NfcHideViewEvent(timeoutMs: 0))); } break; case NfcActivity.processingInterrupted: explicitAction = false; // next action might not be explicit - notifier.update(NfcActivityWidgetCommand( - action: NfcActivityWidgetActionSetWidgetData( + notifier.sendCommand(NfcEventCommand( + event: NfcUpdateViewEvent( child: _NfcActivityWidgetView( title: properties.operationFailure, inProgress: false, @@ -121,13 +121,13 @@ class _DialogProvider extends Notifier { }); _channel.setMethodCallHandler((call) async { - final notifier = ref.read(nfcActivityCommandNotifier.notifier); - final properties = ref.read(nfcActivityWidgetNotifier); + final notifier = ref.read(nfcEventNotifier.notifier); + final properties = ref.read(nfcViewNotifier); switch (call.method) { case 'show': explicitAction = true; - notifier.update(NfcActivityWidgetCommand( - action: NfcActivityWidgetActionShowWidget( + notifier.sendCommand(NfcEventCommand( + event: NfcShowViewEvent( child: _NfcActivityWidgetView( title: l10n.s_nfc_tap_for( properties.operationName ?? '[OPERATION NAME MISSING]'), @@ -137,8 +137,8 @@ class _DialogProvider extends Notifier { break; case 'close': - notifier.update(NfcActivityWidgetCommand( - action: const NfcActivityWidgetActionHideWidget(timeoutMs: 0))); + notifier.sendCommand( + NfcEventCommand(event: const NfcHideViewEvent(timeoutMs: 0))); break; default: @@ -163,7 +163,7 @@ class _DialogProvider extends Notifier { Timer.periodic( const Duration(milliseconds: 200), (timer) { - if (!ref.read(nfcActivityWidgetNotifier.select((s) => s.isShowing))) { + if (!ref.read(nfcViewNotifier.select((s) => s.isShowing))) { timer.cancel(); completer.complete(); } @@ -220,7 +220,7 @@ class MethodChannelHelper { String? operationFailure, bool? showSuccess, Map arguments = const {}}) async { - final notifier = _ref.read(nfcActivityWidgetNotifier.notifier); + final notifier = _ref.read(nfcViewNotifier.notifier); notifier.setDialogProperties( operationName: operationName, operationProcessing: operationProcessing, @@ -244,7 +244,7 @@ class MethodChannelNotifier extends Notifier { Future invoke(String name, [Map params = const {}]) async { - final notifier = ref.read(nfcActivityWidgetNotifier.notifier); + final notifier = ref.read(nfcViewNotifier.notifier); notifier.setDialogProperties( operationName: params['operationName'], operationProcessing: params['operationProcessing'], diff --git a/lib/android/views/nfc/models.dart b/lib/android/views/nfc/models.dart new file mode 100644 index 00000000..bc917f95 --- /dev/null +++ b/lib/android/views/nfc/models.dart @@ -0,0 +1,66 @@ +/* + * 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. + */ + +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'models.freezed.dart'; + +class NfcEvent { + const NfcEvent(); +} + +class NfcShowViewEvent extends NfcEvent { + final Widget child; + + const NfcShowViewEvent({required this.child}); +} + +class NfcHideViewEvent extends NfcEvent { + final int timeoutMs; + + const NfcHideViewEvent({required this.timeoutMs}); +} + +class NfcCancelEvent extends NfcEvent { + const NfcCancelEvent(); +} + +class NfcUpdateViewEvent extends NfcEvent { + final Widget child; + + const NfcUpdateViewEvent({required this.child}); +} + +@freezed +class NfcView with _$NfcView { + factory NfcView( + {required bool isShowing, + required Widget child, + bool? showCloseButton, + bool? showSuccess, + String? operationName, + String? operationProcessing, + String? operationSuccess, + String? operationFailure}) = _NfcView; +} + +@freezed +class NfcEventCommand with _$NfcEventCommand { + factory NfcEventCommand({ + @Default(NfcEvent()) NfcEvent event, + }) = _NfcEventCommand; +} diff --git a/lib/android/views/nfc/models.freezed.dart b/lib/android/views/nfc/models.freezed.dart new file mode 100644 index 00000000..28fbc7d0 --- /dev/null +++ b/lib/android/views/nfc/models.freezed.dart @@ -0,0 +1,430 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$NfcView { + bool get isShowing => throw _privateConstructorUsedError; + Widget get child => throw _privateConstructorUsedError; + bool? get showCloseButton => throw _privateConstructorUsedError; + bool? get showSuccess => throw _privateConstructorUsedError; + String? get operationName => throw _privateConstructorUsedError; + String? get operationProcessing => throw _privateConstructorUsedError; + String? get operationSuccess => throw _privateConstructorUsedError; + String? get operationFailure => throw _privateConstructorUsedError; + + /// Create a copy of NfcView + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $NfcViewCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $NfcViewCopyWith<$Res> { + factory $NfcViewCopyWith(NfcView value, $Res Function(NfcView) then) = + _$NfcViewCopyWithImpl<$Res, NfcView>; + @useResult + $Res call( + {bool isShowing, + Widget child, + bool? showCloseButton, + bool? showSuccess, + String? operationName, + String? operationProcessing, + String? operationSuccess, + String? operationFailure}); +} + +/// @nodoc +class _$NfcViewCopyWithImpl<$Res, $Val extends NfcView> + implements $NfcViewCopyWith<$Res> { + _$NfcViewCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of NfcView + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isShowing = null, + Object? child = null, + Object? showCloseButton = freezed, + Object? showSuccess = freezed, + Object? operationName = freezed, + Object? operationProcessing = freezed, + Object? operationSuccess = freezed, + Object? operationFailure = freezed, + }) { + return _then(_value.copyWith( + isShowing: null == isShowing + ? _value.isShowing + : isShowing // ignore: cast_nullable_to_non_nullable + as bool, + child: null == child + ? _value.child + : child // ignore: cast_nullable_to_non_nullable + as Widget, + showCloseButton: freezed == showCloseButton + ? _value.showCloseButton + : showCloseButton // ignore: cast_nullable_to_non_nullable + as bool?, + showSuccess: freezed == showSuccess + ? _value.showSuccess + : showSuccess // ignore: cast_nullable_to_non_nullable + as bool?, + operationName: freezed == operationName + ? _value.operationName + : operationName // ignore: cast_nullable_to_non_nullable + as String?, + operationProcessing: freezed == operationProcessing + ? _value.operationProcessing + : operationProcessing // ignore: cast_nullable_to_non_nullable + as String?, + operationSuccess: freezed == operationSuccess + ? _value.operationSuccess + : operationSuccess // ignore: cast_nullable_to_non_nullable + as String?, + operationFailure: freezed == operationFailure + ? _value.operationFailure + : operationFailure // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$NfcViewImplCopyWith<$Res> implements $NfcViewCopyWith<$Res> { + factory _$$NfcViewImplCopyWith( + _$NfcViewImpl value, $Res Function(_$NfcViewImpl) then) = + __$$NfcViewImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {bool isShowing, + Widget child, + bool? showCloseButton, + bool? showSuccess, + String? operationName, + String? operationProcessing, + String? operationSuccess, + String? operationFailure}); +} + +/// @nodoc +class __$$NfcViewImplCopyWithImpl<$Res> + extends _$NfcViewCopyWithImpl<$Res, _$NfcViewImpl> + implements _$$NfcViewImplCopyWith<$Res> { + __$$NfcViewImplCopyWithImpl( + _$NfcViewImpl _value, $Res Function(_$NfcViewImpl) _then) + : super(_value, _then); + + /// Create a copy of NfcView + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isShowing = null, + Object? child = null, + Object? showCloseButton = freezed, + Object? showSuccess = freezed, + Object? operationName = freezed, + Object? operationProcessing = freezed, + Object? operationSuccess = freezed, + Object? operationFailure = freezed, + }) { + return _then(_$NfcViewImpl( + isShowing: null == isShowing + ? _value.isShowing + : isShowing // ignore: cast_nullable_to_non_nullable + as bool, + child: null == child + ? _value.child + : child // ignore: cast_nullable_to_non_nullable + as Widget, + showCloseButton: freezed == showCloseButton + ? _value.showCloseButton + : showCloseButton // ignore: cast_nullable_to_non_nullable + as bool?, + showSuccess: freezed == showSuccess + ? _value.showSuccess + : showSuccess // ignore: cast_nullable_to_non_nullable + as bool?, + operationName: freezed == operationName + ? _value.operationName + : operationName // ignore: cast_nullable_to_non_nullable + as String?, + operationProcessing: freezed == operationProcessing + ? _value.operationProcessing + : operationProcessing // ignore: cast_nullable_to_non_nullable + as String?, + operationSuccess: freezed == operationSuccess + ? _value.operationSuccess + : operationSuccess // ignore: cast_nullable_to_non_nullable + as String?, + operationFailure: freezed == operationFailure + ? _value.operationFailure + : operationFailure // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$NfcViewImpl implements _NfcView { + _$NfcViewImpl( + {required this.isShowing, + required this.child, + this.showCloseButton, + this.showSuccess, + this.operationName, + this.operationProcessing, + this.operationSuccess, + this.operationFailure}); + + @override + final bool isShowing; + @override + final Widget child; + @override + final bool? showCloseButton; + @override + final bool? showSuccess; + @override + final String? operationName; + @override + final String? operationProcessing; + @override + final String? operationSuccess; + @override + final String? operationFailure; + + @override + String toString() { + return 'NfcView(isShowing: $isShowing, child: $child, showCloseButton: $showCloseButton, showSuccess: $showSuccess, operationName: $operationName, operationProcessing: $operationProcessing, operationSuccess: $operationSuccess, operationFailure: $operationFailure)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NfcViewImpl && + (identical(other.isShowing, isShowing) || + other.isShowing == isShowing) && + (identical(other.child, child) || other.child == child) && + (identical(other.showCloseButton, showCloseButton) || + other.showCloseButton == showCloseButton) && + (identical(other.showSuccess, showSuccess) || + other.showSuccess == showSuccess) && + (identical(other.operationName, operationName) || + other.operationName == operationName) && + (identical(other.operationProcessing, operationProcessing) || + other.operationProcessing == operationProcessing) && + (identical(other.operationSuccess, operationSuccess) || + other.operationSuccess == operationSuccess) && + (identical(other.operationFailure, operationFailure) || + other.operationFailure == operationFailure)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + isShowing, + child, + showCloseButton, + showSuccess, + operationName, + operationProcessing, + operationSuccess, + operationFailure); + + /// Create a copy of NfcView + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$NfcViewImplCopyWith<_$NfcViewImpl> get copyWith => + __$$NfcViewImplCopyWithImpl<_$NfcViewImpl>(this, _$identity); +} + +abstract class _NfcView implements NfcView { + factory _NfcView( + {required final bool isShowing, + required final Widget child, + final bool? showCloseButton, + final bool? showSuccess, + final String? operationName, + final String? operationProcessing, + final String? operationSuccess, + final String? operationFailure}) = _$NfcViewImpl; + + @override + bool get isShowing; + @override + Widget get child; + @override + bool? get showCloseButton; + @override + bool? get showSuccess; + @override + String? get operationName; + @override + String? get operationProcessing; + @override + String? get operationSuccess; + @override + String? get operationFailure; + + /// Create a copy of NfcView + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$NfcViewImplCopyWith<_$NfcViewImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$NfcEventCommand { + NfcEvent get event => throw _privateConstructorUsedError; + + /// Create a copy of NfcEventCommand + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $NfcEventCommandCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $NfcEventCommandCopyWith<$Res> { + factory $NfcEventCommandCopyWith( + NfcEventCommand value, $Res Function(NfcEventCommand) then) = + _$NfcEventCommandCopyWithImpl<$Res, NfcEventCommand>; + @useResult + $Res call({NfcEvent event}); +} + +/// @nodoc +class _$NfcEventCommandCopyWithImpl<$Res, $Val extends NfcEventCommand> + implements $NfcEventCommandCopyWith<$Res> { + _$NfcEventCommandCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of NfcEventCommand + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? event = null, + }) { + return _then(_value.copyWith( + event: null == event + ? _value.event + : event // ignore: cast_nullable_to_non_nullable + as NfcEvent, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$NfcEventCommandImplCopyWith<$Res> + implements $NfcEventCommandCopyWith<$Res> { + factory _$$NfcEventCommandImplCopyWith(_$NfcEventCommandImpl value, + $Res Function(_$NfcEventCommandImpl) then) = + __$$NfcEventCommandImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({NfcEvent event}); +} + +/// @nodoc +class __$$NfcEventCommandImplCopyWithImpl<$Res> + extends _$NfcEventCommandCopyWithImpl<$Res, _$NfcEventCommandImpl> + implements _$$NfcEventCommandImplCopyWith<$Res> { + __$$NfcEventCommandImplCopyWithImpl( + _$NfcEventCommandImpl _value, $Res Function(_$NfcEventCommandImpl) _then) + : super(_value, _then); + + /// Create a copy of NfcEventCommand + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? event = null, + }) { + return _then(_$NfcEventCommandImpl( + event: null == event + ? _value.event + : event // ignore: cast_nullable_to_non_nullable + as NfcEvent, + )); + } +} + +/// @nodoc + +class _$NfcEventCommandImpl implements _NfcEventCommand { + _$NfcEventCommandImpl({this.event = const NfcEvent()}); + + @override + @JsonKey() + final NfcEvent event; + + @override + String toString() { + return 'NfcEventCommand(event: $event)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NfcEventCommandImpl && + (identical(other.event, event) || other.event == event)); + } + + @override + int get hashCode => Object.hash(runtimeType, event); + + /// Create a copy of NfcEventCommand + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$NfcEventCommandImplCopyWith<_$NfcEventCommandImpl> get copyWith => + __$$NfcEventCommandImplCopyWithImpl<_$NfcEventCommandImpl>( + this, _$identity); +} + +abstract class _NfcEventCommand implements NfcEventCommand { + factory _NfcEventCommand({final NfcEvent event}) = _$NfcEventCommandImpl; + + @override + NfcEvent get event; + + /// Create a copy of NfcEventCommand + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$NfcEventCommandImplCopyWith<_$NfcEventCommandImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/android/views/nfc/nfc_activity_command_listener.dart b/lib/android/views/nfc/nfc_activity_command_listener.dart index c25d2d3f..90fef3ff 100644 --- a/lib/android/views/nfc/nfc_activity_command_listener.dart +++ b/lib/android/views/nfc/nfc_activity_command_listener.dart @@ -17,8 +17,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../app/models.dart'; import '../../tap_request_dialog.dart'; +import 'models.dart'; import 'nfc_activity_overlay.dart'; final nfcActivityCommandListener = Provider<_NfcActivityCommandListener>( @@ -26,28 +26,27 @@ final nfcActivityCommandListener = Provider<_NfcActivityCommandListener>( class _NfcActivityCommandListener { final ProviderRef _ref; - ProviderSubscription? listener; + ProviderSubscription? listener; _NfcActivityCommandListener(this._ref); void startListener(BuildContext context) { - debugPrint('XXX Started listener'); listener?.close(); - listener = _ref.listen(nfcActivityCommandNotifier.select((c) => c.action), + listener = _ref.listen(nfcEventNotifier.select((c) => c.event), (previous, action) { debugPrint( 'XXX Change in command for Overlay: $previous -> $action in context: $context'); switch (action) { - case (NfcActivityWidgetActionShowWidget a): + case (NfcShowViewEvent a): _show(context, a.child); break; - case (NfcActivityWidgetActionSetWidgetData a): - _ref.read(nfcActivityWidgetNotifier.notifier).update(a.child); + case (NfcUpdateViewEvent a): + _ref.read(nfcViewNotifier.notifier).update(a.child); break; - case (NfcActivityWidgetActionHideWidget _): + case (NfcHideViewEvent _): _hide(context); break; - case (NfcActivityWidgetActionCancelWidget _): + case (NfcCancelEvent _): _ref.read(androidDialogProvider.notifier).cancelDialog(); _hide(context); break; @@ -56,17 +55,15 @@ class _NfcActivityCommandListener { } void _show(BuildContext context, Widget child) async { - final widgetNotifier = _ref.read(nfcActivityWidgetNotifier.notifier); + final widgetNotifier = _ref.read(nfcViewNotifier.notifier); widgetNotifier.update(child); - if (!_ref.read(nfcActivityWidgetNotifier.select((s) => s.isShowing))) { + if (!_ref.read(nfcViewNotifier.select((s) => s.isShowing))) { widgetNotifier.setShowing(true); final result = await showModalBottomSheet( context: context, builder: (BuildContext context) { return const NfcBottomSheet(); }); - - debugPrint('XXX result is: $result'); if (result == null) { // the modal sheet was cancelled by Back button, close button or dismiss _ref.read(androidDialogProvider.notifier).cancelDialog(); @@ -76,9 +73,9 @@ class _NfcActivityCommandListener { } void _hide(BuildContext context) { - if (_ref.read(nfcActivityWidgetNotifier.select((s) => s.isShowing))) { - Navigator.of(context).pop('AFTER OP'); - _ref.read(nfcActivityWidgetNotifier.notifier).setShowing(false); + if (_ref.read(nfcViewNotifier.select((s) => s.isShowing))) { + Navigator.of(context).pop('HIDDEN'); + _ref.read(nfcViewNotifier.notifier).setShowing(false); } } } diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/views/nfc/nfc_activity_overlay.dart index 985b016e..288d7bf6 100644 --- a/lib/android/views/nfc/nfc_activity_overlay.dart +++ b/lib/android/views/nfc/nfc_activity_overlay.dart @@ -4,28 +4,26 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:material_symbols_icons/symbols.dart'; -import '../../../app/models.dart'; import '../../state.dart'; +import 'models.dart'; -final nfcActivityCommandNotifier = NotifierProvider< - _NfcActivityWidgetCommandNotifier, - NfcActivityWidgetCommand>(_NfcActivityWidgetCommandNotifier.new); +final nfcEventNotifier = + NotifierProvider<_NfcEventCommandNotifier, NfcEventCommand>( + _NfcEventCommandNotifier.new); -class _NfcActivityWidgetCommandNotifier - extends Notifier { +class _NfcEventCommandNotifier extends Notifier { @override - NfcActivityWidgetCommand build() { - return NfcActivityWidgetCommand(action: const NfcActivityWidgetAction()); + NfcEventCommand build() { + return NfcEventCommand(event: const NfcEvent()); } - void update(NfcActivityWidgetCommand command) { + void sendCommand(NfcEventCommand command) { state = command; } } -final nfcActivityWidgetNotifier = - NotifierProvider<_NfcActivityWidgetNotifier, NfcActivityWidgetState>( - _NfcActivityWidgetNotifier.new); +final nfcViewNotifier = + NotifierProvider<_NfcViewNotifier, NfcView>(_NfcViewNotifier.new); class NfcActivityClosingCountdownWidgetView extends ConsumerStatefulWidget { final int closeInSec; @@ -100,16 +98,15 @@ class _NfcActivityClosingCountdownWidgetViewState void hideNow() { debugPrint('XXX closing because have to!'); - ref.read(nfcActivityCommandNotifier.notifier).update( - NfcActivityWidgetCommand( - action: const NfcActivityWidgetActionHideWidget(timeoutMs: 0))); + ref.read(nfcEventNotifier.notifier).sendCommand( + NfcEventCommand(event: const NfcHideViewEvent(timeoutMs: 0))); } } -class _NfcActivityWidgetNotifier extends Notifier { +class _NfcViewNotifier extends Notifier { @override - NfcActivityWidgetState build() { - return NfcActivityWidgetState(isShowing: false, child: const SizedBox()); + NfcView build() { + return NfcView(isShowing: false, child: const SizedBox()); } void update(Widget child) { @@ -140,9 +137,9 @@ class NfcBottomSheet extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final widget = ref.watch(nfcActivityWidgetNotifier.select((s) => s.child)); - final showCloseButton = ref.watch( - nfcActivityWidgetNotifier.select((s) => s.showCloseButton ?? false)); + final widget = ref.watch(nfcViewNotifier.select((s) => s.child)); + final showCloseButton = + ref.watch(nfcViewNotifier.select((s) => s.showCloseButton ?? false)); return Column( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, diff --git a/lib/app/models.dart b/lib/app/models.dart index 433e3ab3..1f45ebab 100755 --- a/lib/app/models.dart +++ b/lib/app/models.dart @@ -170,46 +170,3 @@ class _ColorConverter implements JsonConverter { @override int? toJson(Color? object) => object?.value; } - -class NfcActivityWidgetAction { - const NfcActivityWidgetAction(); -} - -class NfcActivityWidgetActionShowWidget extends NfcActivityWidgetAction { - final Widget child; - const NfcActivityWidgetActionShowWidget({required this.child}); -} - -class NfcActivityWidgetActionHideWidget extends NfcActivityWidgetAction { - final int timeoutMs; - const NfcActivityWidgetActionHideWidget({required this.timeoutMs}); -} - -class NfcActivityWidgetActionCancelWidget extends NfcActivityWidgetAction { - const NfcActivityWidgetActionCancelWidget(); -} - -class NfcActivityWidgetActionSetWidgetData extends NfcActivityWidgetAction { - final Widget child; - const NfcActivityWidgetActionSetWidgetData({required this.child}); -} - -@freezed -class NfcActivityWidgetState with _$NfcActivityWidgetState { - factory NfcActivityWidgetState( - {required bool isShowing, - required Widget child, - bool? showCloseButton, - bool? showSuccess, - String? operationName, - String? operationProcessing, - String? operationSuccess, - String? operationFailure}) = _NfcActivityWidgetState; -} - -@freezed -class NfcActivityWidgetCommand with _$NfcActivityWidgetCommand { - factory NfcActivityWidgetCommand({ - @Default(NfcActivityWidgetAction()) NfcActivityWidgetAction action, - }) = _NfcActivityWidgetCommand; -} diff --git a/lib/app/models.freezed.dart b/lib/app/models.freezed.dart index 52854fb3..37629fcb 100644 --- a/lib/app/models.freezed.dart +++ b/lib/app/models.freezed.dart @@ -1346,410 +1346,3 @@ abstract class _KeyCustomization implements KeyCustomization { _$$KeyCustomizationImplCopyWith<_$KeyCustomizationImpl> get copyWith => throw _privateConstructorUsedError; } - -/// @nodoc -mixin _$NfcActivityWidgetState { - bool get isShowing => throw _privateConstructorUsedError; - Widget get child => throw _privateConstructorUsedError; - bool? get showCloseButton => throw _privateConstructorUsedError; - bool? get showSuccess => throw _privateConstructorUsedError; - String? get operationName => throw _privateConstructorUsedError; - String? get operationProcessing => throw _privateConstructorUsedError; - String? get operationSuccess => throw _privateConstructorUsedError; - String? get operationFailure => throw _privateConstructorUsedError; - - @JsonKey(ignore: true) - $NfcActivityWidgetStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $NfcActivityWidgetStateCopyWith<$Res> { - factory $NfcActivityWidgetStateCopyWith(NfcActivityWidgetState value, - $Res Function(NfcActivityWidgetState) then) = - _$NfcActivityWidgetStateCopyWithImpl<$Res, NfcActivityWidgetState>; - @useResult - $Res call( - {bool isShowing, - Widget child, - bool? showCloseButton, - bool? showSuccess, - String? operationName, - String? operationProcessing, - String? operationSuccess, - String? operationFailure}); -} - -/// @nodoc -class _$NfcActivityWidgetStateCopyWithImpl<$Res, - $Val extends NfcActivityWidgetState> - implements $NfcActivityWidgetStateCopyWith<$Res> { - _$NfcActivityWidgetStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? isShowing = null, - Object? child = null, - Object? showCloseButton = freezed, - Object? showSuccess = freezed, - Object? operationName = freezed, - Object? operationProcessing = freezed, - Object? operationSuccess = freezed, - Object? operationFailure = freezed, - }) { - return _then(_value.copyWith( - isShowing: null == isShowing - ? _value.isShowing - : isShowing // ignore: cast_nullable_to_non_nullable - as bool, - child: null == child - ? _value.child - : child // ignore: cast_nullable_to_non_nullable - as Widget, - showCloseButton: freezed == showCloseButton - ? _value.showCloseButton - : showCloseButton // ignore: cast_nullable_to_non_nullable - as bool?, - showSuccess: freezed == showSuccess - ? _value.showSuccess - : showSuccess // ignore: cast_nullable_to_non_nullable - as bool?, - operationName: freezed == operationName - ? _value.operationName - : operationName // ignore: cast_nullable_to_non_nullable - as String?, - operationProcessing: freezed == operationProcessing - ? _value.operationProcessing - : operationProcessing // ignore: cast_nullable_to_non_nullable - as String?, - operationSuccess: freezed == operationSuccess - ? _value.operationSuccess - : operationSuccess // ignore: cast_nullable_to_non_nullable - as String?, - operationFailure: freezed == operationFailure - ? _value.operationFailure - : operationFailure // ignore: cast_nullable_to_non_nullable - as String?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$NfcActivityWidgetStateImplCopyWith<$Res> - implements $NfcActivityWidgetStateCopyWith<$Res> { - factory _$$NfcActivityWidgetStateImplCopyWith( - _$NfcActivityWidgetStateImpl value, - $Res Function(_$NfcActivityWidgetStateImpl) then) = - __$$NfcActivityWidgetStateImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {bool isShowing, - Widget child, - bool? showCloseButton, - bool? showSuccess, - String? operationName, - String? operationProcessing, - String? operationSuccess, - String? operationFailure}); -} - -/// @nodoc -class __$$NfcActivityWidgetStateImplCopyWithImpl<$Res> - extends _$NfcActivityWidgetStateCopyWithImpl<$Res, - _$NfcActivityWidgetStateImpl> - implements _$$NfcActivityWidgetStateImplCopyWith<$Res> { - __$$NfcActivityWidgetStateImplCopyWithImpl( - _$NfcActivityWidgetStateImpl _value, - $Res Function(_$NfcActivityWidgetStateImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? isShowing = null, - Object? child = null, - Object? showCloseButton = freezed, - Object? showSuccess = freezed, - Object? operationName = freezed, - Object? operationProcessing = freezed, - Object? operationSuccess = freezed, - Object? operationFailure = freezed, - }) { - return _then(_$NfcActivityWidgetStateImpl( - isShowing: null == isShowing - ? _value.isShowing - : isShowing // ignore: cast_nullable_to_non_nullable - as bool, - child: null == child - ? _value.child - : child // ignore: cast_nullable_to_non_nullable - as Widget, - showCloseButton: freezed == showCloseButton - ? _value.showCloseButton - : showCloseButton // ignore: cast_nullable_to_non_nullable - as bool?, - showSuccess: freezed == showSuccess - ? _value.showSuccess - : showSuccess // ignore: cast_nullable_to_non_nullable - as bool?, - operationName: freezed == operationName - ? _value.operationName - : operationName // ignore: cast_nullable_to_non_nullable - as String?, - operationProcessing: freezed == operationProcessing - ? _value.operationProcessing - : operationProcessing // ignore: cast_nullable_to_non_nullable - as String?, - operationSuccess: freezed == operationSuccess - ? _value.operationSuccess - : operationSuccess // ignore: cast_nullable_to_non_nullable - as String?, - operationFailure: freezed == operationFailure - ? _value.operationFailure - : operationFailure // ignore: cast_nullable_to_non_nullable - as String?, - )); - } -} - -/// @nodoc - -class _$NfcActivityWidgetStateImpl implements _NfcActivityWidgetState { - _$NfcActivityWidgetStateImpl( - {required this.isShowing, - required this.child, - this.showCloseButton, - this.showSuccess, - this.operationName, - this.operationProcessing, - this.operationSuccess, - this.operationFailure}); - - @override - final bool isShowing; - @override - final Widget child; - @override - final bool? showCloseButton; - @override - final bool? showSuccess; - @override - final String? operationName; - @override - final String? operationProcessing; - @override - final String? operationSuccess; - @override - final String? operationFailure; - - @override - String toString() { - return 'NfcActivityWidgetState(isShowing: $isShowing, child: $child, showCloseButton: $showCloseButton, showSuccess: $showSuccess, operationName: $operationName, operationProcessing: $operationProcessing, operationSuccess: $operationSuccess, operationFailure: $operationFailure)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$NfcActivityWidgetStateImpl && - (identical(other.isShowing, isShowing) || - other.isShowing == isShowing) && - (identical(other.child, child) || other.child == child) && - (identical(other.showCloseButton, showCloseButton) || - other.showCloseButton == showCloseButton) && - (identical(other.showSuccess, showSuccess) || - other.showSuccess == showSuccess) && - (identical(other.operationName, operationName) || - other.operationName == operationName) && - (identical(other.operationProcessing, operationProcessing) || - other.operationProcessing == operationProcessing) && - (identical(other.operationSuccess, operationSuccess) || - other.operationSuccess == operationSuccess) && - (identical(other.operationFailure, operationFailure) || - other.operationFailure == operationFailure)); - } - - @override - int get hashCode => Object.hash( - runtimeType, - isShowing, - child, - showCloseButton, - showSuccess, - operationName, - operationProcessing, - operationSuccess, - operationFailure); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$NfcActivityWidgetStateImplCopyWith<_$NfcActivityWidgetStateImpl> - get copyWith => __$$NfcActivityWidgetStateImplCopyWithImpl< - _$NfcActivityWidgetStateImpl>(this, _$identity); -} - -abstract class _NfcActivityWidgetState implements NfcActivityWidgetState { - factory _NfcActivityWidgetState( - {required final bool isShowing, - required final Widget child, - final bool? showCloseButton, - final bool? showSuccess, - final String? operationName, - final String? operationProcessing, - final String? operationSuccess, - final String? operationFailure}) = _$NfcActivityWidgetStateImpl; - - @override - bool get isShowing; - @override - Widget get child; - @override - bool? get showCloseButton; - @override - bool? get showSuccess; - @override - String? get operationName; - @override - String? get operationProcessing; - @override - String? get operationSuccess; - @override - String? get operationFailure; - @override - @JsonKey(ignore: true) - _$$NfcActivityWidgetStateImplCopyWith<_$NfcActivityWidgetStateImpl> - get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -mixin _$NfcActivityWidgetCommand { - NfcActivityWidgetAction get action => throw _privateConstructorUsedError; - - @JsonKey(ignore: true) - $NfcActivityWidgetCommandCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $NfcActivityWidgetCommandCopyWith<$Res> { - factory $NfcActivityWidgetCommandCopyWith(NfcActivityWidgetCommand value, - $Res Function(NfcActivityWidgetCommand) then) = - _$NfcActivityWidgetCommandCopyWithImpl<$Res, NfcActivityWidgetCommand>; - @useResult - $Res call({NfcActivityWidgetAction action}); -} - -/// @nodoc -class _$NfcActivityWidgetCommandCopyWithImpl<$Res, - $Val extends NfcActivityWidgetCommand> - implements $NfcActivityWidgetCommandCopyWith<$Res> { - _$NfcActivityWidgetCommandCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? action = null, - }) { - return _then(_value.copyWith( - action: null == action - ? _value.action - : action // ignore: cast_nullable_to_non_nullable - as NfcActivityWidgetAction, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$NfcActivityWidgetCommandImplCopyWith<$Res> - implements $NfcActivityWidgetCommandCopyWith<$Res> { - factory _$$NfcActivityWidgetCommandImplCopyWith( - _$NfcActivityWidgetCommandImpl value, - $Res Function(_$NfcActivityWidgetCommandImpl) then) = - __$$NfcActivityWidgetCommandImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({NfcActivityWidgetAction action}); -} - -/// @nodoc -class __$$NfcActivityWidgetCommandImplCopyWithImpl<$Res> - extends _$NfcActivityWidgetCommandCopyWithImpl<$Res, - _$NfcActivityWidgetCommandImpl> - implements _$$NfcActivityWidgetCommandImplCopyWith<$Res> { - __$$NfcActivityWidgetCommandImplCopyWithImpl( - _$NfcActivityWidgetCommandImpl _value, - $Res Function(_$NfcActivityWidgetCommandImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? action = null, - }) { - return _then(_$NfcActivityWidgetCommandImpl( - action: null == action - ? _value.action - : action // ignore: cast_nullable_to_non_nullable - as NfcActivityWidgetAction, - )); - } -} - -/// @nodoc - -class _$NfcActivityWidgetCommandImpl implements _NfcActivityWidgetCommand { - _$NfcActivityWidgetCommandImpl( - {this.action = const NfcActivityWidgetAction()}); - - @override - @JsonKey() - final NfcActivityWidgetAction action; - - @override - String toString() { - return 'NfcActivityWidgetCommand(action: $action)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$NfcActivityWidgetCommandImpl && - (identical(other.action, action) || other.action == action)); - } - - @override - int get hashCode => Object.hash(runtimeType, action); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$NfcActivityWidgetCommandImplCopyWith<_$NfcActivityWidgetCommandImpl> - get copyWith => __$$NfcActivityWidgetCommandImplCopyWithImpl< - _$NfcActivityWidgetCommandImpl>(this, _$identity); -} - -abstract class _NfcActivityWidgetCommand implements NfcActivityWidgetCommand { - factory _NfcActivityWidgetCommand({final NfcActivityWidgetAction action}) = - _$NfcActivityWidgetCommandImpl; - - @override - NfcActivityWidgetAction get action; - @override - @JsonKey(ignore: true) - _$$NfcActivityWidgetCommandImplCopyWith<_$NfcActivityWidgetCommandImpl> - get copyWith => throw _privateConstructorUsedError; -} From 075ce16e57e83b00d227c8ac55083e5843fe75ec Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 30 Aug 2024 13:44:20 +0200 Subject: [PATCH 12/50] replace pulsing widget with nfc progressbar --- lib/android/tap_request_dialog.dart | 7 +-- lib/android/views/nfc/nfc_progress_bar.dart | 40 ++++++++++++ lib/widgets/pulsing.dart | 68 --------------------- 3 files changed, 42 insertions(+), 73 deletions(-) create mode 100644 lib/android/views/nfc/nfc_progress_bar.dart delete mode 100644 lib/widgets/pulsing.dart diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 9b7991f3..520a1269 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -19,13 +19,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:material_symbols_icons/symbols.dart'; import '../app/state.dart'; -import '../widgets/pulsing.dart'; import 'state.dart'; import 'views/nfc/models.dart'; import 'views/nfc/nfc_activity_overlay.dart'; +import 'views/nfc/nfc_progress_bar.dart'; const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); @@ -197,9 +196,7 @@ class _NfcActivityWidgetView extends StatelessWidget { textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleSmall), const SizedBox(height: 32), - inProgress - ? const Pulsing(child: Icon(Symbols.contactless, size: 64)) - : const Icon(Symbols.contactless, size: 64), + NfcIconProgressBar(inProgress), const SizedBox(height: 24) ], ), diff --git a/lib/android/views/nfc/nfc_progress_bar.dart b/lib/android/views/nfc/nfc_progress_bar.dart new file mode 100644 index 00000000..89b5d532 --- /dev/null +++ b/lib/android/views/nfc/nfc_progress_bar.dart @@ -0,0 +1,40 @@ +/* + * 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. + */ + +import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; + +class NfcIconProgressBar extends StatelessWidget { + final bool inProgress; + + const NfcIconProgressBar(this.inProgress, {super.key}); + + @override + Widget build(BuildContext context) => Stack( + alignment: AlignmentDirectional.center, + children: [ + Visibility( + visible: inProgress, + child: const SizedBox( + width: 64, + height: 64, + child: CircularProgressIndicator(), + ), + ), + const Icon(Symbols.contactless, size: 64) + ], + ); +} diff --git a/lib/widgets/pulsing.dart b/lib/widgets/pulsing.dart deleted file mode 100644 index 9e941b1a..00000000 --- a/lib/widgets/pulsing.dart +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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. - */ - -import 'package:flutter/material.dart'; - -class Pulsing extends StatefulWidget { - final Widget child; - - const Pulsing({super.key, required this.child}); - - @override - State createState() => _PulsingState(); -} - -class _PulsingState extends State with SingleTickerProviderStateMixin { - late final AnimationController controller; - late final Animation animationScale; - - late final CurvedAnimation curvedAnimation; - - static const _duration = Duration(milliseconds: 400); - - @override - Widget build(BuildContext context) { - return SizedBox( - child: Transform.scale(scale: animationScale.value, child: widget.child), - ); - } - - @override - void initState() { - super.initState(); - controller = AnimationController( - duration: _duration, - vsync: this, - ); - curvedAnimation = CurvedAnimation( - parent: controller, curve: Curves.easeIn, reverseCurve: Curves.easeOut); - animationScale = Tween( - begin: 1.0, - end: 1.2, - ).animate(curvedAnimation) - ..addListener(() { - setState(() {}); - }); - - controller.repeat(reverse: true); - } - - @override - void dispose() { - controller.dispose(); - super.dispose(); - } -} From 4534120ac4f48b38c3e18958fff7eaa24b926266 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 30 Aug 2024 13:51:05 +0200 Subject: [PATCH 13/50] remove unused widgets --- lib/android/views/nfc/fade_in_out.dart | 94 ------------ .../nfc/main_page_nfc_activity_widget.dart | 47 ------ lib/android/views/nfc/nfc_activity_icon.dart | 142 ------------------ .../views/nfc/nfc_activity_widget.dart | 49 ------ lib/app/views/main_page.dart | 3 +- 5 files changed, 1 insertion(+), 334 deletions(-) delete mode 100644 lib/android/views/nfc/fade_in_out.dart delete mode 100644 lib/android/views/nfc/main_page_nfc_activity_widget.dart delete mode 100644 lib/android/views/nfc/nfc_activity_icon.dart delete mode 100644 lib/android/views/nfc/nfc_activity_widget.dart diff --git a/lib/android/views/nfc/fade_in_out.dart b/lib/android/views/nfc/fade_in_out.dart deleted file mode 100644 index 2cd270a1..00000000 --- a/lib/android/views/nfc/fade_in_out.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -/// Repeatedly fades in and out its child -class FadeInOut extends StatefulWidget { - final Widget child; - final double minOpacity; - final double maxOpacity; - final Duration pulseDuration; - final Duration delayBetweenShakesDuration; - final Duration startupDelay; - - const FadeInOut( - {super.key, - required this.child, - this.minOpacity = 0.0, - this.maxOpacity = 1.0, - this.pulseDuration = const Duration(milliseconds: 300), - this.delayBetweenShakesDuration = const Duration(seconds: 3), - this.startupDelay = Duration.zero}); - - @override - State createState() => _FadeInOutState(); -} - -class _FadeInOutState extends State - with SingleTickerProviderStateMixin { - late AnimationController _controller; - late Animation _animation; - late Timer delayTimer; - - bool playingForward = true; - - @override - void initState() { - super.initState(); - _controller = AnimationController( - vsync: this, - duration: - Duration(milliseconds: widget.pulseDuration.inMilliseconds ~/ 2)); - - _controller.addListener(() async { - if (_controller.isCompleted || _controller.isDismissed) { - playingForward = !playingForward; - var delay = Duration.zero; - if (playingForward == true) { - delay = widget.delayBetweenShakesDuration; - } - - if (delayTimer.isActive) { - delayTimer.cancel(); - } - - delayTimer = Timer(delay, () async { - if (_controller.isCompleted) { - await _controller.reverse(); - } else if (_controller.isDismissed) { - await _controller.forward(); - } - }); - } - }); - - _animation = - Tween(begin: widget.minOpacity, end: widget.maxOpacity).animate( - CurvedAnimation(parent: _controller, curve: Curves.ease), - ); - - delayTimer = Timer(widget.startupDelay, () { - _controller.forward(); - }); - } - - @override - void dispose() { - delayTimer.cancel(); - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _controller, - builder: (BuildContext context, Widget? child) { - return Opacity( - opacity: _animation.value, - child: widget.child, - ); - }, - ); - } -} diff --git a/lib/android/views/nfc/main_page_nfc_activity_widget.dart b/lib/android/views/nfc/main_page_nfc_activity_widget.dart deleted file mode 100644 index f41b4722..00000000 --- a/lib/android/views/nfc/main_page_nfc_activity_widget.dart +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2023 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. - */ - -import 'package:flutter/material.dart'; - -import '../../../app/views/horizontal_shake.dart'; -import '../../state.dart'; -import 'nfc_activity_widget.dart'; - -class MainPageNfcActivityWidget extends StatelessWidget { - final Widget widget; - - const MainPageNfcActivityWidget(this.widget, {super.key}); - - @override - Widget build(BuildContext context) { - return NfcActivityWidget( - width: 128.0, - height: 128.0, - iconView: (nfcActivityState) { - return switch (nfcActivityState) { - NfcActivity.ready => HorizontalShake( - shakeCount: 2, - shakeDuration: const Duration(milliseconds: 50), - delayBetweenShakesDuration: const Duration(seconds: 6), - startupDelay: const Duration(seconds: 3), - child: widget, - ), - _ => widget - }; - }, - ); - } -} diff --git a/lib/android/views/nfc/nfc_activity_icon.dart b/lib/android/views/nfc/nfc_activity_icon.dart deleted file mode 100644 index 91374042..00000000 --- a/lib/android/views/nfc/nfc_activity_icon.dart +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2023 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. - */ - -import 'dart:math'; -import 'dart:ui'; - -import 'package:flutter/material.dart'; - -import '../../../app/views/horizontal_shake.dart'; -import '../../../theme.dart'; -import '../../state.dart'; -import 'fade_in_out.dart'; - -/// Default icon for [NfcActivityWidget] -class NfcActivityIcon extends StatelessWidget { - final NfcActivity nfcActivity; - - const NfcActivityIcon(this.nfcActivity, {super.key}); - - @override - Widget build(BuildContext context) => switch (nfcActivity) { - NfcActivity.ready => const HorizontalShake( - startupDelay: Duration(seconds: 4), - child: _NfcIconWithOpacity(0.8)), - NfcActivity.processingStarted => Stack( - fit: StackFit.loose, - children: [ - FadeInOut( - minOpacity: 0.1, - maxOpacity: 1.0, - pulseDuration: const Duration(milliseconds: 300), - delayBetweenShakesDuration: const Duration(milliseconds: 20), - child: ImageFiltered( - imageFilter: ImageFilter.blur( - sigmaX: 7.0, - sigmaY: 7.0, - tileMode: TileMode.decal, - ), - child: const Opacity( - opacity: 0.6, - child: _NfcIcon( - color: defaultPrimaryColor, - ), - ), - ), - ), - const _NfcIcon(), - ], - ), - NfcActivity.processingInterrupted => - const _NfcIconWrapper(Icons.warning_amber_rounded), - _ => const _NfcIcon(), - }; -} - -class _NfcIconWithOpacity extends StatelessWidget { - final double opacity; - - const _NfcIconWithOpacity(this.opacity); - - @override - Widget build(BuildContext context) => Opacity( - opacity: opacity, - child: const _NfcIcon(), - ); -} - -class _NfcIconWrapper extends StatelessWidget { - final IconData _iconData; - - const _NfcIconWrapper(this._iconData); - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext buildContext, BoxConstraints constraints) => - Icon( - _iconData, - size: constraints.biggest.width, - )); - } -} - -class _NfcIcon extends StatelessWidget { - final Color? color; - - const _NfcIcon({this.color}); - - @override - Widget build(BuildContext context) { - final theme = IconTheme.of(context); - return LayoutBuilder( - builder: (BuildContext buildContext, BoxConstraints constraints) => - CustomPaint( - size: Size.copy(constraints.biggest), - painter: _NfcIconPainter(color ?? theme.color ?? Colors.black), - ), - ); - } -} - -class _NfcIconPainter extends CustomPainter { - final Color color; - - _NfcIconPainter(this.color); - - @override - void paint(Canvas canvas, Size size) { - final step = size.width / 4; - const sweep = pi / 4; - - final paint = Paint() - ..color = color - ..style = PaintingStyle.stroke - ..strokeCap = StrokeCap.round - ..strokeWidth = step / 2; - - final rect = - Offset(size.width * -1.7, 0) & Size(size.width * 2, size.height); - for (var i = 0; i < 3; i++) { - canvas.drawArc(rect.inflate(i * step), -sweep / 2, sweep, false, paint); - } - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return false; - } -} diff --git a/lib/android/views/nfc/nfc_activity_widget.dart b/lib/android/views/nfc/nfc_activity_widget.dart deleted file mode 100644 index 703ffa08..00000000 --- a/lib/android/views/nfc/nfc_activity_widget.dart +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 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. - */ - -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:logging/logging.dart'; - -import '../../../app/logging.dart'; -import '../../state.dart'; -import 'nfc_activity_icon.dart'; - -final _logger = Logger('NfcActivityWidget'); - -class NfcActivityWidget extends ConsumerWidget { - final double width; - final double height; - final Widget Function(NfcActivity)? iconView; - - const NfcActivityWidget( - {super.key, this.width = 32.0, this.height = 32.0, this.iconView}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final NfcActivity nfcActivityState = ref.watch(androidNfcActivityProvider); - - _logger.debug('State for NfcActivityWidget changed to $nfcActivityState'); - - return IgnorePointer( - child: SizedBox( - width: width, - height: height, - child: iconView?.call(nfcActivityState) ?? - NfcActivityIcon(nfcActivityState)), - ); - } -} diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index ebf77ad7..7e6cbc25 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -21,7 +21,6 @@ import 'package:material_symbols_icons/symbols.dart'; import '../../android/app_methods.dart'; import '../../android/state.dart'; -import '../../android/views/nfc/main_page_nfc_activity_widget.dart'; import '../../core/state.dart'; import '../../fido/views/fingerprints_screen.dart'; import '../../fido/views/passkeys_screen.dart'; @@ -87,7 +86,7 @@ class MainPage extends ConsumerWidget { var isNfcEnabled = ref.watch(androidNfcStateProvider); return HomeMessagePage( centered: true, - graphic: MainPageNfcActivityWidget(noKeyImage), + graphic: noKeyImage, header: hasNfcSupport && isNfcEnabled ? l10n.l_insert_or_tap_yk : l10n.l_insert_yk, From cc761c32bab847655fb311ea5c0eda55296f6679 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 30 Aug 2024 13:54:26 +0200 Subject: [PATCH 14/50] update naming style --- lib/android/init.dart | 2 +- lib/android/tap_request_dialog.dart | 4 ++-- .../nfc/nfc_activity_command_listener.dart | 18 +++++++++--------- .../views/nfc/nfc_activity_overlay.dart | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/android/init.dart b/lib/android/init.dart index 607067ab..d1a97a16 100644 --- a/lib/android/init.dart +++ b/lib/android/init.dart @@ -107,7 +107,7 @@ Future initialize() async { child: DismissKeyboard( child: YubicoAuthenticatorApp(page: Consumer( builder: (context, ref, child) { - ref.read(nfcActivityCommandListener).startListener(context); + ref.read(nfcEventCommandListener).startListener(context); Timer.run(() { ref.read(featureFlagProvider.notifier) diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 520a1269..a9055ad7 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -39,7 +39,7 @@ class _DialogProvider extends Notifier { int build() { final l10n = ref.read(l10nProvider); ref.listen(androidNfcActivityProvider, (previous, current) { - final notifier = ref.read(nfcEventNotifier.notifier); + final notifier = ref.read(nfcEventCommandNotifier.notifier); if (!explicitAction) { // setup properties for ad-hoc action @@ -120,7 +120,7 @@ class _DialogProvider extends Notifier { }); _channel.setMethodCallHandler((call) async { - final notifier = ref.read(nfcEventNotifier.notifier); + final notifier = ref.read(nfcEventCommandNotifier.notifier); final properties = ref.read(nfcViewNotifier); switch (call.method) { case 'show': diff --git a/lib/android/views/nfc/nfc_activity_command_listener.dart b/lib/android/views/nfc/nfc_activity_command_listener.dart index 90fef3ff..13756d35 100644 --- a/lib/android/views/nfc/nfc_activity_command_listener.dart +++ b/lib/android/views/nfc/nfc_activity_command_listener.dart @@ -21,18 +21,18 @@ import '../../tap_request_dialog.dart'; import 'models.dart'; import 'nfc_activity_overlay.dart'; -final nfcActivityCommandListener = Provider<_NfcActivityCommandListener>( - (ref) => _NfcActivityCommandListener(ref)); +final nfcEventCommandListener = + Provider<_NfcEventCommandListener>((ref) => _NfcEventCommandListener(ref)); -class _NfcActivityCommandListener { +class _NfcEventCommandListener { final ProviderRef _ref; ProviderSubscription? listener; - _NfcActivityCommandListener(this._ref); + _NfcEventCommandListener(this._ref); void startListener(BuildContext context) { listener?.close(); - listener = _ref.listen(nfcEventNotifier.select((c) => c.event), + listener = _ref.listen(nfcEventCommandNotifier.select((c) => c.event), (previous, action) { debugPrint( 'XXX Change in command for Overlay: $previous -> $action in context: $context'); @@ -55,10 +55,10 @@ class _NfcActivityCommandListener { } void _show(BuildContext context, Widget child) async { - final widgetNotifier = _ref.read(nfcViewNotifier.notifier); - widgetNotifier.update(child); + final notifier = _ref.read(nfcViewNotifier.notifier); + notifier.update(child); if (!_ref.read(nfcViewNotifier.select((s) => s.isShowing))) { - widgetNotifier.setShowing(true); + notifier.setShowing(true); final result = await showModalBottomSheet( context: context, builder: (BuildContext context) { @@ -68,7 +68,7 @@ class _NfcActivityCommandListener { // the modal sheet was cancelled by Back button, close button or dismiss _ref.read(androidDialogProvider.notifier).cancelDialog(); } - widgetNotifier.setShowing(false); + notifier.setShowing(false); } } diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/views/nfc/nfc_activity_overlay.dart index 288d7bf6..f9e0c2b8 100644 --- a/lib/android/views/nfc/nfc_activity_overlay.dart +++ b/lib/android/views/nfc/nfc_activity_overlay.dart @@ -7,7 +7,7 @@ import 'package:material_symbols_icons/symbols.dart'; import '../../state.dart'; import 'models.dart'; -final nfcEventNotifier = +final nfcEventCommandNotifier = NotifierProvider<_NfcEventCommandNotifier, NfcEventCommand>( _NfcEventCommandNotifier.new); @@ -98,7 +98,7 @@ class _NfcActivityClosingCountdownWidgetViewState void hideNow() { debugPrint('XXX closing because have to!'); - ref.read(nfcEventNotifier.notifier).sendCommand( + ref.read(nfcEventCommandNotifier.notifier).sendCommand( NfcEventCommand(event: const NfcHideViewEvent(timeoutMs: 0))); } } From 44e3bc76910b09d284e25c5f89f93a5a06734b11 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 30 Aug 2024 14:05:50 +0200 Subject: [PATCH 15/50] refactor --- lib/android/fido/state.dart | 2 +- lib/android/method_channel_notifier.dart | 45 ++++++++++++ lib/android/oath/state.dart | 2 +- lib/android/tap_request_dialog.dart | 70 ++----------------- lib/android/views/nfc/nfc_content_widget.dart | 34 +++++++++ 5 files changed, 87 insertions(+), 66 deletions(-) create mode 100644 lib/android/method_channel_notifier.dart create mode 100644 lib/android/views/nfc/nfc_content_widget.dart diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index fb9b9178..d0229a67 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -32,7 +32,7 @@ import '../../exception/no_data_exception.dart'; import '../../exception/platform_exception_decoder.dart'; import '../../fido/models.dart'; import '../../fido/state.dart'; -import '../tap_request_dialog.dart'; +import '../method_channel_notifier.dart'; final _log = Logger('android.fido.state'); diff --git a/lib/android/method_channel_notifier.dart b/lib/android/method_channel_notifier.dart new file mode 100644 index 00000000..dd8d2327 --- /dev/null +++ b/lib/android/method_channel_notifier.dart @@ -0,0 +1,45 @@ +/* + * 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. + */ + +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'tap_request_dialog.dart'; +import 'views/nfc/nfc_activity_overlay.dart'; + +class MethodChannelNotifier extends Notifier { + final MethodChannel _channel; + + MethodChannelNotifier(this._channel); + + @override + void build() {} + + Future invoke(String name, + [Map params = const {}]) async { + final notifier = ref.read(nfcViewNotifier.notifier); + notifier.setDialogProperties( + operationName: params['operationName'], + operationProcessing: params['operationProcessing'], + operationSuccess: params['operationSuccess'], + operationFailure: params['operationFailure'], + showSuccess: params['showSuccess']); + + final result = await _channel.invokeMethod(name, params['callArgs']); + await ref.read(androidDialogProvider.notifier).waitForDialogClosed(); + return result; + } +} diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index f4c0b6f5..6e6dcaa8 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -35,7 +35,7 @@ import '../../exception/no_data_exception.dart'; import '../../exception/platform_exception_decoder.dart'; import '../../oath/models.dart'; import '../../oath/state.dart'; -import '../tap_request_dialog.dart'; +import '../method_channel_notifier.dart'; final _log = Logger('android.oath.state'); diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index a9055ad7..53032a8c 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -24,7 +24,7 @@ import '../app/state.dart'; import 'state.dart'; import 'views/nfc/models.dart'; import 'views/nfc/nfc_activity_overlay.dart'; -import 'views/nfc/nfc_progress_bar.dart'; +import 'views/nfc/nfc_content_widget.dart'; const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); @@ -52,12 +52,9 @@ class _DialogProvider extends Notifier { final properties = ref.read(nfcViewNotifier); - debugPrint('XXX now it is: $current'); switch (current) { case NfcActivity.processingStarted: processingTimer?.cancel(); - - debugPrint('XXX explicit action: $explicitAction'); final timeout = explicitAction ? 300 : 200; processingTimer = Timer(Duration(milliseconds: timeout), () { @@ -65,7 +62,7 @@ class _DialogProvider extends Notifier { // show the widget notifier.sendCommand(NfcEventCommand( event: NfcShowViewEvent( - child: _NfcActivityWidgetView( + child: NfcContentWidget( title: properties.operationProcessing, subtitle: '', inProgress: true, @@ -74,7 +71,7 @@ class _DialogProvider extends Notifier { // the processing view will only be shown if the timer is still active notifier.sendCommand(NfcEventCommand( event: NfcUpdateViewEvent( - child: _NfcActivityWidgetView( + child: NfcContentWidget( title: properties.operationProcessing, subtitle: l10n.s_nfc_hold_key, inProgress: true, @@ -90,7 +87,7 @@ class _DialogProvider extends Notifier { event: NfcUpdateViewEvent( child: NfcActivityClosingCountdownWidgetView( closeInSec: 5, - child: _NfcActivityWidgetView( + child: NfcContentWidget( title: properties.operationSuccess, subtitle: l10n.s_nfc_remove_key, inProgress: false, @@ -106,7 +103,7 @@ class _DialogProvider extends Notifier { explicitAction = false; // next action might not be explicit notifier.sendCommand(NfcEventCommand( event: NfcUpdateViewEvent( - child: _NfcActivityWidgetView( + child: NfcContentWidget( title: properties.operationFailure, inProgress: false, )))); @@ -127,7 +124,7 @@ class _DialogProvider extends Notifier { explicitAction = true; notifier.sendCommand(NfcEventCommand( event: NfcShowViewEvent( - child: _NfcActivityWidgetView( + child: NfcContentWidget( title: l10n.s_nfc_tap_for( properties.operationName ?? '[OPERATION NAME MISSING]'), subtitle: '', @@ -173,37 +170,6 @@ class _DialogProvider extends Notifier { } } -class _NfcActivityWidgetView extends StatelessWidget { - final bool inProgress; - final String? title; - final String? subtitle; - - const _NfcActivityWidgetView( - {required this.title, this.subtitle, this.inProgress = false}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Column( - children: [ - Text(title ?? 'Missing title', - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleLarge), - const SizedBox(height: 8), - if (subtitle != null) - Text(subtitle!, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleSmall), - const SizedBox(height: 32), - NfcIconProgressBar(inProgress), - const SizedBox(height: 24) - ], - ), - ); - } -} - class MethodChannelHelper { final ProviderRef _ref; final MethodChannel _channel; @@ -230,27 +196,3 @@ class MethodChannelHelper { return result; } } - -class MethodChannelNotifier extends Notifier { - final MethodChannel _channel; - - MethodChannelNotifier(this._channel); - - @override - void build() {} - - Future invoke(String name, - [Map params = const {}]) async { - final notifier = ref.read(nfcViewNotifier.notifier); - notifier.setDialogProperties( - operationName: params['operationName'], - operationProcessing: params['operationProcessing'], - operationSuccess: params['operationSuccess'], - operationFailure: params['operationFailure'], - showSuccess: params['showSuccess']); - - final result = await _channel.invokeMethod(name, params['callArgs']); - await ref.read(androidDialogProvider.notifier).waitForDialogClosed(); - return result; - } -} diff --git a/lib/android/views/nfc/nfc_content_widget.dart b/lib/android/views/nfc/nfc_content_widget.dart new file mode 100644 index 00000000..37fe9667 --- /dev/null +++ b/lib/android/views/nfc/nfc_content_widget.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +import 'nfc_progress_bar.dart'; + +class NfcContentWidget extends StatelessWidget { + final bool inProgress; + final String? title; + final String? subtitle; + + const NfcContentWidget( + {super.key, required this.title, this.subtitle, this.inProgress = false}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + children: [ + Text(title ?? 'Missing title', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 8), + if (subtitle != null) + Text(subtitle!, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleSmall), + const SizedBox(height: 32), + NfcIconProgressBar(inProgress), + const SizedBox(height: 24) + ], + ), + ); + } +} From dd1c52fbd94da07f46ef1c238f24bac98a49f794 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 30 Aug 2024 14:16:34 +0200 Subject: [PATCH 16/50] add helper methods for commands --- lib/android/tap_request_dialog.dart | 37 ++++++++++------------------- lib/android/views/nfc/models.dart | 9 +++++++ 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 53032a8c..50e7c170 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -60,22 +60,18 @@ class _DialogProvider extends Notifier { processingTimer = Timer(Duration(milliseconds: timeout), () { if (!explicitAction) { // show the widget - notifier.sendCommand(NfcEventCommand( - event: NfcShowViewEvent( - child: NfcContentWidget( + notifier.sendCommand(showNfcView(NfcContentWidget( title: properties.operationProcessing, subtitle: '', inProgress: true, - )))); + ))); } else { // the processing view will only be shown if the timer is still active - notifier.sendCommand(NfcEventCommand( - event: NfcUpdateViewEvent( - child: NfcContentWidget( + notifier.sendCommand(updateNfcView(NfcContentWidget( title: properties.operationProcessing, subtitle: l10n.s_nfc_hold_key, inProgress: true, - )))); + ))); } }); break; @@ -83,30 +79,26 @@ class _DialogProvider extends Notifier { explicitAction = false; // next action might not be explicit processingTimer?.cancel(); if (properties.showSuccess ?? false) { - notifier.sendCommand(NfcEventCommand( - event: NfcUpdateViewEvent( - child: NfcActivityClosingCountdownWidgetView( + notifier.sendCommand( + updateNfcView(NfcActivityClosingCountdownWidgetView( closeInSec: 5, child: NfcContentWidget( title: properties.operationSuccess, subtitle: l10n.s_nfc_remove_key, inProgress: false, ), - )))); + ))); } else { // directly hide - notifier.sendCommand( - NfcEventCommand(event: const NfcHideViewEvent(timeoutMs: 0))); + notifier.sendCommand(hideNfcView); } break; case NfcActivity.processingInterrupted: explicitAction = false; // next action might not be explicit - notifier.sendCommand(NfcEventCommand( - event: NfcUpdateViewEvent( - child: NfcContentWidget( + notifier.sendCommand(updateNfcView(NfcContentWidget( title: properties.operationFailure, inProgress: false, - )))); + ))); break; case NfcActivity.notActive: debugPrint('Received not handled notActive'); @@ -122,19 +114,16 @@ class _DialogProvider extends Notifier { switch (call.method) { case 'show': explicitAction = true; - notifier.sendCommand(NfcEventCommand( - event: NfcShowViewEvent( - child: NfcContentWidget( + notifier.sendCommand(showNfcView(NfcContentWidget( title: l10n.s_nfc_tap_for( properties.operationName ?? '[OPERATION NAME MISSING]'), subtitle: '', inProgress: false, - )))); + ))); break; case 'close': - notifier.sendCommand( - NfcEventCommand(event: const NfcHideViewEvent(timeoutMs: 0))); + notifier.sendCommand(hideNfcView); break; default: diff --git a/lib/android/views/nfc/models.dart b/lib/android/views/nfc/models.dart index bc917f95..b1a82284 100644 --- a/lib/android/views/nfc/models.dart +++ b/lib/android/views/nfc/models.dart @@ -64,3 +64,12 @@ class NfcEventCommand with _$NfcEventCommand { @Default(NfcEvent()) NfcEvent event, }) = _NfcEventCommand; } + +final hideNfcView = + NfcEventCommand(event: const NfcHideViewEvent(timeoutMs: 0)); + +NfcEventCommand updateNfcView(Widget child) => + NfcEventCommand(event: NfcUpdateViewEvent(child: child)); + +NfcEventCommand showNfcView(Widget child) => + NfcEventCommand(event: NfcShowViewEvent(child: child)); From f99b0b14eec70d202b729c93ddd2674e348a2e65 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 30 Aug 2024 15:00:12 +0200 Subject: [PATCH 17/50] update dialog titles --- lib/android/fido/state.dart | 2 +- lib/android/oath/state.dart | 21 +++++++------- lib/android/tap_request_dialog.dart | 5 ++-- .../views/nfc/nfc_activity_overlay.dart | 29 +++++++++---------- lib/android/views/nfc/nfc_content_widget.dart | 2 +- lib/l10n/app_de.arb | 3 +- lib/l10n/app_en.arb | 3 +- lib/l10n/app_fr.arb | 3 +- lib/l10n/app_ja.arb | 3 +- lib/l10n/app_pl.arb | 3 +- 10 files changed, 37 insertions(+), 37 deletions(-) diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index d0229a67..c06fdf4f 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -421,7 +421,7 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { Future unlock(String pin) async => invoke('unlock', { 'callArgs': {'pin': pin}, - 'operationName': l10n.c_nfc_unlock, + 'operationName': l10n.s_unlock, 'operationProcessing': l10n.s_nfc_unlock_processing, 'operationSuccess': l10n.s_nfc_unlock_success, 'operationFailure': l10n.s_nfc_unlock_failure, diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index 6e6dcaa8..068cd7f2 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -338,7 +338,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future unlock(String password, {bool remember = false}) async => invoke('unlock', { 'callArgs': {'password': password, 'remember': remember}, - 'operationName': l10n.c_nfc_unlock, + 'operationName': l10n.s_unlock, 'operationProcessing': l10n.s_nfc_unlock_processing, 'operationSuccess': l10n.s_nfc_unlock_success, 'operationFailure': l10n.s_nfc_unlock_failure, @@ -347,9 +347,8 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future setPassword(String? current, String password) async => invoke('setPassword', { 'callArgs': {'current': current, 'password': password}, - 'operationName': current != null - ? l10n.c_nfc_oath_change_password - : l10n.c_nfc_oath_set_password, + 'operationName': + current != null ? l10n.s_change_password : l10n.s_set_password, 'operationProcessing': current != null ? l10n.s_nfc_oath_change_password_processing : l10n.s_nfc_oath_set_password_processing, @@ -364,7 +363,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future unsetPassword(String current) async => invoke('unsetPassword', { 'callArgs': {'current': current}, - 'operationName': l10n.c_nfc_oath_remove_password, + 'operationName': l10n.s_remove_password, 'operationProcessing': l10n.s_nfc_oath_remove_password_processing, 'operationSuccess': l10n.s_password_removed, 'operationFailure': l10n.s_nfc_oath_remove_password_failure, @@ -375,7 +374,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future calculate(OathCredential credential) async => invoke('calculate', { 'callArgs': {'credentialId': credential.id}, - 'operationName': l10n.c_nfc_oath_calculate_code, + 'operationName': l10n.s_nfc_oath_calculate_code, 'operationProcessing': l10n.s_nfc_oath_calculate_code_processing, 'operationSuccess': l10n.s_nfc_oath_calculate_code_success, 'operationFailure': l10n.s_nfc_oath_calculate_code_failure, @@ -388,7 +387,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uri': credentialUri.toString(), 'requireTouch': requireTouch }, - 'operationName': l10n.c_nfc_oath_add_account, + 'operationName': l10n.s_add_account, 'operationProcessing': l10n.s_nfc_oath_add_account_processing, 'operationSuccess': l10n.s_account_added, 'operationFailure': l10n.s_nfc_oath_add_account_failure, @@ -402,7 +401,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uris': credentialUris, 'requireTouch': touchRequired, }, - 'operationName': l10n.c_nfc_oath_add_multiple_accounts, + 'operationName': l10n.s_add_accounts, 'operationProcessing': l10n.s_nfc_oath_add_multiple_accounts_processing, 'operationSuccess': l10n.s_nfc_oath_add_multiple_accounts_success, 'operationFailure': l10n.s_nfc_oath_add_multiple_accounts_failure, @@ -415,7 +414,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uri': credentialUri.toString(), 'requireTouch': requireTouch }, - 'operationName': l10n.c_nfc_oath_add_account, + 'operationName': l10n.s_add_account, 'operationProcessing': l10n.s_nfc_oath_add_account_processing, 'operationSuccess': l10n.s_account_added, 'operationFailure': l10n.s_nfc_oath_add_account_failure, @@ -424,7 +423,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future deleteAccount(OathCredential credential) async => invoke('deleteAccount', { 'callArgs': {'credentialId': credential.id}, - 'operationName': l10n.c_nfc_oath_delete_account, + 'operationName': l10n.s_delete_account, 'operationProcessing': l10n.s_nfc_oath_delete_account_processing, 'operationSuccess': l10n.s_account_deleted, 'operationFailure': l10n.s_nfc_oath_delete_account_failure, @@ -439,7 +438,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'name': name, 'issuer': issuer }, - 'operationName': l10n.c_nfc_oath_rename_account, + 'operationName': l10n.s_rename_account, 'operationProcessing': l10n.s_nfc_oath_rename_account_processing, 'operationSuccess': l10n.s_account_renamed, 'operationFailure': l10n.s_nfc_oath_rename_account_failure, diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 50e7c170..08bacc9e 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -115,9 +115,8 @@ class _DialogProvider extends Notifier { case 'show': explicitAction = true; notifier.sendCommand(showNfcView(NfcContentWidget( - title: l10n.s_nfc_tap_for( - properties.operationName ?? '[OPERATION NAME MISSING]'), - subtitle: '', + title: properties.operationName ?? '[OPERATION NAME MISSING]', + subtitle: 'Scan your YubiKey', inProgress: false, ))); break; diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/views/nfc/nfc_activity_overlay.dart index f9e0c2b8..6871e779 100644 --- a/lib/android/views/nfc/nfc_activity_overlay.dart +++ b/lib/android/views/nfc/nfc_activity_overlay.dart @@ -145,23 +145,20 @@ class NfcBottomSheet extends ConsumerWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (showCloseButton) const SizedBox(height: 8), - if (showCloseButton) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - IconButton( - onPressed: () => Navigator.of(context).pop(), - icon: const Icon(Symbols.close, fill: 1, size: 24)) - ], + Stack(fit: StackFit.passthrough, children: [ + if (showCloseButton) + Positioned( + top: 8, + right: 8, + child: IconButton( + onPressed: () => Navigator.of(context).pop(), + icon: const Icon(Symbols.close, fill: 1, size: 24)), ), - ), - if (showCloseButton) const SizedBox(height: 16), - if (!showCloseButton) const SizedBox(height: 48), - widget, + Padding( + padding: const EdgeInsets.fromLTRB(0, 40, 0, 0), + child: widget, + ) + ]), const SizedBox(height: 32), ], ); diff --git a/lib/android/views/nfc/nfc_content_widget.dart b/lib/android/views/nfc/nfc_content_widget.dart index 37fe9667..6822134a 100644 --- a/lib/android/views/nfc/nfc_content_widget.dart +++ b/lib/android/views/nfc/nfc_content_widget.dart @@ -23,7 +23,7 @@ class NfcContentWidget extends StatelessWidget { if (subtitle != null) Text(subtitle!, textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleSmall), + style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 32), NfcIconProgressBar(inProgress), const SizedBox(height: 24) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index b34fb4fb..b068a034 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -359,6 +359,7 @@ "s_password": "Passwort", "s_manage_password": "Passwort verwalten", "s_set_password": "Passwort setzen", + "s_change_password": null, "s_password_set": "Passwort gesetzt", "s_show_password": "Passwort anzeigen", "s_hide_password": "Passwort verstekcen", @@ -917,7 +918,7 @@ "s_nfc_oath_delete_account_processing": null, "s_nfc_oath_delete_account_failure": null, - "c_nfc_oath_calculate_code": null, + "s_nfc_oath_calculate_code": null, "s_nfc_oath_calculate_code_processing": null, "s_nfc_oath_calculate_code_success": null, "s_nfc_oath_calculate_code_failure": null, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ffdabb09..fe15677d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -359,6 +359,7 @@ "s_password": "Password", "s_manage_password": "Manage password", "s_set_password": "Set password", + "s_change_password": "Change password", "s_password_set": "Password set", "s_show_password": "Show password", "s_hide_password": "Hide password", @@ -917,7 +918,7 @@ "s_nfc_oath_delete_account_processing": "Deleting account", "s_nfc_oath_delete_account_failure": "Failed to delete account", - "c_nfc_oath_calculate_code": "calculate code", + "s_nfc_oath_calculate_code": "Calculate code", "s_nfc_oath_calculate_code_processing": "Calculating", "s_nfc_oath_calculate_code_success": "Code calculated", "s_nfc_oath_calculate_code_failure": "Failed to calculate code", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 6077ec1a..9ffd11ba 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -359,6 +359,7 @@ "s_password": "Mot de passe", "s_manage_password": "Gérer mot de passe", "s_set_password": "Créer mot de passe", + "s_change_password": null, "s_password_set": "Mot de passe créé", "s_show_password": "Montrer mot de passe", "s_hide_password": "Masquer mot de passe", @@ -917,7 +918,7 @@ "s_nfc_oath_delete_account_processing": null, "s_nfc_oath_delete_account_failure": null, - "c_nfc_oath_calculate_code": null, + "s_nfc_oath_calculate_code": null, "s_nfc_oath_calculate_code_processing": null, "s_nfc_oath_calculate_code_success": null, "s_nfc_oath_calculate_code_failure": null, diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index d08331c1..237228e4 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -359,6 +359,7 @@ "s_password": "パスワード", "s_manage_password": "パスワードを管理", "s_set_password": "パスワードを設定", + "s_change_password": null, "s_password_set": "パスワードが設定されました", "s_show_password": "パスワードを表示", "s_hide_password": "パスワードを非表示", @@ -917,7 +918,7 @@ "s_nfc_oath_delete_account_processing": null, "s_nfc_oath_delete_account_failure": null, - "c_nfc_oath_calculate_code": null, + "s_nfc_oath_calculate_code": null, "s_nfc_oath_calculate_code_processing": null, "s_nfc_oath_calculate_code_success": null, "s_nfc_oath_calculate_code_failure": null, diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 2e3d3e99..cda0ae11 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -359,6 +359,7 @@ "s_password": "Hasło", "s_manage_password": "Zarządzaj hasłem", "s_set_password": "Ustaw hasło", + "s_change_password": null, "s_password_set": "Hasło zostało ustawione", "s_show_password": "Pokaż hasło", "s_hide_password": "Ukryj hasło", @@ -917,7 +918,7 @@ "s_nfc_oath_delete_account_processing": null, "s_nfc_oath_delete_account_failure": null, - "c_nfc_oath_calculate_code": null, + "s_nfc_oath_calculate_code": null, "s_nfc_oath_calculate_code_processing": null, "s_nfc_oath_calculate_code_success": null, "s_nfc_oath_calculate_code_failure": null, From f50093276000b47f19f1ea9d02614e6323705f30 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 30 Aug 2024 15:40:29 +0200 Subject: [PATCH 18/50] provide icon widget to nfc content vidget --- lib/android/tap_request_dialog.dart | 22 ++++++++++-------- lib/android/views/nfc/nfc_content_widget.dart | 23 ++++++++++--------- lib/l10n/app_de.arb | 3 +++ lib/l10n/app_en.arb | 3 +++ lib/l10n/app_fr.arb | 3 +++ lib/l10n/app_ja.arb | 3 +++ lib/l10n/app_pl.arb | 3 +++ 7 files changed, 40 insertions(+), 20 deletions(-) diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 08bacc9e..65c03abf 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -19,12 +19,14 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:material_symbols_icons/symbols.dart'; import '../app/state.dart'; import 'state.dart'; import 'views/nfc/models.dart'; import 'views/nfc/nfc_activity_overlay.dart'; import 'views/nfc/nfc_content_widget.dart'; +import 'views/nfc/nfc_progress_bar.dart'; const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); @@ -61,16 +63,15 @@ class _DialogProvider extends Notifier { if (!explicitAction) { // show the widget notifier.sendCommand(showNfcView(NfcContentWidget( - title: properties.operationProcessing, - subtitle: '', - inProgress: true, + title: properties.operationName, + icon: const NfcIconProgressBar(true), ))); } else { // the processing view will only be shown if the timer is still active notifier.sendCommand(updateNfcView(NfcContentWidget( title: properties.operationProcessing, subtitle: l10n.s_nfc_hold_key, - inProgress: true, + icon: const NfcIconProgressBar(true), ))); } }); @@ -85,7 +86,10 @@ class _DialogProvider extends Notifier { child: NfcContentWidget( title: properties.operationSuccess, subtitle: l10n.s_nfc_remove_key, - inProgress: false, + icon: const Icon( + Symbols.check, + size: 64, + ), ), ))); } else { @@ -97,7 +101,7 @@ class _DialogProvider extends Notifier { explicitAction = false; // next action might not be explicit notifier.sendCommand(updateNfcView(NfcContentWidget( title: properties.operationFailure, - inProgress: false, + icon: const NfcIconProgressBar(false), ))); break; case NfcActivity.notActive: @@ -115,9 +119,9 @@ class _DialogProvider extends Notifier { case 'show': explicitAction = true; notifier.sendCommand(showNfcView(NfcContentWidget( - title: properties.operationName ?? '[OPERATION NAME MISSING]', - subtitle: 'Scan your YubiKey', - inProgress: false, + title: properties.operationName, + subtitle: l10n.s_nfc_scan_yubikey, + icon: const NfcIconProgressBar(false), ))); break; diff --git a/lib/android/views/nfc/nfc_content_widget.dart b/lib/android/views/nfc/nfc_content_widget.dart index 6822134a..95106e7f 100644 --- a/lib/android/views/nfc/nfc_content_widget.dart +++ b/lib/android/views/nfc/nfc_content_widget.dart @@ -1,31 +1,32 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'nfc_progress_bar.dart'; +import '../../../app/state.dart'; -class NfcContentWidget extends StatelessWidget { - final bool inProgress; +class NfcContentWidget extends ConsumerWidget { final String? title; final String? subtitle; + final Widget icon; const NfcContentWidget( - {super.key, required this.title, this.subtitle, this.inProgress = false}); + {super.key, required this.title, this.subtitle, required this.icon}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final l10n = ref.watch(l10nProvider); return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Column( children: [ - Text(title ?? 'Missing title', + Text(title ?? l10n.s_nfc_ready_to_scan, textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 8), - if (subtitle != null) - Text(subtitle!, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium), + Text(subtitle ?? '', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 32), - NfcIconProgressBar(inProgress), + icon, const SizedBox(height: 24) ], ), diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index b068a034..6b07a5ce 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -961,6 +961,9 @@ "s_nfc_hold_key": null, "s_nfc_remove_key": null, + "s_nfc_ready_to_scan": null, + "s_nfc_scan_yubikey": null, + "c_nfc_unlock": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index fe15677d..1012193a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -961,6 +961,9 @@ "s_nfc_hold_key": "Hold YubiKey", "s_nfc_remove_key": "You can remove YubiKey", + "s_nfc_ready_to_scan": "Ready to scan", + "s_nfc_scan_yubikey": "Scan your YubiKey", + "c_nfc_unlock": "unlock", "s_nfc_unlock_processing": "Unlocking", "s_nfc_unlock_success": "Accounts unlocked", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 9ffd11ba..d62e16f0 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -961,6 +961,9 @@ "s_nfc_hold_key": null, "s_nfc_remove_key": null, + "s_nfc_ready_to_scan": null, + "s_nfc_scan_yubikey": null, + "c_nfc_unlock": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 237228e4..c1b66ff4 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -961,6 +961,9 @@ "s_nfc_hold_key": null, "s_nfc_remove_key": null, + "s_nfc_ready_to_scan": null, + "s_nfc_scan_yubikey": null, + "c_nfc_unlock": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index cda0ae11..5079b7a3 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -961,6 +961,9 @@ "s_nfc_hold_key": null, "s_nfc_remove_key": null, + "s_nfc_ready_to_scan": null, + "s_nfc_scan_yubikey": null, + "c_nfc_unlock": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, From c3b0c7999175aae6c10805924edb392daa0ef5d0 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 30 Aug 2024 16:15:00 +0200 Subject: [PATCH 19/50] unify dialog title, add success icon --- lib/android/fido/state.dart | 12 ---- lib/android/method_channel_notifier.dart | 2 - lib/android/oath/state.dart | 23 -------- lib/android/tap_request_dialog.dart | 21 ++----- lib/android/views/nfc/models.dart | 2 - lib/android/views/nfc/models.freezed.dart | 56 +------------------ .../views/nfc/nfc_activity_overlay.dart | 8 +-- lib/android/views/nfc/nfc_content_widget.dart | 2 +- lib/android/views/nfc/nfc_success_icon.dart | 29 ++++++++++ lib/l10n/app_de.arb | 29 +--------- lib/l10n/app_en.arb | 29 +--------- lib/l10n/app_fr.arb | 29 +--------- lib/l10n/app_ja.arb | 29 +--------- lib/l10n/app_pl.arb | 29 +--------- 14 files changed, 44 insertions(+), 256 deletions(-) create mode 100644 lib/android/views/nfc/nfc_success_icon.dart diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index c06fdf4f..8e6cd216 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -384,8 +384,6 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { 'rpId': credential.rpId, 'credentialId': credential.credentialId }, - 'operationName': l10n.c_nfc_fido_delete_passkey, - 'operationProcessing': l10n.s_nfc_fido_delete_passkey_processing, 'operationSuccess': l10n.s_passkey_deleted, 'operationFailure': l10n.s_nfc_fido_delete_passkey_failure, 'showSuccess': true @@ -394,8 +392,6 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { Future cancelReset() async => invoke('cancelReset'); Future reset() async => invoke('reset', { - 'operationName': l10n.c_nfc_fido_reset, - 'operationProcessing': l10n.s_nfc_fido_reset_processing, 'operationSuccess': l10n.s_nfc_fido_reset_success, 'operationFailure': l10n.s_nfc_fido_reset_failure, 'showSuccess': true @@ -404,12 +400,6 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { Future setPin(String newPin, {String? oldPin}) async => invoke('setPin', { 'callArgs': {'pin': oldPin, 'newPin': newPin}, - 'operationName': oldPin != null - ? l10n.c_nfc_fido_change_pin - : l10n.c_nfc_fido_set_pin, - 'operationProcessing': oldPin != null - ? l10n.s_nfc_fido_change_pin_processing - : l10n.s_nfc_fido_set_pin_processing, 'operationSuccess': oldPin != null ? l10n.s_nfc_fido_change_pin_success : l10n.s_pin_set, @@ -421,8 +411,6 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { Future unlock(String pin) async => invoke('unlock', { 'callArgs': {'pin': pin}, - 'operationName': l10n.s_unlock, - 'operationProcessing': l10n.s_nfc_unlock_processing, 'operationSuccess': l10n.s_nfc_unlock_success, 'operationFailure': l10n.s_nfc_unlock_failure, 'showSuccess': true diff --git a/lib/android/method_channel_notifier.dart b/lib/android/method_channel_notifier.dart index dd8d2327..ea07220b 100644 --- a/lib/android/method_channel_notifier.dart +++ b/lib/android/method_channel_notifier.dart @@ -32,8 +32,6 @@ class MethodChannelNotifier extends Notifier { [Map params = const {}]) async { final notifier = ref.read(nfcViewNotifier.notifier); notifier.setDialogProperties( - operationName: params['operationName'], - operationProcessing: params['operationProcessing'], operationSuccess: params['operationSuccess'], operationFailure: params['operationFailure'], showSuccess: params['showSuccess']); diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index 068cd7f2..b40f634f 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -329,8 +329,6 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { void build() {} Future reset() async => invoke('reset', { - 'operationName': l10n.c_nfc_oath_reset, - 'operationProcessing': l10n.s_nfc_oath_reset_processing, 'operationSuccess': l10n.s_nfc_oath_reset_success, 'operationFailure': l10n.s_nfc_oath_reset_failure }); @@ -338,8 +336,6 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future unlock(String password, {bool remember = false}) async => invoke('unlock', { 'callArgs': {'password': password, 'remember': remember}, - 'operationName': l10n.s_unlock, - 'operationProcessing': l10n.s_nfc_unlock_processing, 'operationSuccess': l10n.s_nfc_unlock_success, 'operationFailure': l10n.s_nfc_unlock_failure, }); @@ -347,11 +343,6 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future setPassword(String? current, String password) async => invoke('setPassword', { 'callArgs': {'current': current, 'password': password}, - 'operationName': - current != null ? l10n.s_change_password : l10n.s_set_password, - 'operationProcessing': current != null - ? l10n.s_nfc_oath_change_password_processing - : l10n.s_nfc_oath_set_password_processing, 'operationSuccess': current != null ? l10n.s_nfc_oath_change_password_success : l10n.s_password_set, @@ -363,8 +354,6 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future unsetPassword(String current) async => invoke('unsetPassword', { 'callArgs': {'current': current}, - 'operationName': l10n.s_remove_password, - 'operationProcessing': l10n.s_nfc_oath_remove_password_processing, 'operationSuccess': l10n.s_password_removed, 'operationFailure': l10n.s_nfc_oath_remove_password_failure, }); @@ -374,8 +363,6 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future calculate(OathCredential credential) async => invoke('calculate', { 'callArgs': {'credentialId': credential.id}, - 'operationName': l10n.s_nfc_oath_calculate_code, - 'operationProcessing': l10n.s_nfc_oath_calculate_code_processing, 'operationSuccess': l10n.s_nfc_oath_calculate_code_success, 'operationFailure': l10n.s_nfc_oath_calculate_code_failure, }); @@ -387,8 +374,6 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uri': credentialUri.toString(), 'requireTouch': requireTouch }, - 'operationName': l10n.s_add_account, - 'operationProcessing': l10n.s_nfc_oath_add_account_processing, 'operationSuccess': l10n.s_account_added, 'operationFailure': l10n.s_nfc_oath_add_account_failure, 'showSuccess': true @@ -401,8 +386,6 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uris': credentialUris, 'requireTouch': touchRequired, }, - 'operationName': l10n.s_add_accounts, - 'operationProcessing': l10n.s_nfc_oath_add_multiple_accounts_processing, 'operationSuccess': l10n.s_nfc_oath_add_multiple_accounts_success, 'operationFailure': l10n.s_nfc_oath_add_multiple_accounts_failure, }); @@ -414,8 +397,6 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uri': credentialUri.toString(), 'requireTouch': requireTouch }, - 'operationName': l10n.s_add_account, - 'operationProcessing': l10n.s_nfc_oath_add_account_processing, 'operationSuccess': l10n.s_account_added, 'operationFailure': l10n.s_nfc_oath_add_account_failure, }); @@ -423,8 +404,6 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future deleteAccount(OathCredential credential) async => invoke('deleteAccount', { 'callArgs': {'credentialId': credential.id}, - 'operationName': l10n.s_delete_account, - 'operationProcessing': l10n.s_nfc_oath_delete_account_processing, 'operationSuccess': l10n.s_account_deleted, 'operationFailure': l10n.s_nfc_oath_delete_account_failure, 'showSuccess': true @@ -438,8 +417,6 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'name': name, 'issuer': issuer }, - 'operationName': l10n.s_rename_account, - 'operationProcessing': l10n.s_nfc_oath_rename_account_processing, 'operationSuccess': l10n.s_account_renamed, 'operationFailure': l10n.s_nfc_oath_rename_account_failure, }); diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 65c03abf..198713bd 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -19,7 +19,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:material_symbols_icons/symbols.dart'; import '../app/state.dart'; import 'state.dart'; @@ -27,6 +26,7 @@ import 'views/nfc/models.dart'; import 'views/nfc/nfc_activity_overlay.dart'; import 'views/nfc/nfc_content_widget.dart'; import 'views/nfc/nfc_progress_bar.dart'; +import 'views/nfc/nfc_success_icon.dart'; const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); @@ -46,7 +46,6 @@ class _DialogProvider extends Notifier { if (!explicitAction) { // setup properties for ad-hoc action ref.read(nfcViewNotifier.notifier).setDialogProperties( - operationProcessing: l10n.s_nfc_read_key, operationFailure: l10n.l_nfc_read_key_failure, showSuccess: false, ); @@ -63,14 +62,13 @@ class _DialogProvider extends Notifier { if (!explicitAction) { // show the widget notifier.sendCommand(showNfcView(NfcContentWidget( - title: properties.operationName, + title: l10n.s_nfc_accessing_yubikey, icon: const NfcIconProgressBar(true), ))); } else { // the processing view will only be shown if the timer is still active notifier.sendCommand(updateNfcView(NfcContentWidget( - title: properties.operationProcessing, - subtitle: l10n.s_nfc_hold_key, + title: l10n.s_nfc_accessing_yubikey, icon: const NfcIconProgressBar(true), ))); } @@ -86,10 +84,7 @@ class _DialogProvider extends Notifier { child: NfcContentWidget( title: properties.operationSuccess, subtitle: l10n.s_nfc_remove_key, - icon: const Icon( - Symbols.check, - size: 64, - ), + icon: const NfcIconSuccess(), ), ))); } else { @@ -114,12 +109,10 @@ class _DialogProvider extends Notifier { _channel.setMethodCallHandler((call) async { final notifier = ref.read(nfcEventCommandNotifier.notifier); - final properties = ref.read(nfcViewNotifier); switch (call.method) { case 'show': explicitAction = true; notifier.sendCommand(showNfcView(NfcContentWidget( - title: properties.operationName, subtitle: l10n.s_nfc_scan_yubikey, icon: const NfcIconProgressBar(false), ))); @@ -169,16 +162,12 @@ class MethodChannelHelper { const MethodChannelHelper(this._ref, this._channel); Future invoke(String method, - {String? operationName, - String? operationSuccess, - String? operationProcessing, + {String? operationSuccess, String? operationFailure, bool? showSuccess, Map arguments = const {}}) async { final notifier = _ref.read(nfcViewNotifier.notifier); notifier.setDialogProperties( - operationName: operationName, - operationProcessing: operationProcessing, operationSuccess: operationSuccess, operationFailure: operationFailure, showSuccess: showSuccess); diff --git a/lib/android/views/nfc/models.dart b/lib/android/views/nfc/models.dart index b1a82284..e9722106 100644 --- a/lib/android/views/nfc/models.dart +++ b/lib/android/views/nfc/models.dart @@ -52,8 +52,6 @@ class NfcView with _$NfcView { required Widget child, bool? showCloseButton, bool? showSuccess, - String? operationName, - String? operationProcessing, String? operationSuccess, String? operationFailure}) = _NfcView; } diff --git a/lib/android/views/nfc/models.freezed.dart b/lib/android/views/nfc/models.freezed.dart index 28fbc7d0..eee13f30 100644 --- a/lib/android/views/nfc/models.freezed.dart +++ b/lib/android/views/nfc/models.freezed.dart @@ -20,8 +20,6 @@ mixin _$NfcView { Widget get child => throw _privateConstructorUsedError; bool? get showCloseButton => throw _privateConstructorUsedError; bool? get showSuccess => throw _privateConstructorUsedError; - String? get operationName => throw _privateConstructorUsedError; - String? get operationProcessing => throw _privateConstructorUsedError; String? get operationSuccess => throw _privateConstructorUsedError; String? get operationFailure => throw _privateConstructorUsedError; @@ -41,8 +39,6 @@ abstract class $NfcViewCopyWith<$Res> { Widget child, bool? showCloseButton, bool? showSuccess, - String? operationName, - String? operationProcessing, String? operationSuccess, String? operationFailure}); } @@ -66,8 +62,6 @@ class _$NfcViewCopyWithImpl<$Res, $Val extends NfcView> Object? child = null, Object? showCloseButton = freezed, Object? showSuccess = freezed, - Object? operationName = freezed, - Object? operationProcessing = freezed, Object? operationSuccess = freezed, Object? operationFailure = freezed, }) { @@ -88,14 +82,6 @@ class _$NfcViewCopyWithImpl<$Res, $Val extends NfcView> ? _value.showSuccess : showSuccess // ignore: cast_nullable_to_non_nullable as bool?, - operationName: freezed == operationName - ? _value.operationName - : operationName // ignore: cast_nullable_to_non_nullable - as String?, - operationProcessing: freezed == operationProcessing - ? _value.operationProcessing - : operationProcessing // ignore: cast_nullable_to_non_nullable - as String?, operationSuccess: freezed == operationSuccess ? _value.operationSuccess : operationSuccess // ignore: cast_nullable_to_non_nullable @@ -120,8 +106,6 @@ abstract class _$$NfcViewImplCopyWith<$Res> implements $NfcViewCopyWith<$Res> { Widget child, bool? showCloseButton, bool? showSuccess, - String? operationName, - String? operationProcessing, String? operationSuccess, String? operationFailure}); } @@ -143,8 +127,6 @@ class __$$NfcViewImplCopyWithImpl<$Res> Object? child = null, Object? showCloseButton = freezed, Object? showSuccess = freezed, - Object? operationName = freezed, - Object? operationProcessing = freezed, Object? operationSuccess = freezed, Object? operationFailure = freezed, }) { @@ -165,14 +147,6 @@ class __$$NfcViewImplCopyWithImpl<$Res> ? _value.showSuccess : showSuccess // ignore: cast_nullable_to_non_nullable as bool?, - operationName: freezed == operationName - ? _value.operationName - : operationName // ignore: cast_nullable_to_non_nullable - as String?, - operationProcessing: freezed == operationProcessing - ? _value.operationProcessing - : operationProcessing // ignore: cast_nullable_to_non_nullable - as String?, operationSuccess: freezed == operationSuccess ? _value.operationSuccess : operationSuccess // ignore: cast_nullable_to_non_nullable @@ -193,8 +167,6 @@ class _$NfcViewImpl implements _NfcView { required this.child, this.showCloseButton, this.showSuccess, - this.operationName, - this.operationProcessing, this.operationSuccess, this.operationFailure}); @@ -207,17 +179,13 @@ class _$NfcViewImpl implements _NfcView { @override final bool? showSuccess; @override - final String? operationName; - @override - final String? operationProcessing; - @override final String? operationSuccess; @override final String? operationFailure; @override String toString() { - return 'NfcView(isShowing: $isShowing, child: $child, showCloseButton: $showCloseButton, showSuccess: $showSuccess, operationName: $operationName, operationProcessing: $operationProcessing, operationSuccess: $operationSuccess, operationFailure: $operationFailure)'; + return 'NfcView(isShowing: $isShowing, child: $child, showCloseButton: $showCloseButton, showSuccess: $showSuccess, operationSuccess: $operationSuccess, operationFailure: $operationFailure)'; } @override @@ -232,10 +200,6 @@ class _$NfcViewImpl implements _NfcView { other.showCloseButton == showCloseButton) && (identical(other.showSuccess, showSuccess) || other.showSuccess == showSuccess) && - (identical(other.operationName, operationName) || - other.operationName == operationName) && - (identical(other.operationProcessing, operationProcessing) || - other.operationProcessing == operationProcessing) && (identical(other.operationSuccess, operationSuccess) || other.operationSuccess == operationSuccess) && (identical(other.operationFailure, operationFailure) || @@ -243,16 +207,8 @@ class _$NfcViewImpl implements _NfcView { } @override - int get hashCode => Object.hash( - runtimeType, - isShowing, - child, - showCloseButton, - showSuccess, - operationName, - operationProcessing, - operationSuccess, - operationFailure); + int get hashCode => Object.hash(runtimeType, isShowing, child, + showCloseButton, showSuccess, operationSuccess, operationFailure); /// Create a copy of NfcView /// with the given fields replaced by the non-null parameter values. @@ -269,8 +225,6 @@ abstract class _NfcView implements NfcView { required final Widget child, final bool? showCloseButton, final bool? showSuccess, - final String? operationName, - final String? operationProcessing, final String? operationSuccess, final String? operationFailure}) = _$NfcViewImpl; @@ -283,10 +237,6 @@ abstract class _NfcView implements NfcView { @override bool? get showSuccess; @override - String? get operationName; - @override - String? get operationProcessing; - @override String? get operationSuccess; @override String? get operationFailure; diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/views/nfc/nfc_activity_overlay.dart index 6871e779..ee2e1b00 100644 --- a/lib/android/views/nfc/nfc_activity_overlay.dart +++ b/lib/android/views/nfc/nfc_activity_overlay.dart @@ -118,14 +118,8 @@ class _NfcViewNotifier extends Notifier { } void setDialogProperties( - {String? operationName, - String? operationProcessing, - String? operationSuccess, - String? operationFailure, - bool? showSuccess}) { + {String? operationSuccess, String? operationFailure, bool? showSuccess}) { state = state.copyWith( - operationName: operationName, - operationProcessing: operationProcessing, operationSuccess: operationSuccess, operationFailure: operationFailure, showSuccess: showSuccess); diff --git a/lib/android/views/nfc/nfc_content_widget.dart b/lib/android/views/nfc/nfc_content_widget.dart index 95106e7f..4e8fe038 100644 --- a/lib/android/views/nfc/nfc_content_widget.dart +++ b/lib/android/views/nfc/nfc_content_widget.dart @@ -9,7 +9,7 @@ class NfcContentWidget extends ConsumerWidget { final Widget icon; const NfcContentWidget( - {super.key, required this.title, this.subtitle, required this.icon}); + {super.key, this.title, this.subtitle, required this.icon}); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/android/views/nfc/nfc_success_icon.dart b/lib/android/views/nfc/nfc_success_icon.dart new file mode 100644 index 00000000..8b8ce9b1 --- /dev/null +++ b/lib/android/views/nfc/nfc_success_icon.dart @@ -0,0 +1,29 @@ +/* + * 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. + */ + +import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; + +class NfcIconSuccess extends StatelessWidget { + const NfcIconSuccess({super.key}); + + @override + Widget build(BuildContext context) => Icon( + Symbols.check, + size: 64, + color: Theme.of(context).colorScheme.primary, + ); +} diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 6b07a5ce..ce9baeea 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -889,62 +889,36 @@ "s_allow_screenshots": "Bildschirmfotos erlauben", "@_nfc_oath_actions": {}, - "c_nfc_oath_reset": null, - "s_nfc_oath_reset_processing": null, "s_nfc_oath_reset_success": null, "s_nfc_oath_reset_failure": null, - "c_nfc_oath_set_password": null, - "c_nfc_oath_change_password": null, - "s_nfc_oath_set_password_processing": null, - "s_nfc_oath_change_password_processing": null, "s_nfc_oath_change_password_success": null, "s_nfc_oath_set_password_failure": null, "s_nfc_oath_change_password_failure": null, - "c_nfc_oath_remove_password": null, - "s_nfc_oath_remove_password_processing": null, "s_nfc_oath_remove_password_failure": null, - "c_nfc_oath_add_account": null, - "s_nfc_oath_add_account_processing": null, "s_nfc_oath_add_account_failure": null, - "c_nfc_oath_rename_account": null, - "s_nfc_oath_rename_account_processing": null, "s_nfc_oath_rename_account_failure": null, - "c_nfc_oath_delete_account": null, - "s_nfc_oath_delete_account_processing": null, "s_nfc_oath_delete_account_failure": null, - "s_nfc_oath_calculate_code": null, - "s_nfc_oath_calculate_code_processing": null, "s_nfc_oath_calculate_code_success": null, "s_nfc_oath_calculate_code_failure": null, - "c_nfc_oath_add_multiple_accounts": null, - "s_nfc_oath_add_multiple_accounts_processing": null, "s_nfc_oath_add_multiple_accounts_success": null, "s_nfc_oath_add_multiple_accounts_failure": null, "@_nfc_fido_actions": {}, - "c_nfc_fido_reset": null, - "s_nfc_fido_reset_processing": null, "s_nfc_fido_reset_success": null, "s_nfc_fido_reset_failure": null, - "c_nfc_fido_set_pin": null, - "s_nfc_fido_set_pin_processing": null, "s_nfc_fido_set_pin_failure": null, - "c_nfc_fido_change_pin": null, - "s_nfc_fido_change_pin_processing": null, "s_nfc_fido_change_pin_success": null, "s_nfc_fido_change_pin_failure": null, - "c_nfc_fido_delete_passkey": null, - "s_nfc_fido_delete_passkey_processing": null, "s_nfc_fido_delete_passkey_failure": null, "@_nfc_actions": {}, @@ -955,13 +929,12 @@ } }, - "s_nfc_read_key": null, "l_nfc_read_key_failure": null, - "s_nfc_hold_key": null, "s_nfc_remove_key": null, "s_nfc_ready_to_scan": null, + "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, "c_nfc_unlock": null, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1012193a..ff8914a5 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -889,62 +889,36 @@ "s_allow_screenshots": "Allow screenshots", "@_nfc_oath_actions": {}, - "c_nfc_oath_reset": "reset Accounts", - "s_nfc_oath_reset_processing": "Reset in progress", "s_nfc_oath_reset_success": "Accounts reset", "s_nfc_oath_reset_failure": "Failed to reset accounts", - "c_nfc_oath_set_password": "set password", - "c_nfc_oath_change_password": "change password", - "s_nfc_oath_set_password_processing": "Setting password", - "s_nfc_oath_change_password_processing": "Changing password", "s_nfc_oath_change_password_success": "Password changed", "s_nfc_oath_set_password_failure": "Failed to set password", "s_nfc_oath_change_password_failure": "Failed to change password", - "c_nfc_oath_remove_password": "remove password", - "s_nfc_oath_remove_password_processing": "Removing password", "s_nfc_oath_remove_password_failure": "Failed to remove password", - "c_nfc_oath_add_account": "add account", - "s_nfc_oath_add_account_processing": "Adding account", "s_nfc_oath_add_account_failure": "Failed to add account", - "c_nfc_oath_rename_account": "rename account", - "s_nfc_oath_rename_account_processing": "Renaming account", "s_nfc_oath_rename_account_failure": "Failed to rename account", - "c_nfc_oath_delete_account": "delete account", - "s_nfc_oath_delete_account_processing": "Deleting account", "s_nfc_oath_delete_account_failure": "Failed to delete account", - "s_nfc_oath_calculate_code": "Calculate code", - "s_nfc_oath_calculate_code_processing": "Calculating", "s_nfc_oath_calculate_code_success": "Code calculated", "s_nfc_oath_calculate_code_failure": "Failed to calculate code", - "c_nfc_oath_add_multiple_accounts": "add selected accounts", - "s_nfc_oath_add_multiple_accounts_processing": "Adding accounts", "s_nfc_oath_add_multiple_accounts_success": "Accounts added", "s_nfc_oath_add_multiple_accounts_failure": "Failed to add accounts", "@_nfc_fido_actions": {}, - "c_nfc_fido_reset": "reset FIDO application", - "s_nfc_fido_reset_processing": "Resetting FIDO", "s_nfc_fido_reset_success": "FIDO reset", "s_nfc_fido_reset_failure": "FIDO reset failed", - "c_nfc_fido_set_pin": "set PIN", - "s_nfc_fido_set_pin_processing": "Setting PIN", "s_nfc_fido_set_pin_failure": "Failure setting PIN", - "c_nfc_fido_change_pin": "change PIN", - "s_nfc_fido_change_pin_processing": "Changing PIN", "s_nfc_fido_change_pin_success": "PIN changed", "s_nfc_fido_change_pin_failure": "Failure changing PIN", - "c_nfc_fido_delete_passkey": "delete passkey", - "s_nfc_fido_delete_passkey_processing": "Deleting passkey", "s_nfc_fido_delete_passkey_failure": "Failed to delete passkey", "@_nfc_actions": {}, @@ -955,13 +929,12 @@ } }, - "s_nfc_read_key": "Reading YubiKey", "l_nfc_read_key_failure": "Failed to read YubiKey, try again", - "s_nfc_hold_key": "Hold YubiKey", "s_nfc_remove_key": "You can remove YubiKey", "s_nfc_ready_to_scan": "Ready to scan", + "s_nfc_accessing_yubikey": "Accessing YubiKey", "s_nfc_scan_yubikey": "Scan your YubiKey", "c_nfc_unlock": "unlock", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d62e16f0..70b62152 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -889,62 +889,36 @@ "s_allow_screenshots": "Autoriser captures d'écran", "@_nfc_oath_actions": {}, - "c_nfc_oath_reset": null, - "s_nfc_oath_reset_processing": null, "s_nfc_oath_reset_success": null, "s_nfc_oath_reset_failure": null, - "c_nfc_oath_set_password": null, - "c_nfc_oath_change_password": null, - "s_nfc_oath_set_password_processing": null, - "s_nfc_oath_change_password_processing": null, "s_nfc_oath_change_password_success": null, "s_nfc_oath_set_password_failure": null, "s_nfc_oath_change_password_failure": null, - "c_nfc_oath_remove_password": null, - "s_nfc_oath_remove_password_processing": null, "s_nfc_oath_remove_password_failure": null, - "c_nfc_oath_add_account": null, - "s_nfc_oath_add_account_processing": null, "s_nfc_oath_add_account_failure": null, - "c_nfc_oath_rename_account": null, - "s_nfc_oath_rename_account_processing": null, "s_nfc_oath_rename_account_failure": null, - "c_nfc_oath_delete_account": null, - "s_nfc_oath_delete_account_processing": null, "s_nfc_oath_delete_account_failure": null, - "s_nfc_oath_calculate_code": null, - "s_nfc_oath_calculate_code_processing": null, "s_nfc_oath_calculate_code_success": null, "s_nfc_oath_calculate_code_failure": null, - "c_nfc_oath_add_multiple_accounts": null, - "s_nfc_oath_add_multiple_accounts_processing": null, "s_nfc_oath_add_multiple_accounts_success": null, "s_nfc_oath_add_multiple_accounts_failure": null, "@_nfc_fido_actions": {}, - "c_nfc_fido_reset": null, - "s_nfc_fido_reset_processing": null, "s_nfc_fido_reset_success": null, "s_nfc_fido_reset_failure": null, - "c_nfc_fido_set_pin": null, - "s_nfc_fido_set_pin_processing": null, "s_nfc_fido_set_pin_failure": null, - "c_nfc_fido_change_pin": null, - "s_nfc_fido_change_pin_processing": null, "s_nfc_fido_change_pin_success": null, "s_nfc_fido_change_pin_failure": null, - "c_nfc_fido_delete_passkey": null, - "s_nfc_fido_delete_passkey_processing": null, "s_nfc_fido_delete_passkey_failure": null, "@_nfc_actions": {}, @@ -955,13 +929,12 @@ } }, - "s_nfc_read_key": null, "l_nfc_read_key_failure": null, - "s_nfc_hold_key": null, "s_nfc_remove_key": null, "s_nfc_ready_to_scan": null, + "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, "c_nfc_unlock": null, diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index c1b66ff4..27e96407 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -889,62 +889,36 @@ "s_allow_screenshots": "スクリーンショットを許可", "@_nfc_oath_actions": {}, - "c_nfc_oath_reset": null, - "s_nfc_oath_reset_processing": null, "s_nfc_oath_reset_success": null, "s_nfc_oath_reset_failure": null, - "c_nfc_oath_set_password": null, - "c_nfc_oath_change_password": null, - "s_nfc_oath_set_password_processing": null, - "s_nfc_oath_change_password_processing": null, "s_nfc_oath_change_password_success": null, "s_nfc_oath_set_password_failure": null, "s_nfc_oath_change_password_failure": null, - "c_nfc_oath_remove_password": null, - "s_nfc_oath_remove_password_processing": null, "s_nfc_oath_remove_password_failure": null, - "c_nfc_oath_add_account": null, - "s_nfc_oath_add_account_processing": null, "s_nfc_oath_add_account_failure": null, - "c_nfc_oath_rename_account": null, - "s_nfc_oath_rename_account_processing": null, "s_nfc_oath_rename_account_failure": null, - "c_nfc_oath_delete_account": null, - "s_nfc_oath_delete_account_processing": null, "s_nfc_oath_delete_account_failure": null, - "s_nfc_oath_calculate_code": null, - "s_nfc_oath_calculate_code_processing": null, "s_nfc_oath_calculate_code_success": null, "s_nfc_oath_calculate_code_failure": null, - "c_nfc_oath_add_multiple_accounts": null, - "s_nfc_oath_add_multiple_accounts_processing": null, "s_nfc_oath_add_multiple_accounts_success": null, "s_nfc_oath_add_multiple_accounts_failure": null, "@_nfc_fido_actions": {}, - "c_nfc_fido_reset": null, - "s_nfc_fido_reset_processing": null, "s_nfc_fido_reset_success": null, "s_nfc_fido_reset_failure": null, - "c_nfc_fido_set_pin": null, - "s_nfc_fido_set_pin_processing": null, "s_nfc_fido_set_pin_failure": null, - "c_nfc_fido_change_pin": null, - "s_nfc_fido_change_pin_processing": null, "s_nfc_fido_change_pin_success": null, "s_nfc_fido_change_pin_failure": null, - "c_nfc_fido_delete_passkey": null, - "s_nfc_fido_delete_passkey_processing": null, "s_nfc_fido_delete_passkey_failure": null, "@_nfc_actions": {}, @@ -955,13 +929,12 @@ } }, - "s_nfc_read_key": null, "l_nfc_read_key_failure": null, - "s_nfc_hold_key": null, "s_nfc_remove_key": null, "s_nfc_ready_to_scan": null, + "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, "c_nfc_unlock": null, diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 5079b7a3..7ebdaae3 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -889,62 +889,36 @@ "s_allow_screenshots": "Zezwalaj na zrzuty ekranu", "@_nfc_oath_actions": {}, - "c_nfc_oath_reset": null, - "s_nfc_oath_reset_processing": null, "s_nfc_oath_reset_success": null, "s_nfc_oath_reset_failure": null, - "c_nfc_oath_set_password": null, - "c_nfc_oath_change_password": null, - "s_nfc_oath_set_password_processing": null, - "s_nfc_oath_change_password_processing": null, "s_nfc_oath_change_password_success": null, "s_nfc_oath_set_password_failure": null, "s_nfc_oath_change_password_failure": null, - "c_nfc_oath_remove_password": null, - "s_nfc_oath_remove_password_processing": null, "s_nfc_oath_remove_password_failure": null, - "c_nfc_oath_add_account": null, - "s_nfc_oath_add_account_processing": null, "s_nfc_oath_add_account_failure": null, - "c_nfc_oath_rename_account": null, - "s_nfc_oath_rename_account_processing": null, "s_nfc_oath_rename_account_failure": null, - "c_nfc_oath_delete_account": null, - "s_nfc_oath_delete_account_processing": null, "s_nfc_oath_delete_account_failure": null, - "s_nfc_oath_calculate_code": null, - "s_nfc_oath_calculate_code_processing": null, "s_nfc_oath_calculate_code_success": null, "s_nfc_oath_calculate_code_failure": null, - "c_nfc_oath_add_multiple_accounts": null, - "s_nfc_oath_add_multiple_accounts_processing": null, "s_nfc_oath_add_multiple_accounts_success": null, "s_nfc_oath_add_multiple_accounts_failure": null, "@_nfc_fido_actions": {}, - "c_nfc_fido_reset": null, - "s_nfc_fido_reset_processing": null, "s_nfc_fido_reset_success": null, "s_nfc_fido_reset_failure": null, - "c_nfc_fido_set_pin": null, - "s_nfc_fido_set_pin_processing": null, "s_nfc_fido_set_pin_failure": null, - "c_nfc_fido_change_pin": null, - "s_nfc_fido_change_pin_processing": null, "s_nfc_fido_change_pin_success": null, "s_nfc_fido_change_pin_failure": null, - "c_nfc_fido_delete_passkey": null, - "s_nfc_fido_delete_passkey_processing": null, "s_nfc_fido_delete_passkey_failure": null, "@_nfc_actions": {}, @@ -955,13 +929,12 @@ } }, - "s_nfc_read_key": null, "l_nfc_read_key_failure": null, - "s_nfc_hold_key": null, "s_nfc_remove_key": null, "s_nfc_ready_to_scan": null, + "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, "c_nfc_unlock": null, From afaab491b812b59fe35770ddaec460a82e2f9cf1 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 30 Aug 2024 16:59:47 +0200 Subject: [PATCH 20/50] add close button to the bottom sheet --- lib/android/tap_request_dialog.dart | 19 ++++++++++++++----- .../views/nfc/nfc_activity_overlay.dart | 8 ++++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 198713bd..6dc03731 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -40,21 +40,24 @@ class _DialogProvider extends Notifier { @override int build() { final l10n = ref.read(l10nProvider); + final viewNotifier = ref.read(nfcViewNotifier.notifier); + ref.listen(androidNfcActivityProvider, (previous, current) { final notifier = ref.read(nfcEventCommandNotifier.notifier); if (!explicitAction) { // setup properties for ad-hoc action - ref.read(nfcViewNotifier.notifier).setDialogProperties( - operationFailure: l10n.l_nfc_read_key_failure, - showSuccess: false, - ); + viewNotifier.setDialogProperties( + operationFailure: l10n.l_nfc_read_key_failure, + showSuccess: false, + showCloseButton: false); } final properties = ref.read(nfcViewNotifier); switch (current) { case NfcActivity.processingStarted: + viewNotifier.setDialogProperties(showCloseButton: false); processingTimer?.cancel(); final timeout = explicitAction ? 300 : 200; @@ -112,6 +115,10 @@ class _DialogProvider extends Notifier { switch (call.method) { case 'show': explicitAction = true; + + // we want to show the close button + viewNotifier.setDialogProperties(showCloseButton: true); + notifier.sendCommand(showNfcView(NfcContentWidget( subtitle: l10n.s_nfc_scan_yubikey, icon: const NfcIconProgressBar(false), @@ -165,12 +172,14 @@ class MethodChannelHelper { {String? operationSuccess, String? operationFailure, bool? showSuccess, + bool? showCloseButton, Map arguments = const {}}) async { final notifier = _ref.read(nfcViewNotifier.notifier); notifier.setDialogProperties( operationSuccess: operationSuccess, operationFailure: operationFailure, - showSuccess: showSuccess); + showSuccess: showSuccess, + showCloseButton: showCloseButton); final result = await _channel.invokeMethod(method, arguments); await _ref.read(androidDialogProvider.notifier).waitForDialogClosed(); diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/views/nfc/nfc_activity_overlay.dart index ee2e1b00..0c90af3b 100644 --- a/lib/android/views/nfc/nfc_activity_overlay.dart +++ b/lib/android/views/nfc/nfc_activity_overlay.dart @@ -118,11 +118,15 @@ class _NfcViewNotifier extends Notifier { } void setDialogProperties( - {String? operationSuccess, String? operationFailure, bool? showSuccess}) { + {String? operationSuccess, + String? operationFailure, + bool? showSuccess, + bool? showCloseButton}) { state = state.copyWith( operationSuccess: operationSuccess, operationFailure: operationFailure, - showSuccess: showSuccess); + showSuccess: showSuccess, + showCloseButton: showCloseButton); } } From 34f78d251820f4c2a513c6ffdbe2fb3deaf845c0 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Sat, 31 Aug 2024 10:45:33 +0200 Subject: [PATCH 21/50] support for FIDO, conditional messages --- .../com/yubico/authenticator/MainActivity.kt | 8 ++++--- .../authenticator/device/DeviceManager.kt | 20 ++++++++++++++--- .../fido/FidoConnectionHelper.kt | 8 +++---- .../management/ManagementConnectionHelper.kt | 22 +++++++------------ .../management/ManagementHandler.kt | 11 +--------- .../yubico/authenticator/oath/OathManager.kt | 18 +++++---------- lib/android/oath/state.dart | 18 +++++++++++---- lib/android/tap_request_dialog.dart | 5 ++++- .../views/nfc/nfc_activity_overlay.dart | 12 +++++----- lib/app/message.dart | 9 ++++++-- lib/l10n/app_de.arb | 6 +++++ lib/l10n/app_en.arb | 6 +++++ lib/l10n/app_fr.arb | 6 +++++ lib/l10n/app_ja.arb | 6 +++++ lib/l10n/app_pl.arb | 6 +++++ lib/oath/views/rename_account_dialog.dart | 4 ++-- 16 files changed, 102 insertions(+), 63 deletions(-) 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 fe941f4d..8ad116aa 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -352,6 +352,9 @@ class MainActivity : FlutterFragmentActivity() { contextManager?.let { try { + if (device is NfcYubiKeyDevice) { + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_STARTED) + } it.processYubiKey(device) if (device is NfcYubiKeyDevice) { appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_FINISHED) @@ -389,11 +392,11 @@ class MainActivity : FlutterFragmentActivity() { messenger = flutterEngine.dartExecutor.binaryMessenger flutterLog = FlutterLog(messenger) - deviceManager = DeviceManager(this, viewModel) + appMethodChannel = AppMethodChannel(messenger) + deviceManager = DeviceManager(this, viewModel,appMethodChannel) appContext = AppContext(messenger, this.lifecycleScope, viewModel) dialogManager = DialogManager(messenger, this.lifecycleScope) appPreferences = AppPreferences(this) - appMethodChannel = AppMethodChannel(messenger) appLinkMethodChannel = AppLinkMethodChannel(messenger) managementHandler = ManagementHandler(messenger, deviceManager, dialogManager) @@ -441,7 +444,6 @@ class MainActivity : FlutterFragmentActivity() { oathViewModel, dialogManager, appPreferences, - appMethodChannel, nfcActivityListener ) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt index 01e7f04f..e58ae5e7 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt @@ -20,8 +20,10 @@ import androidx.collection.ArraySet import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.Observer +import com.yubico.authenticator.MainActivity import com.yubico.authenticator.MainViewModel import com.yubico.authenticator.OperationContext +import com.yubico.authenticator.yubikit.NfcActivityState import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice import com.yubico.yubikit.core.YubiKeyDevice import com.yubico.yubikit.core.smartcard.scp.ScpKeyParams @@ -41,7 +43,8 @@ interface DeviceListener { class DeviceManager( private val lifecycleOwner: LifecycleOwner, - private val appViewModel: MainViewModel + private val appViewModel: MainViewModel, + private val appMethodChannel: MainActivity.AppMethodChannel ) { var clearDeviceInfoOnDisconnect: Boolean = true @@ -179,8 +182,19 @@ class DeviceManager( onUsb(it) } - suspend fun withKey(onNfc: suspend () -> T, onUsb: suspend (UsbYubiKeyDevice) -> T) = + suspend fun withKey( + onNfc: suspend () -> com.yubico.yubikit.core.util.Result, + onUsb: suspend (UsbYubiKeyDevice) -> T + ): T = appViewModel.connectedYubiKey.value?.let { onUsb(it) - } ?: onNfc() + } ?: try { + onNfc().value.also { + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_FINISHED) + } + } catch (e: Throwable) { + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) + throw e + } + } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt index 445067f2..8418f588 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt @@ -60,7 +60,7 @@ class FidoConnectionHelper( block(YubiKitFidoSession(it)) } - suspend fun useSessionNfc(block: (YubiKitFidoSession) -> T): T { + suspend fun useSessionNfc(block: (YubiKitFidoSession) -> T): Result { try { val result = suspendCoroutine { outer -> pendingAction = { @@ -74,11 +74,11 @@ class FidoConnectionHelper( pendingAction = null } } - return result + return Result.success(result!!) } catch (cancelled: CancellationException) { - throw cancelled + return Result.failure(cancelled) } catch (error: Throwable) { - throw error + return Result.failure(error) } finally { dialogManager.closeDialog() } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt index 4c82c856..87212d4a 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt @@ -35,12 +35,9 @@ class ManagementConnectionHelper( ) { private var action: ManagementAction? = null - suspend fun useSession( - actionDescription: ManagementActionDescription, - action: (YubiKitManagementSession) -> T - ): T { + suspend fun useSession(action: (YubiKitManagementSession) -> T): T { return deviceManager.withKey( - onNfc = { useSessionNfc(actionDescription, action) }, + onNfc = { useSessionNfc(action) }, onUsb = { useSessionUsb(it, action) }) } @@ -51,28 +48,25 @@ class ManagementConnectionHelper( block(YubiKitManagementSession(it)) } - private suspend fun useSessionNfc( - actionDescription: ManagementActionDescription, - block: (YubiKitManagementSession) -> T - ): T { + private suspend fun useSessionNfc(block: (YubiKitManagementSession) -> T): Result { try { - val result = suspendCoroutine { outer -> + val result = suspendCoroutine { outer -> action = { outer.resumeWith(runCatching { block.invoke(it.value) }) } dialogManager.showDialog { - logger.debug("Cancelled Dialog {}", actionDescription.name) + logger.debug("Cancelled Dialog") action?.invoke(Result.failure(CancellationException())) action = null } } - return result + return Result.success(result!!) } catch (cancelled: CancellationException) { - throw cancelled + return Result.failure(cancelled) } catch (error: Throwable) { - throw error + return Result.failure(error) } finally { dialogManager.closeDialog() } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementHandler.kt b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementHandler.kt index 4c5096d1..473eb3ab 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementHandler.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementHandler.kt @@ -27,15 +27,6 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.asCoroutineDispatcher import java.util.concurrent.Executors -const val dialogDescriptionManagementIndex = 300 - -enum class ManagementActionDescription(private val value: Int) { - DeviceReset(0), ActionFailure(1); - - val id: Int - get() = value + dialogDescriptionManagementIndex -} - class ManagementHandler( messenger: BinaryMessenger, deviceManager: DeviceManager, @@ -58,7 +49,7 @@ class ManagementHandler( } private suspend fun deviceReset(): String = - connectionHelper.useSession(ManagementActionDescription.DeviceReset) { managementSession -> + connectionHelper.useSession { managementSession -> managementSession.deviceReset() NULL } 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 1a5802f1..41b7d3a5 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 @@ -80,7 +80,6 @@ class OathManager( private val oathViewModel: OathViewModel, private val dialogManager: DialogManager, private val appPreferences: AppPreferences, - private val appMethodChannel: MainActivity.AppMethodChannel, private val nfcActivityListener: NfcActivityListener ) : AppContextManager(), DeviceListener { @@ -221,10 +220,6 @@ class OathManager( override suspend fun processYubiKey(device: YubiKeyDevice) { try { - if (device is NfcYubiKeyDevice) { - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_STARTED) - } - device.withConnection { connection -> val session = getOathSession(connection) val previousId = oathViewModel.currentSession()?.deviceId @@ -310,7 +305,6 @@ class OathManager( deviceManager.setDeviceInfo(getDeviceInfo(device)) } } catch (e: Exception) { - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) // OATH not enabled/supported, try to get DeviceInfo over other USB interfaces logger.error("Failed to connect to CCID: ", e) // Clear any cached OATH state @@ -346,7 +340,7 @@ class OathManager( logger.debug("Added cred {}", credential) jsonSerializer.encodeToString(addedCred) - } + }.value } private suspend fun addAccountsToAny( @@ -725,7 +719,7 @@ class OathManager( private suspend fun useOathSessionNfc( block: (YubiKitOathSession) -> T - ): T { + ): Result { var firstShow = true while (true) { // loop until success or cancel try { @@ -749,14 +743,12 @@ class OathManager( // here the coroutine is suspended and waits till pendingAction is // invoked - the pending action result will resume this coroutine } - nfcActivityListener.onChange(NfcActivityState.PROCESSING_FINISHED) - return result + return Result.success(result!!) } catch (cancelled: CancellationException) { - throw cancelled + return Result.failure(cancelled) } catch (e: Exception) { logger.error("Exception during action: ", e) - nfcActivityListener.onChange(NfcActivityState.PROCESSING_INTERRUPTED) - throw e + return Result.failure(e) } } // while } diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index b40f634f..720d9cd5 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -109,8 +109,13 @@ class _AndroidOathStateNotifier extends OathStateNotifier { try { await oath.setPassword(current, password); return true; - } on PlatformException catch (e) { - _log.debug('Calling set password failed with exception: $e'); + } on PlatformException catch (pe) { + final decoded = pe.decode(); + if (decoded is CancellationException) { + _log.debug('Set password cancelled'); + throw decoded; + } + _log.debug('Calling set password failed with exception: $pe'); return false; } } @@ -120,8 +125,13 @@ class _AndroidOathStateNotifier extends OathStateNotifier { try { await oath.unsetPassword(current); return true; - } on PlatformException catch (e) { - _log.debug('Calling unset password failed with exception: $e'); + } on PlatformException catch (pe) { + final decoded = pe.decode(); + if (decoded is CancellationException) { + _log.debug('Unset password cancelled'); + throw decoded; + } + _log.debug('Calling unset password failed with exception: $pe'); return false; } } diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 6dc03731..3413d278 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -20,6 +20,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../app/message.dart'; import '../app/state.dart'; import 'state.dart'; import 'views/nfc/models.dart'; @@ -80,7 +81,9 @@ class _DialogProvider extends Notifier { case NfcActivity.processingFinished: explicitAction = false; // next action might not be explicit processingTimer?.cancel(); - if (properties.showSuccess ?? false) { + final showSuccess = properties.showSuccess ?? false; + allowMessages = !showSuccess; + if (showSuccess) { notifier.sendCommand( updateNfcView(NfcActivityClosingCountdownWidgetView( closeInSec: 5, diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/views/nfc/nfc_activity_overlay.dart index 0c90af3b..b9b0218f 100644 --- a/lib/android/views/nfc/nfc_activity_overlay.dart +++ b/lib/android/views/nfc/nfc_activity_overlay.dart @@ -97,9 +97,7 @@ class _NfcActivityClosingCountdownWidgetViewState } void hideNow() { - debugPrint('XXX closing because have to!'); - ref.read(nfcEventCommandNotifier.notifier).sendCommand( - NfcEventCommand(event: const NfcHideViewEvent(timeoutMs: 0))); + ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView); } } @@ -123,10 +121,10 @@ class _NfcViewNotifier extends Notifier { bool? showSuccess, bool? showCloseButton}) { state = state.copyWith( - operationSuccess: operationSuccess, - operationFailure: operationFailure, - showSuccess: showSuccess, - showCloseButton: showCloseButton); + operationSuccess: operationSuccess ?? state.operationSuccess, + operationFailure: operationFailure ?? state.operationFailure, + showSuccess: showSuccess ?? state.showSuccess, + showCloseButton: showCloseButton ?? state.showCloseButton); } } diff --git a/lib/app/message.dart b/lib/app/message.dart index 431e5d6a..f523f425 100755 --- a/lib/app/message.dart +++ b/lib/app/message.dart @@ -21,12 +21,17 @@ import 'package:flutter/material.dart'; import '../widgets/toast.dart'; +var allowMessages = true; + void Function() showMessage( BuildContext context, String message, { Duration duration = const Duration(seconds: 2), -}) => - showToast(context, message, duration: duration); +}) { + return allowMessages + ? showToast(context, message, duration: duration) + : () {}; +} Future showBlurDialog({ required BuildContext context, diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index ce9baeea..a1ca5b9e 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -442,6 +442,12 @@ "s_rename_account": "Konto umbenennen", "l_rename_account_desc": "Bearbeiten Sie den Aussteller/Namen des Kontos", "s_account_renamed": "Konto umbenannt", + "l_rename_account_failed": null, + "@l_rename_account_failed": { + "placeholders": { + "message": {} + } + }, "p_rename_will_change_account_displayed": "Das ändert die Anzeige dieses Kontos in der Liste.", "s_delete_account": "Konto löschen", "l_delete_account_desc": "Löschen Sie das Konto von Ihrem YubiKey", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ff8914a5..ddf0897c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -442,6 +442,12 @@ "s_rename_account": "Rename account", "l_rename_account_desc": "Edit the issuer/name of the account", "s_account_renamed": "Account renamed", + "l_rename_account_failed": "Failed renaming account: {message}", + "@l_rename_account_failed": { + "placeholders": { + "message": {} + } + }, "p_rename_will_change_account_displayed": "This will change how the account is displayed in the list.", "s_delete_account": "Delete account", "l_delete_account_desc": "Remove the account from your YubiKey", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 70b62152..16ad7692 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -442,6 +442,12 @@ "s_rename_account": "Renommer compte", "l_rename_account_desc": "Modifier émetteur/nom du compte", "s_account_renamed": "Compte renommé", + "l_rename_account_failed": null, + "@l_rename_account_failed": { + "placeholders": { + "message": {} + } + }, "p_rename_will_change_account_displayed": "Cela modifiera l'affichage du compte dans la liste.", "s_delete_account": "Supprimer compte", "l_delete_account_desc": "Supprimer le compte de votre YubiKey", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 27e96407..dc66bd8c 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -442,6 +442,12 @@ "s_rename_account": "アカウント名を変更", "l_rename_account_desc": "アカウントの発行者/名前を編集", "s_account_renamed": "アカウントの名前が変更されました", + "l_rename_account_failed": null, + "@l_rename_account_failed": { + "placeholders": { + "message": {} + } + }, "p_rename_will_change_account_displayed": "これにより、リスト内のアカウントの表示が変更されます。", "s_delete_account": "アカウントを削除", "l_delete_account_desc": "YubiKeyからアカウントを削除", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 7ebdaae3..59f55a96 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -442,6 +442,12 @@ "s_rename_account": "Zmień nazwę konta", "l_rename_account_desc": "Edytuj wydawcę/nazwę konta", "s_account_renamed": "Zmieniono nazwę konta", + "l_rename_account_failed": null, + "@l_rename_account_failed": { + "placeholders": { + "message": {} + } + }, "p_rename_will_change_account_displayed": "Spowoduje to zmianę sposobu wyświetlania konta na liście.", "s_delete_account": "Usuń konto", "l_delete_account_desc": "Usuń konto z klucza YubiKey", diff --git a/lib/oath/views/rename_account_dialog.dart b/lib/oath/views/rename_account_dialog.dart index ad5bdf54..6c9989e4 100755 --- a/lib/oath/views/rename_account_dialog.dart +++ b/lib/oath/views/rename_account_dialog.dart @@ -92,7 +92,7 @@ class RenameAccountDialog extends ConsumerStatefulWidget { } on CancellationException catch (_) { // ignored } catch (e) { - _log.error('Failed to add account', e); + _log.error('Failed to rename account', e); final String errorMessage; // TODO: Make this cleaner than importing desktop specific RpcError. if (e is RpcError) { @@ -103,7 +103,7 @@ class RenameAccountDialog extends ConsumerStatefulWidget { await withContext((context) async => showMessage( context, AppLocalizations.of(context)! - .l_account_add_failed(errorMessage), + .l_rename_account_failed(errorMessage), duration: const Duration(seconds: 4), )); return null; From f98e34b5d0b9a201f98c94abbb78f3b41b915168 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 4 Sep 2024 13:34:00 +0200 Subject: [PATCH 22/50] support retries on NFC failure --- .../com/yubico/authenticator/MainActivity.kt | 49 +++++++---- .../authenticator/device/DeviceManager.kt | 44 +++++++--- .../fido/FidoConnectionHelper.kt | 29 +++---- .../yubico/authenticator/fido/FidoManager.kt | 13 +-- .../authenticator/fido/FidoResetHelper.kt | 13 ++- .../management/ManagementConnectionHelper.kt | 33 +++---- .../management/ManagementHandler.kt | 6 +- .../yubico/authenticator/oath/OathManager.kt | 87 +++++++------------ .../authenticator/yubikit/ConnectionHelper.kt | 12 ++- lib/android/fido/state.dart | 2 +- lib/android/tap_request_dialog.dart | 16 ++-- .../nfc/nfc_activity_command_listener.dart | 7 +- .../views/nfc/nfc_activity_overlay.dart | 6 +- lib/android/views/nfc/nfc_failure_icon.dart | 29 +++++++ lib/l10n/app_de.arb | 1 + lib/l10n/app_en.arb | 3 +- lib/l10n/app_fr.arb | 1 + lib/l10n/app_ja.arb | 1 + lib/l10n/app_pl.arb | 1 + lib/l10n/app_vi.arb | 1 + lib/oath/views/rename_account_dialog.dart | 1 + 21 files changed, 204 insertions(+), 151 deletions(-) create mode 100644 lib/android/views/nfc/nfc_failure_icon.dart 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 6e4c3072..2f5dfacf 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -21,7 +21,6 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.annotation.SuppressLint -import android.content.* import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.pm.ActivityInfo import android.content.pm.PackageManager @@ -80,6 +79,7 @@ import kotlinx.coroutines.launch import org.json.JSONObject import org.slf4j.LoggerFactory import java.io.Closeable +import java.io.IOException import java.security.NoSuchAlgorithmException import java.util.concurrent.Executors import javax.crypto.Mac @@ -318,10 +318,14 @@ class MainActivity : FlutterFragmentActivity() { return } - // If NFC and FIPS check for SCP11b key - if (device.transport == Transport.NFC && deviceInfo.fipsCapable != 0) { - logger.debug("Checking for usable SCP11b key...") - deviceManager.scpKeyParams = + if (device is NfcYubiKeyDevice) { + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_STARTED) + } + + val scpKeyParams : ScpKeyParams? = try { + // If NFC and FIPS check for SCP11b key + if (device.transport == Transport.NFC && deviceInfo.fipsCapable != 0) { + logger.debug("Checking for usable SCP11b key...") device.withConnection { connection -> val scp = SecurityDomainSession(connection) val keyRef = scp.keyInformation.keys.firstOrNull { it.kid == ScpKid.SCP11b } @@ -335,15 +339,22 @@ class MainActivity : FlutterFragmentActivity() { logger.debug("Found SCP11b key: {}", keyRef) } } + } else null + } catch (e: Exception) { + logger.debug("Exception while getting scp keys: ", e) + if (device is NfcYubiKeyDevice) { + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) + } + null } // this YubiKey provides SCP11b key but the phone cannot perform AESCMAC - if (deviceManager.scpKeyParams != null && !supportsScp11b) { + if (scpKeyParams != null && !supportsScp11b) { deviceManager.setDeviceInfo(noScp11bNfcSupport) return } - deviceManager.setDeviceInfo(deviceInfo) + deviceManager.setDeviceInfo(deviceInfo, scpKeyParams) val supportedContexts = DeviceManager.getSupportedContexts(deviceInfo) logger.debug("Connected key supports: {}", supportedContexts) if (!supportedContexts.contains(viewModel.appContext.value)) { @@ -362,9 +373,6 @@ class MainActivity : FlutterFragmentActivity() { contextManager?.let { try { - if (device is NfcYubiKeyDevice) { - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_STARTED) - } it.processYubiKey(device) if (device is NfcYubiKeyDevice) { appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_FINISHED) @@ -372,10 +380,12 @@ class MainActivity : FlutterFragmentActivity() { appMethodChannel.nfcActivityStateChanged(NfcActivityState.READY) } } - } catch (e: Throwable) { + } catch (e: IOException) { + logger.debug("Caught IOException during YubiKey processing: ", e) appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) - logger.error("Error processing YubiKey in AppContextManager", e) } + + } } @@ -403,12 +413,13 @@ class MainActivity : FlutterFragmentActivity() { messenger = flutterEngine.dartExecutor.binaryMessenger flutterLog = FlutterLog(messenger) appMethodChannel = AppMethodChannel(messenger) - deviceManager = DeviceManager(this, viewModel,appMethodChannel) - appContext = AppContext(messenger, this.lifecycleScope, viewModel) dialogManager = DialogManager(messenger, this.lifecycleScope) + deviceManager = DeviceManager(this, viewModel,appMethodChannel, dialogManager) + appContext = AppContext(messenger, this.lifecycleScope, viewModel) + appPreferences = AppPreferences(this) appLinkMethodChannel = AppLinkMethodChannel(messenger) - managementHandler = ManagementHandler(messenger, deviceManager, dialogManager) + managementHandler = ManagementHandler(messenger, deviceManager) nfcActivityListener.appMethodChannel = appMethodChannel @@ -453,8 +464,7 @@ class MainActivity : FlutterFragmentActivity() { deviceManager, oathViewModel, dialogManager, - appPreferences, - nfcActivityListener + appPreferences ) OperationContext.FidoFingerprints, @@ -462,9 +472,10 @@ class MainActivity : FlutterFragmentActivity() { messenger, this, deviceManager, + appMethodChannel, + dialogManager, fidoViewModel, - viewModel, - dialogManager + viewModel ) else -> null diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt index e58ae5e7..1791b4d9 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt @@ -20,6 +20,7 @@ import androidx.collection.ArraySet import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.Observer +import com.yubico.authenticator.DialogManager import com.yubico.authenticator.MainActivity import com.yubico.authenticator.MainViewModel import com.yubico.authenticator.OperationContext @@ -28,7 +29,10 @@ import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice import com.yubico.yubikit.core.YubiKeyDevice import com.yubico.yubikit.core.smartcard.scp.ScpKeyParams import com.yubico.yubikit.management.Capability +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.delay import org.slf4j.LoggerFactory +import kotlin.coroutines.suspendCoroutine interface DeviceListener { // a USB device is connected @@ -44,7 +48,8 @@ interface DeviceListener { class DeviceManager( private val lifecycleOwner: LifecycleOwner, private val appViewModel: MainViewModel, - private val appMethodChannel: MainActivity.AppMethodChannel + private val appMethodChannel: MainActivity.AppMethodChannel, + private val dialogManager: DialogManager ) { var clearDeviceInfoOnDisconnect: Boolean = true @@ -168,9 +173,9 @@ class DeviceManager( appViewModel.connectedYubiKey.removeObserver(usbObserver) } - fun setDeviceInfo(deviceInfo: Info?) { + fun setDeviceInfo(deviceInfo: Info?, scpKeyParams: ScpKeyParams? = null) { appViewModel.setDeviceInfo(deviceInfo) - scpKeyParams = null + this.scpKeyParams = scpKeyParams } fun isUsbKeyConnected(): Boolean { @@ -183,18 +188,35 @@ class DeviceManager( } suspend fun withKey( + onUsb: suspend (UsbYubiKeyDevice) -> T, onNfc: suspend () -> com.yubico.yubikit.core.util.Result, - onUsb: suspend (UsbYubiKeyDevice) -> T + onDialogCancelled: () -> Unit ): T = appViewModel.connectedYubiKey.value?.let { onUsb(it) - } ?: try { - onNfc().value.also { - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_FINISHED) - } - } catch (e: Throwable) { - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) - throw e + } ?: onNfcWithRetries(onNfc, onDialogCancelled) + + private suspend fun onNfcWithRetries( + onNfc: suspend () -> com.yubico.yubikit.core.util.Result, + onDialogCancelled: () -> Unit) : T { + + dialogManager.showDialog { + logger.debug("Cancelled dialog") + onDialogCancelled.invoke() } + while (true) { + try { + return onNfc.invoke().value + } catch (e: Exception) { + if (e is CancellationException) { + throw e + } + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) + } + + logger.debug("NFC action failed, asking to try again") + } + } + } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt index 8418f588..061b9c43 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt @@ -16,7 +16,6 @@ package com.yubico.authenticator.fido -import com.yubico.authenticator.DialogManager import com.yubico.authenticator.device.DeviceManager import com.yubico.authenticator.fido.data.YubiKitFidoSession import com.yubico.authenticator.yubikit.withConnection @@ -27,10 +26,7 @@ import org.slf4j.LoggerFactory import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.suspendCoroutine -class FidoConnectionHelper( - private val deviceManager: DeviceManager, - private val dialogManager: DialogManager -) { +class FidoConnectionHelper(private val deviceManager: DeviceManager) { private var pendingAction: FidoAction? = null fun invokePending(fidoSession: YubiKitFidoSession) { @@ -47,10 +43,15 @@ class FidoConnectionHelper( } } - suspend fun useSession(action: (YubiKitFidoSession) -> T): T { + suspend fun useSession(block: (YubiKitFidoSession) -> T): T { return deviceManager.withKey( - onNfc = { useSessionNfc(action) }, - onUsb = { useSessionUsb(it, action) }) + onUsb = { useSessionUsb(it, block) }, + onNfc = { useSessionNfc(block) }, + onDialogCancelled = { + pendingAction?.invoke(Result.failure(CancellationException())) + pendingAction = null + } + ) } suspend fun useSessionUsb( @@ -60,7 +61,9 @@ class FidoConnectionHelper( block(YubiKitFidoSession(it)) } - suspend fun useSessionNfc(block: (YubiKitFidoSession) -> T): Result { + suspend fun useSessionNfc( + block: (YubiKitFidoSession) -> T + ): Result { try { val result = suspendCoroutine { outer -> pendingAction = { @@ -68,19 +71,13 @@ class FidoConnectionHelper( block.invoke(it.value) }) } - dialogManager.showDialog { - logger.debug("Cancelled dialog") - pendingAction?.invoke(Result.failure(CancellationException())) - pendingAction = null - } } return Result.success(result!!) } catch (cancelled: CancellationException) { return Result.failure(cancelled) } catch (error: Throwable) { + logger.error("Exception during action: ", error) return Result.failure(error) - } finally { - dialogManager.closeDialog() } } 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 09b98cb1..93d521b7 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 @@ -19,6 +19,7 @@ package com.yubico.authenticator.fido import androidx.lifecycle.LifecycleOwner import com.yubico.authenticator.AppContextManager import com.yubico.authenticator.DialogManager +import com.yubico.authenticator.MainActivity import com.yubico.authenticator.MainViewModel import com.yubico.authenticator.NULL import com.yubico.authenticator.asString @@ -68,9 +69,10 @@ class FidoManager( messenger: BinaryMessenger, lifecycleOwner: LifecycleOwner, private val deviceManager: DeviceManager, + private val appMethodChannel: MainActivity.AppMethodChannel, + private val dialogManager: DialogManager, private val fidoViewModel: FidoViewModel, - mainViewModel: MainViewModel, - dialogManager: DialogManager, + mainViewModel: MainViewModel ) : AppContextManager(), DeviceListener { @OptIn(ExperimentalStdlibApi::class) @@ -97,7 +99,7 @@ class FidoManager( } } - private val connectionHelper = FidoConnectionHelper(deviceManager, dialogManager) + private val connectionHelper = FidoConnectionHelper(deviceManager) private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val coroutineScope = CoroutineScope(SupervisorJob() + dispatcher) @@ -114,6 +116,8 @@ class FidoManager( FidoResetHelper( lifecycleOwner, deviceManager, + appMethodChannel, + dialogManager, fidoViewModel, mainViewModel, connectionHelper, @@ -194,7 +198,6 @@ class FidoManager( // Clear any cached FIDO state fidoViewModel.clearSessionState() } - } private fun processYubiKey(connection: YubiKeyConnection, device: YubiKeyDevice) { @@ -578,7 +581,7 @@ class FidoManager( } else -> throw ctapException } - } catch (io: IOException) { + } catch (_: IOException) { return@useSession JSONObject( mapOf( "success" to false, 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 d7f4d367..8ef6363f 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 @@ -18,11 +18,14 @@ package com.yubico.authenticator.fido import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner +import com.yubico.authenticator.DialogManager +import com.yubico.authenticator.MainActivity import com.yubico.authenticator.MainViewModel import com.yubico.authenticator.NULL import com.yubico.authenticator.device.DeviceManager import com.yubico.authenticator.fido.data.Session import com.yubico.authenticator.fido.data.YubiKitFidoSession +import com.yubico.authenticator.yubikit.NfcActivityState import com.yubico.yubikit.core.application.CommandState import com.yubico.yubikit.core.fido.CtapException import kotlinx.coroutines.CoroutineScope @@ -68,6 +71,8 @@ fun createCaptureErrorEvent(code: Int) : FidoRegisterFpCaptureErrorEvent { class FidoResetHelper( private val lifecycleOwner: LifecycleOwner, private val deviceManager: DeviceManager, + private val appMethodChannel: MainActivity.AppMethodChannel, + private val dialogManager: DialogManager, private val fidoViewModel: FidoViewModel, private val mainViewModel: MainViewModel, private val connectionHelper: FidoConnectionHelper, @@ -106,7 +111,7 @@ class FidoResetHelper( resetOverNfc() } logger.info("FIDO reset complete") - } catch (e: CancellationException) { + } catch (_: CancellationException) { logger.debug("FIDO reset cancelled") } finally { withContext(Dispatchers.Main) { @@ -209,15 +214,19 @@ class FidoResetHelper( private suspend fun resetOverNfc() = suspendCoroutine { continuation -> coroutineScope.launch { + dialogManager.showDialog { + + } fidoViewModel.updateResetState(FidoResetState.Touch) try { connectionHelper.useSessionNfc { fidoSession -> doReset(fidoSession) continuation.resume(Unit) - } + }.value } catch (e: Throwable) { // on NFC, clean device info in this situation mainViewModel.setDeviceInfo(null) + logger.error("Failure during FIDO reset:", e) continuation.resumeWithException(e) } } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt index 87212d4a..17a91bc6 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt @@ -16,13 +16,11 @@ package com.yubico.authenticator.management -import com.yubico.authenticator.DialogManager import com.yubico.authenticator.device.DeviceManager import com.yubico.authenticator.yubikit.withConnection import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice import com.yubico.yubikit.core.smartcard.SmartCardConnection import com.yubico.yubikit.core.util.Result -import org.slf4j.LoggerFactory import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.suspendCoroutine @@ -30,16 +28,19 @@ typealias YubiKitManagementSession = com.yubico.yubikit.management.ManagementSes typealias ManagementAction = (Result) -> Unit class ManagementConnectionHelper( - private val deviceManager: DeviceManager, - private val dialogManager: DialogManager + private val deviceManager: DeviceManager ) { private var action: ManagementAction? = null - suspend fun useSession(action: (YubiKitManagementSession) -> T): T { - return deviceManager.withKey( - onNfc = { useSessionNfc(action) }, - onUsb = { useSessionUsb(it, action) }) - } + suspend fun useSession(block: (YubiKitManagementSession) -> T): T = + deviceManager.withKey( + onUsb = { useSessionUsb(it, block) }, + onNfc = { useSessionNfc(block) }, + onDialogCancelled = { + action?.invoke(Result.failure(CancellationException())) + action = null + }, + ) private suspend fun useSessionUsb( device: UsbYubiKeyDevice, @@ -48,7 +49,8 @@ class ManagementConnectionHelper( block(YubiKitManagementSession(it)) } - private suspend fun useSessionNfc(block: (YubiKitManagementSession) -> T): Result { + private suspend fun useSessionNfc( + block: (YubiKitManagementSession) -> T): Result { try { val result = suspendCoroutine { outer -> action = { @@ -56,23 +58,12 @@ class ManagementConnectionHelper( block.invoke(it.value) }) } - dialogManager.showDialog { - logger.debug("Cancelled Dialog") - action?.invoke(Result.failure(CancellationException())) - action = null - } } return Result.success(result!!) } catch (cancelled: CancellationException) { return Result.failure(cancelled) } catch (error: Throwable) { return Result.failure(error) - } finally { - dialogManager.closeDialog() } } - - companion object { - private val logger = LoggerFactory.getLogger(ManagementConnectionHelper::class.java) - } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementHandler.kt b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementHandler.kt index 473eb3ab..abf5542b 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementHandler.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementHandler.kt @@ -16,7 +16,6 @@ package com.yubico.authenticator.management -import com.yubico.authenticator.DialogManager import com.yubico.authenticator.NULL import com.yubico.authenticator.device.DeviceManager import com.yubico.authenticator.setHandler @@ -29,14 +28,13 @@ import java.util.concurrent.Executors class ManagementHandler( messenger: BinaryMessenger, - deviceManager: DeviceManager, - dialogManager: DialogManager + deviceManager: DeviceManager ) { private val channel = MethodChannel(messenger, "android.management.methods") private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val coroutineScope = CoroutineScope(SupervisorJob() + dispatcher) - private val connectionHelper = ManagementConnectionHelper(deviceManager, dialogManager) + private val connectionHelper = ManagementConnectionHelper(deviceManager) init { channel.setHandler(coroutineScope) { method, _ -> 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 41b7d3a5..0139460d 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 @@ -42,8 +42,6 @@ import com.yubico.authenticator.oath.keystore.ClearingMemProvider import com.yubico.authenticator.oath.keystore.KeyProvider import com.yubico.authenticator.oath.keystore.KeyStoreProvider import com.yubico.authenticator.oath.keystore.SharedPrefProvider -import com.yubico.authenticator.yubikit.NfcActivityListener -import com.yubico.authenticator.yubikit.NfcActivityState import com.yubico.authenticator.yubikit.DeviceInfoHelper.Companion.getDeviceInfo import com.yubico.authenticator.yubikit.withConnection import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice @@ -58,7 +56,6 @@ import com.yubico.yubikit.core.smartcard.SmartCardProtocol import com.yubico.yubikit.core.util.Result import com.yubico.yubikit.management.Capability import com.yubico.yubikit.oath.CredentialData -import com.yubico.yubikit.support.DeviceUtil import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.MethodChannel import kotlinx.coroutines.* @@ -66,10 +63,10 @@ import kotlinx.serialization.encodeToString import org.slf4j.LoggerFactory import java.io.IOException import java.net.URI -import java.util.TimerTask import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicBoolean import kotlin.coroutines.suspendCoroutine +import kotlin.random.Random typealias OathAction = (Result) -> Unit @@ -79,8 +76,7 @@ class OathManager( private val deviceManager: DeviceManager, private val oathViewModel: OathViewModel, private val dialogManager: DialogManager, - private val appPreferences: AppPreferences, - private val nfcActivityListener: NfcActivityListener + private val appPreferences: AppPreferences ) : AppContextManager(), DeviceListener { companion object { @@ -216,8 +212,6 @@ class OathManager( coroutineScope.cancel() } - var showProcessingTimerTask: TimerTask? = null - override suspend fun processYubiKey(device: YubiKeyDevice) { try { device.withConnection { connection -> @@ -227,8 +221,8 @@ class OathManager( // Either run a pending action, or just refresh codes if (pendingAction != null) { pendingAction?.let { action -> - action.invoke(Result.success(session)) pendingAction = null + action.invoke(Result.success(session)) } } else { // Refresh codes @@ -268,7 +262,6 @@ class OathManager( } else { // Awaiting an action for a different device? Fail it and stop processing. action.invoke(Result.failure(IllegalStateException("Wrong deviceId"))) - showProcessingTimerTask?.cancel() return@withConnection } } @@ -289,14 +282,12 @@ class OathManager( supportedCapabilities = oathCapabilities ) ) - showProcessingTimerTask?.cancel() return@withConnection } } } } - showProcessingTimerTask?.cancel() logger.debug( "Successfully read Oath session info (and credentials if unlocked) from connected key" ) @@ -320,7 +311,7 @@ class OathManager( val credentialData: CredentialData = CredentialData.parseUri(URI.create(uri)) addToAny = true - return useOathSessionNfc { session -> + return useSessionNfc { session -> // We need to check for duplicates here since we haven't yet read the credentials if (session.credentials.any { it.id.contentEquals(credentialData.id) }) { throw IllegalArgumentException() @@ -499,12 +490,6 @@ class OathManager( renamed ) -// // simulate long taking op -// val renamedCredential = credential -// logger.debug("simulate error") -// Thread.sleep(3000) -// throw IOException("Test exception") - jsonSerializer.encodeToString(renamed) } @@ -527,7 +512,7 @@ class OathManager( deviceManager.withKey { usbYubiKeyDevice -> try { - useOathSessionUsb(usbYubiKeyDevice) { session -> + useSessionUsb(usbYubiKeyDevice) { session -> try { oathViewModel.updateCredentials(calculateOathCodes(session)) } catch (apduException: ApduException) { @@ -653,7 +638,6 @@ class OathManager( return session } - private fun calculateOathCodes(session: YubiKitOathSession): Map { val isUsbKey = deviceManager.isUsbKeyConnected() var timestamp = System.currentTimeMillis() @@ -693,19 +677,23 @@ class OathManager( private suspend fun useOathSession( unlock: Boolean = true, updateDeviceInfo: Boolean = false, - action: (YubiKitOathSession) -> T + block: (YubiKitOathSession) -> T ): T { // callers can decide whether the session should be unlocked first unlockOnConnect.set(unlock) // callers can request whether device info should be updated after session operation this@OathManager.updateDeviceInfo.set(updateDeviceInfo) return deviceManager.withKey( - onUsb = { useOathSessionUsb(it, updateDeviceInfo, action) }, - onNfc = { useOathSessionNfc(action) } + onUsb = { useSessionUsb(it, updateDeviceInfo, block) }, + onNfc = { useSessionNfc(block) }, + onDialogCancelled = { + pendingAction?.invoke(Result.failure(CancellationException())) + pendingAction = null + }, ) } - private suspend fun useOathSessionUsb( + private suspend fun useSessionUsb( device: UsbYubiKeyDevice, updateDeviceInfo: Boolean = false, block: (YubiKitOathSession) -> T @@ -717,40 +705,27 @@ class OathManager( } } - private suspend fun useOathSessionNfc( - block: (YubiKitOathSession) -> T + private suspend fun useSessionNfc( + block: (YubiKitOathSession) -> T, ): Result { - var firstShow = true - while (true) { // loop until success or cancel - try { - val result = suspendCoroutine { outer -> - pendingAction = { - outer.resumeWith(runCatching { - val session = it.value // this can throw CancellationException - nfcActivityListener.onChange(NfcActivityState.PROCESSING_STARTED) - block.invoke(session) - }) - } - - if (firstShow) { - dialogManager.showDialog { - logger.debug("Cancelled dialog") - pendingAction?.invoke(Result.failure(CancellationException())) - pendingAction = null - } - firstShow = false - } - // here the coroutine is suspended and waits till pendingAction is - // invoked - the pending action result will resume this coroutine + try { + val result = suspendCoroutine { outer -> + pendingAction = { + outer.resumeWith(runCatching { + block.invoke(it.value) + }) } - return Result.success(result!!) - } catch (cancelled: CancellationException) { - return Result.failure(cancelled) - } catch (e: Exception) { - logger.error("Exception during action: ", e) - return Result.failure(e) + + // here the coroutine is suspended and waits till pendingAction is + // invoked - the pending action result will resume this coroutine } - } // while + return Result.success(result!!) + } catch (cancelled: CancellationException) { + return Result.failure(cancelled) + } catch (e: Exception) { + logger.error("Exception during action: ", e) + return Result.failure(e) + } } override fun onConnected(device: YubiKeyDevice) { diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/ConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/ConnectionHelper.kt index 0c20ff77..69512c95 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/ConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/ConnectionHelper.kt @@ -23,9 +23,13 @@ import kotlin.coroutines.suspendCoroutine suspend inline fun YubiKeyDevice.withConnection( crossinline block: (C) -> T ): T = suspendCoroutine { continuation -> - requestConnection(C::class.java) { - continuation.resumeWith(runCatching { - block(it.value) - }) + try { + requestConnection(C::class.java) { + continuation.resumeWith(runCatching { + block(it.value) + }) + } + } catch (_: Exception) { + // ignored } } diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index 8e6cd216..9398c20e 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -413,7 +413,7 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { 'callArgs': {'pin': pin}, 'operationSuccess': l10n.s_nfc_unlock_success, 'operationFailure': l10n.s_nfc_unlock_failure, - 'showSuccess': true + 'showSuccess': false }); Future enableEnterpriseAttestation() async => diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 3413d278..b4569855 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -16,19 +16,22 @@ import 'dart:async'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; +import '../app/logging.dart'; import '../app/message.dart'; import '../app/state.dart'; import 'state.dart'; import 'views/nfc/models.dart'; import 'views/nfc/nfc_activity_overlay.dart'; import 'views/nfc/nfc_content_widget.dart'; +import 'views/nfc/nfc_failure_icon.dart'; import 'views/nfc/nfc_progress_bar.dart'; import 'views/nfc/nfc_success_icon.dart'; +final _log = Logger('android.tap_request_dialog'); const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); final androidDialogProvider = @@ -99,17 +102,19 @@ class _DialogProvider extends Notifier { } break; case NfcActivity.processingInterrupted: - explicitAction = false; // next action might not be explicit + processingTimer?.cancel(); + viewNotifier.setDialogProperties(showCloseButton: true); notifier.sendCommand(updateNfcView(NfcContentWidget( title: properties.operationFailure, - icon: const NfcIconProgressBar(false), + subtitle: l10n.s_nfc_scan_again, + icon: const NfcIconFailure(), ))); break; case NfcActivity.notActive: - debugPrint('Received not handled notActive'); + _log.debug('Received not handled notActive'); break; case NfcActivity.ready: - debugPrint('Received not handled ready'); + _log.debug('Received not handled ready'); } }); @@ -143,7 +148,6 @@ class _DialogProvider extends Notifier { } void cancelDialog() async { - debugPrint('Cancelled dialog'); explicitAction = false; await _channel.invokeMethod('cancel'); } diff --git a/lib/android/views/nfc/nfc_activity_command_listener.dart b/lib/android/views/nfc/nfc_activity_command_listener.dart index 13756d35..b7cd94d8 100644 --- a/lib/android/views/nfc/nfc_activity_command_listener.dart +++ b/lib/android/views/nfc/nfc_activity_command_listener.dart @@ -16,11 +16,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; +import '../../../app/logging.dart'; import '../../tap_request_dialog.dart'; import 'models.dart'; import 'nfc_activity_overlay.dart'; +final _log = Logger('android.nfc_activity_command_listener'); + final nfcEventCommandListener = Provider<_NfcEventCommandListener>((ref) => _NfcEventCommandListener(ref)); @@ -34,8 +38,7 @@ class _NfcEventCommandListener { listener?.close(); listener = _ref.listen(nfcEventCommandNotifier.select((c) => c.event), (previous, action) { - debugPrint( - 'XXX Change in command for Overlay: $previous -> $action in context: $context'); + _log.debug('Change in command for Overlay: $previous -> $action'); switch (action) { case (NfcShowViewEvent a): _show(context, a.child); diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/views/nfc/nfc_activity_overlay.dart index b9b0218f..764b8929 100644 --- a/lib/android/views/nfc/nfc_activity_overlay.dart +++ b/lib/android/views/nfc/nfc_activity_overlay.dart @@ -144,14 +144,14 @@ class NfcBottomSheet extends ConsumerWidget { Stack(fit: StackFit.passthrough, children: [ if (showCloseButton) Positioned( - top: 8, - right: 8, + top: 10, + right: 10, child: IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Symbols.close, fill: 1, size: 24)), ), Padding( - padding: const EdgeInsets.fromLTRB(0, 40, 0, 0), + padding: const EdgeInsets.fromLTRB(0, 50, 0, 0), child: widget, ) ]), diff --git a/lib/android/views/nfc/nfc_failure_icon.dart b/lib/android/views/nfc/nfc_failure_icon.dart new file mode 100644 index 00000000..96ce85e0 --- /dev/null +++ b/lib/android/views/nfc/nfc_failure_icon.dart @@ -0,0 +1,29 @@ +/* + * 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. + */ + +import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; + +class NfcIconFailure extends StatelessWidget { + const NfcIconFailure({super.key}); + + @override + Widget build(BuildContext context) => Icon( + Symbols.close, + size: 64, + color: Theme.of(context).colorScheme.primary, + ); +} diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index ef76ca63..49779613 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -944,6 +944,7 @@ "s_nfc_ready_to_scan": null, "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, + "s_nfc_scan_again": null, "c_nfc_unlock": null, "s_nfc_unlock_processing": null, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 58bacda6..ea8f0c75 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -937,13 +937,14 @@ } }, - "l_nfc_read_key_failure": "Failed to read YubiKey, try again", + "l_nfc_read_key_failure": "Failed to scan YubiKey", "s_nfc_remove_key": "You can remove YubiKey", "s_nfc_ready_to_scan": "Ready to scan", "s_nfc_accessing_yubikey": "Accessing YubiKey", "s_nfc_scan_yubikey": "Scan your YubiKey", + "s_nfc_scan_again": "Scan again", "c_nfc_unlock": "unlock", "s_nfc_unlock_processing": "Unlocking", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 0fb875e9..0e5bf7e2 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -944,6 +944,7 @@ "s_nfc_ready_to_scan": null, "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, + "s_nfc_scan_again": null, "c_nfc_unlock": null, "s_nfc_unlock_processing": null, diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index f841392f..18758565 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -944,6 +944,7 @@ "s_nfc_ready_to_scan": null, "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, + "s_nfc_scan_again": null, "c_nfc_unlock": null, "s_nfc_unlock_processing": null, diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 169b935f..a8a7aea4 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -944,6 +944,7 @@ "s_nfc_ready_to_scan": null, "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, + "s_nfc_scan_again": null, "c_nfc_unlock": null, "s_nfc_unlock_processing": null, diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index 05fc3cc1..1991b756 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -944,6 +944,7 @@ "s_nfc_ready_to_scan": null, "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, + "s_nfc_scan_again": null, "c_nfc_unlock": null, "s_nfc_unlock_processing": null, diff --git a/lib/oath/views/rename_account_dialog.dart b/lib/oath/views/rename_account_dialog.dart index 6c9989e4..0b8e4618 100755 --- a/lib/oath/views/rename_account_dialog.dart +++ b/lib/oath/views/rename_account_dialog.dart @@ -199,6 +199,7 @@ class _RenameAccountDialogState extends ConsumerState { ), textInputAction: TextInputAction.next, focusNode: _issuerFocus, + autofocus: true, onChanged: (value) { setState(() { _issuer = value.trim(); From fb2bec0b5e8b595183150d7d8acd6c89935b1fc3 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 5 Sep 2024 07:27:41 +0200 Subject: [PATCH 23/50] schedule device info update in fido and oath --- .../authenticator/device/DeviceManager.kt | 2 -- .../fido/FidoConnectionHelper.kt | 16 +++++++++++++++- .../yubico/authenticator/fido/FidoManager.kt | 2 +- .../yubico/authenticator/oath/OathManager.kt | 19 ++++++++++++++++--- lib/android/fido/state.dart | 6 ++---- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt index 1791b4d9..76998807 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt @@ -30,9 +30,7 @@ import com.yubico.yubikit.core.YubiKeyDevice import com.yubico.yubikit.core.smartcard.scp.ScpKeyParams import com.yubico.yubikit.management.Capability import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.delay import org.slf4j.LoggerFactory -import kotlin.coroutines.suspendCoroutine interface DeviceListener { // a USB device is connected diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt index 4d5f86aa..bb0ede5f 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt @@ -17,6 +17,7 @@ package com.yubico.authenticator.fido import com.yubico.authenticator.device.DeviceManager +import com.yubico.authenticator.device.Info import com.yubico.authenticator.fido.data.YubiKitFidoSession import com.yubico.authenticator.yubikit.DeviceInfoHelper.Companion.getDeviceInfo import com.yubico.authenticator.yubikit.withConnection @@ -24,11 +25,15 @@ import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice import com.yubico.yubikit.core.fido.FidoConnection import com.yubico.yubikit.core.util.Result import org.slf4j.LoggerFactory +import java.util.Timer +import java.util.TimerTask import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.suspendCoroutine +import kotlin.concurrent.schedule class FidoConnectionHelper(private val deviceManager: DeviceManager) { private var pendingAction: FidoAction? = null + private var deviceInfoTimer: TimerTask? = null fun invokePending(fidoSession: YubiKitFidoSession) { pendingAction?.let { action -> @@ -38,6 +43,7 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) { } fun cancelPending() { + deviceInfoTimer?.cancel() pendingAction?.let { action -> action.invoke(Result.failure(CancellationException())) pendingAction = null @@ -67,7 +73,7 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) { block(YubiKitFidoSession(it)) }.also { if (updateDeviceInfo) { - deviceManager.setDeviceInfo(getDeviceInfo(device)) + scheduleDeviceInfoUpdate(getDeviceInfo(device)) } } @@ -91,6 +97,14 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) { } } + fun scheduleDeviceInfoUpdate(deviceInfo: Info?) { + deviceInfoTimer?.cancel() + deviceInfoTimer = Timer("update-device-info", false).schedule(500) { + logger.debug("Updating device info") + deviceManager.setDeviceInfo(deviceInfo) + } + } + companion object { private val logger = LoggerFactory.getLogger(FidoConnectionHelper::class.java) } 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 56659c37..f9381c84 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 @@ -199,7 +199,7 @@ class FidoManager( } if (updateDeviceInfo.getAndSet(false)) { - deviceManager.setDeviceInfo(getDeviceInfo(device)) + connectionHelper.scheduleDeviceInfoUpdate(getDeviceInfo(device)) } } catch (e: Exception) { // something went wrong, try to get DeviceInfo from any available connection type 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 895fa1bd..c453c489 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 @@ -26,6 +26,7 @@ import com.yubico.authenticator.* import com.yubico.authenticator.device.Capabilities import com.yubico.authenticator.device.DeviceListener import com.yubico.authenticator.device.DeviceManager +import com.yubico.authenticator.device.Info import com.yubico.authenticator.device.UnknownDevice import com.yubico.authenticator.oath.data.Code import com.yubico.authenticator.oath.data.CodeType @@ -63,10 +64,12 @@ import kotlinx.serialization.encodeToString import org.slf4j.LoggerFactory import java.io.IOException import java.net.URI +import java.util.Timer +import java.util.TimerTask import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicBoolean import kotlin.coroutines.suspendCoroutine -import kotlin.random.Random +import kotlin.concurrent.schedule typealias OathAction = (Result) -> Unit @@ -108,8 +111,10 @@ class OathManager( private var refreshJob: Job? = null private var addToAny = false private val updateDeviceInfo = AtomicBoolean(false) + private var deviceInfoTimer: TimerTask? = null override fun onPause() { + deviceInfoTimer?.cancel() // cancel any pending actions, except for addToAny if (!addToAny) { pendingAction?.let { @@ -294,7 +299,7 @@ class OathManager( ) if (updateDeviceInfo.getAndSet(false)) { - deviceManager.setDeviceInfo(getDeviceInfo(device)) + scheduleDeviceInfoUpdate(getDeviceInfo(device)) } } catch (e: Exception) { // OATH not enabled/supported, try to get DeviceInfo over other USB interfaces @@ -675,6 +680,14 @@ class OathManager( return credential.data } + fun scheduleDeviceInfoUpdate(deviceInfo: Info?) { + deviceInfoTimer?.cancel() + deviceInfoTimer = Timer("update-device-info", false).schedule(500) { + logger.debug("Updating device info") + deviceManager.setDeviceInfo(deviceInfo) + } + } + private suspend fun useOathSession( unlock: Boolean = true, updateDeviceInfo: Boolean = false, @@ -702,7 +715,7 @@ class OathManager( block(getOathSession(it)) }.also { if (updateDeviceInfo) { - deviceManager.setDeviceInfo(getDeviceInfo(device)) + scheduleDeviceInfoUpdate(getDeviceInfo(device)) } } diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index 9398c20e..b2850c23 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -393,8 +393,7 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { Future reset() async => invoke('reset', { 'operationSuccess': l10n.s_nfc_fido_reset_success, - 'operationFailure': l10n.s_nfc_fido_reset_failure, - 'showSuccess': true + 'operationFailure': l10n.s_nfc_fido_reset_failure }); Future setPin(String newPin, {String? oldPin}) async => @@ -405,8 +404,7 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { : l10n.s_pin_set, 'operationFailure': oldPin != null ? l10n.s_nfc_fido_change_pin_failure - : l10n.s_nfc_fido_set_pin_failure, - 'showSuccess': true + : l10n.s_nfc_fido_set_pin_failure }); Future unlock(String pin) async => invoke('unlock', { From 516d776921afbe0676c0cdb29be5751472e1e692 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 5 Sep 2024 13:33:42 +0200 Subject: [PATCH 24/50] fix pop stack on device change, support nfc retry --- .../yubico/authenticator/AppContextManager.kt | 4 +- .../authenticator/device/DeviceManager.kt | 30 +++++++++-- .../fido/FidoConnectionHelper.kt | 14 ++--- .../yubico/authenticator/fido/FidoManager.kt | 2 +- .../management/ManagementConnectionHelper.kt | 1 + .../yubico/authenticator/oath/OathManager.kt | 23 ++++---- lib/android/fido/state.dart | 6 ++- lib/android/oath/state.dart | 54 +++++++++---------- lib/app/views/main_page.dart | 46 ++++++++++------ 9 files changed, 104 insertions(+), 76 deletions(-) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/AppContextManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/AppContextManager.kt index de1a30ae..e40c6ef0 100755 --- a/android/app/src/main/kotlin/com/yubico/authenticator/AppContextManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/AppContextManager.kt @@ -27,4 +27,6 @@ abstract class AppContextManager { open fun dispose() {} open fun onPause() {} -} \ No newline at end of file +} + +class ContextDisposedException : Exception() \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt index 76998807..6af9b4df 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt @@ -188,11 +188,16 @@ class DeviceManager( suspend fun withKey( onUsb: suspend (UsbYubiKeyDevice) -> T, onNfc: suspend () -> com.yubico.yubikit.core.util.Result, - onDialogCancelled: () -> Unit + onDialogCancelled: () -> Unit, + retryOnNfcFailure: Boolean ): T = appViewModel.connectedYubiKey.value?.let { onUsb(it) - } ?: onNfcWithRetries(onNfc, onDialogCancelled) + } ?: if (retryOnNfcFailure == true) { + onNfcWithRetries(onNfc, onDialogCancelled) + } else { + onNfc(onNfc, onDialogCancelled) + } private suspend fun onNfcWithRetries( onNfc: suspend () -> com.yubico.yubikit.core.util.Result, @@ -207,13 +212,32 @@ class DeviceManager( try { return onNfc.invoke().value } catch (e: Exception) { + + logger.debug("NFC action failed, asking to try again. Failure: ", e) + if (e is CancellationException) { throw e } appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) } - logger.debug("NFC action failed, asking to try again") + + } + } + + private suspend fun onNfc( + onNfc: suspend () -> com.yubico.yubikit.core.util.Result, + onDialogCancelled: () -> Unit) : T { + + dialogManager.showDialog { + onDialogCancelled.invoke() + } + + try { + return onNfc.invoke().value + } catch (e: Exception) { + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) + throw e } } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt index bb0ede5f..45130ea3 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt @@ -52,6 +52,7 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) { suspend fun useSession( updateDeviceInfo: Boolean = false, + retryOnNfcFailure: Boolean = true, block: (YubiKitFidoSession) -> T ): T { FidoManager.updateDeviceInfo.set(updateDeviceInfo) @@ -61,7 +62,8 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) { onDialogCancelled = { pendingAction?.invoke(Result.failure(CancellationException())) pendingAction = null - } + }, + retryOnNfcFailure = retryOnNfcFailure ) } @@ -73,7 +75,7 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) { block(YubiKitFidoSession(it)) }.also { if (updateDeviceInfo) { - scheduleDeviceInfoUpdate(getDeviceInfo(device)) + deviceManager.setDeviceInfo(getDeviceInfo(device)) } } @@ -97,14 +99,6 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) { } } - fun scheduleDeviceInfoUpdate(deviceInfo: Info?) { - deviceInfoTimer?.cancel() - deviceInfoTimer = Timer("update-device-info", false).schedule(500) { - logger.debug("Updating device info") - deviceManager.setDeviceInfo(deviceInfo) - } - } - companion object { private val logger = LoggerFactory.getLogger(FidoConnectionHelper::class.java) } 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 f9381c84..56659c37 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 @@ -199,7 +199,7 @@ class FidoManager( } if (updateDeviceInfo.getAndSet(false)) { - connectionHelper.scheduleDeviceInfoUpdate(getDeviceInfo(device)) + deviceManager.setDeviceInfo(getDeviceInfo(device)) } } catch (e: Exception) { // something went wrong, try to get DeviceInfo from any available connection type diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt index 17a91bc6..cd40aabd 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt @@ -40,6 +40,7 @@ class ManagementConnectionHelper( action?.invoke(Result.failure(CancellationException())) action = null }, + retryOnNfcFailure = false ) private suspend fun useSessionUsb( 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 c453c489..b7b3714e 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 @@ -192,6 +192,7 @@ class OathManager( ) "deleteAccount" -> deleteAccount(args["credentialId"] as String) + "addAccountToAny" -> addAccountToAny( args["uri"] as String, args["requireTouch"] as Boolean @@ -214,7 +215,7 @@ class OathManager( oathChannel.setMethodCallHandler(null) oathViewModel.clearSession() oathViewModel.updateCredentials(mapOf()) - pendingAction?.invoke(Result.failure(Exception())) + pendingAction?.invoke(Result.failure(ContextDisposedException())) coroutineScope.cancel() } @@ -299,7 +300,7 @@ class OathManager( ) if (updateDeviceInfo.getAndSet(false)) { - scheduleDeviceInfoUpdate(getDeviceInfo(device)) + deviceManager.setDeviceInfo(getDeviceInfo(device)) } } catch (e: Exception) { // OATH not enabled/supported, try to get DeviceInfo over other USB interfaces @@ -317,7 +318,7 @@ class OathManager( val credentialData: CredentialData = CredentialData.parseUri(URI.create(uri)) addToAny = true - return useSessionNfc { session -> + return useOathSession(retryOnNfcFailure = false) { session -> // We need to check for duplicates here since we haven't yet read the credentials if (session.credentials.any { it.id.contentEquals(credentialData.id) }) { throw IllegalArgumentException() @@ -337,7 +338,7 @@ class OathManager( logger.debug("Added cred {}", credential) jsonSerializer.encodeToString(addedCred) - }.value + } } private suspend fun addAccountsToAny( @@ -347,7 +348,7 @@ class OathManager( logger.trace("Adding following accounts: {}", uris) addToAny = true - return useOathSession { session -> + return useOathSession(retryOnNfcFailure = false) { session -> var successCount = 0 for (index in uris.indices) { @@ -680,17 +681,10 @@ class OathManager( return credential.data } - fun scheduleDeviceInfoUpdate(deviceInfo: Info?) { - deviceInfoTimer?.cancel() - deviceInfoTimer = Timer("update-device-info", false).schedule(500) { - logger.debug("Updating device info") - deviceManager.setDeviceInfo(deviceInfo) - } - } - private suspend fun useOathSession( unlock: Boolean = true, updateDeviceInfo: Boolean = false, + retryOnNfcFailure: Boolean = true, block: (YubiKitOathSession) -> T ): T { // callers can decide whether the session should be unlocked first @@ -704,6 +698,7 @@ class OathManager( pendingAction?.invoke(Result.failure(CancellationException())) pendingAction = null }, + retryOnNfcFailure = retryOnNfcFailure ) } @@ -715,7 +710,7 @@ class OathManager( block(getOathSession(it)) }.also { if (updateDeviceInfo) { - scheduleDeviceInfoUpdate(getDeviceInfo(device)) + deviceManager.setDeviceInfo(getDeviceInfo(device)) } } diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index b2850c23..9398c20e 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -393,7 +393,8 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { Future reset() async => invoke('reset', { 'operationSuccess': l10n.s_nfc_fido_reset_success, - 'operationFailure': l10n.s_nfc_fido_reset_failure + 'operationFailure': l10n.s_nfc_fido_reset_failure, + 'showSuccess': true }); Future setPin(String newPin, {String? oldPin}) async => @@ -404,7 +405,8 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { : l10n.s_pin_set, 'operationFailure': oldPin != null ? l10n.s_nfc_fido_change_pin_failure - : l10n.s_nfc_fido_set_pin_failure + : l10n.s_nfc_fido_set_pin_failure, + 'showSuccess': true }); Future unlock(String pin) async => invoke('unlock', { diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index a58895eb..de1fe79d 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -177,7 +177,7 @@ Exception handlePlatformException( return CancellationException(); } case PlatformException pe: - if (pe.code == 'JobCancellationException') { + if (pe.code == 'ContextDisposedException') { // pop stack to show FIDO view toast(l10n.l_add_account_func_missing, popStack: true); return CancellationException(); @@ -193,40 +193,31 @@ final addCredentialToAnyProvider = Provider((ref) => (Uri credentialUri, {bool requireTouch = false}) async { final oath = ref.watch(_oathMethodsProvider.notifier); try { - String resultString = await oath.addAccountToAny(credentialUri, - requireTouch: requireTouch); - - var result = jsonDecode(resultString); + var result = jsonDecode(await oath.addAccountToAny( + credentialUri, + requireTouch: requireTouch, + )); return OathCredential.fromJson(result['credential']); } on PlatformException catch (pe) { + _log.error('Received exception: $pe'); throw handlePlatformException(ref, pe); } }); -final addCredentialsToAnyProvider = Provider( - (ref) => (List credentialUris, List touchRequired) async { - final oath = ref.read(_oathMethodsProvider.notifier); - try { - _log.debug( - 'Calling android with ${credentialUris.length} credentials to be added'); - - String resultString = - await oath.addAccounts(credentialUris, touchRequired); - - _log.debug('Call result: $resultString'); - var result = jsonDecode(resultString); - return result['succeeded'] == credentialUris.length; - } on PlatformException catch (pe) { - var decodedException = pe.decode(); - if (decodedException is CancellationException) { - _log.debug('User cancelled adding multiple accounts'); - } else { - _log.error('Failed to add multiple accounts.', pe); - } - - throw decodedException; - } - }); +final addCredentialsToAnyProvider = Provider((ref) => + (List credentialUris, List touchRequired) async { + final oath = ref.read(_oathMethodsProvider.notifier); + try { + _log.debug( + 'Calling android with ${credentialUris.length} credentials to be added'); + var result = + jsonDecode(await oath.addAccounts(credentialUris, touchRequired)); + return result['succeeded'] == credentialUris.length; + } on PlatformException catch (pe) { + _log.error('Received exception: $pe'); + throw handlePlatformException(ref, pe); + } + }); final androidCredentialListProvider = StateNotifierProvider.autoDispose .family?, DevicePath>( @@ -360,7 +351,8 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future reset() async => invoke('reset', { 'operationSuccess': l10n.s_nfc_oath_reset_success, - 'operationFailure': l10n.s_nfc_oath_reset_failure + 'operationFailure': l10n.s_nfc_oath_reset_failure, + 'showSuccess': true }); Future unlock(String password, {bool remember = false}) async => @@ -379,6 +371,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'operationFailure': current != null ? l10n.s_nfc_oath_change_password_failure : l10n.s_nfc_oath_set_password_failure, + 'showSuccess': true }); Future unsetPassword(String current) async => @@ -429,6 +422,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { }, 'operationSuccess': l10n.s_account_added, 'operationFailure': l10n.s_nfc_oath_add_account_failure, + 'showSuccess': true }); Future deleteAccount(OathCredential credential) async => diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index 63afad57..9107eb71 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -57,21 +57,36 @@ class MainPage extends ConsumerWidget { } // If the current device changes, we need to pop any open dialogs. - ref.listen>(currentDeviceDataProvider, (_, __) { - Navigator.of(context).popUntil((route) { - return route.isFirst || - [ - 'device_picker', - 'settings', - 'about', - 'licenses', - 'user_interaction_prompt', - 'oath_add_account', - 'oath_icon_pack_dialog', - 'android_qr_scanner_view', - 'android_alert_dialog' - ].contains(route.settings.name); - }); + ref.listen>(currentDeviceDataProvider, + (prev, next) { + var canPop = true; + if ((next.value != null) && (prev?.value != null)) { + // if there is change only in fipsApproved, don't pop anything + var nextInfo = next.value!.info; + var prevInfo = prev!.value!.info; + + canPop = + prevInfo.copyWith(fipsApproved: nextInfo.fipsApproved) != nextInfo; + } else if (next.hasValue && (prev != null && prev.isLoading)) { + canPop = false; + } + debugPrint('Should pop: $canPop'); + + if (canPop) { + Navigator.of(context).popUntil((route) { + return route.isFirst || + [ + 'device_picker', + 'settings', + 'about', + 'licenses', + 'user_interaction_prompt', + 'oath_add_account', + 'oath_icon_pack_dialog', + 'android_qr_scanner_view', + ].contains(route.settings.name); + }); + } }); final deviceNode = ref.watch(currentDeviceProvider); @@ -155,6 +170,7 @@ class MainPage extends ConsumerWidget { ); } + debugPrint('showing section $section'); return switch (section) { Section.home => HomeScreen(data), Section.accounts => OathScreen(data.node.path), From 4e1abf2b3b4db82d99555ecfeee65ae1e3b8a328 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 5 Sep 2024 15:09:51 +0200 Subject: [PATCH 25/50] handle app context changes --- .../kotlin/com/yubico/authenticator/MainActivity.kt | 12 ++++++++---- .../com/yubico/authenticator/device/DeviceManager.kt | 10 +++++++++- .../com/yubico/authenticator/oath/OathManager.kt | 1 + lib/exception/platform_exception_decoder.dart | 6 ++++++ 4 files changed, 24 insertions(+), 5 deletions(-) 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 2f5dfacf..9a94302f 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -357,6 +357,7 @@ class MainActivity : FlutterFragmentActivity() { deviceManager.setDeviceInfo(deviceInfo, scpKeyParams) val supportedContexts = DeviceManager.getSupportedContexts(deviceInfo) logger.debug("Connected key supports: {}", supportedContexts) + var switchedContext: Boolean = false if (!supportedContexts.contains(viewModel.appContext.value)) { val preferredContext = DeviceManager.getPreferredContext(supportedContexts) logger.debug( @@ -364,17 +365,17 @@ class MainActivity : FlutterFragmentActivity() { viewModel.appContext.value, preferredContext ) - switchContext(preferredContext) + switchedContext = switchContext(preferredContext) } if (contextManager == null && supportedContexts.isNotEmpty()) { - switchContext(DeviceManager.getPreferredContext(supportedContexts)) + switchedContext = switchContext(DeviceManager.getPreferredContext(supportedContexts)) } contextManager?.let { try { it.processYubiKey(device) - if (device is NfcYubiKeyDevice) { + if (!switchedContext && device is NfcYubiKeyDevice) { appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_FINISHED) device.remove { appMethodChannel.nfcActivityStateChanged(NfcActivityState.READY) @@ -440,7 +441,8 @@ class MainActivity : FlutterFragmentActivity() { } } - private fun switchContext(appContext: OperationContext) { + private fun switchContext(appContext: OperationContext) : Boolean { + var switchHappened = false // TODO: refactor this when more OperationContext are handled // only recreate the contextManager object if it cannot be reused if (appContext == OperationContext.Home || @@ -454,6 +456,7 @@ class MainActivity : FlutterFragmentActivity() { } else { contextManager?.dispose() contextManager = null + switchHappened = true } if (contextManager == null) { @@ -481,6 +484,7 @@ class MainActivity : FlutterFragmentActivity() { else -> null } } + return switchHappened } override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) { diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt index 6af9b4df..06233ff1 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt @@ -20,6 +20,7 @@ import androidx.collection.ArraySet import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.Observer +import com.yubico.authenticator.ContextDisposedException import com.yubico.authenticator.DialogManager import com.yubico.authenticator.MainActivity import com.yubico.authenticator.MainViewModel @@ -214,10 +215,17 @@ class DeviceManager( } catch (e: Exception) { logger.debug("NFC action failed, asking to try again. Failure: ", e) - if (e is CancellationException) { throw e } + + if (e is ContextDisposedException) { + // the key does not have the needed context anymore + // we cannot continue + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) + throw e + } + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) } 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 b7b3714e..e782cd4d 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 @@ -216,6 +216,7 @@ class OathManager( oathViewModel.clearSession() oathViewModel.updateCredentials(mapOf()) pendingAction?.invoke(Result.failure(ContextDisposedException())) + pendingAction = null coroutineScope.cancel() } diff --git a/lib/exception/platform_exception_decoder.dart b/lib/exception/platform_exception_decoder.dart index f45f56da..1a71812c 100644 --- a/lib/exception/platform_exception_decoder.dart +++ b/lib/exception/platform_exception_decoder.dart @@ -24,11 +24,17 @@ extension Decoder on PlatformException { bool _isApduException() => code == 'ApduException'; + bool _isContextDisposed() => code == 'ContextDisposedException'; + Exception decode() { if (_isCancellation()) { return CancellationException(); } + if (_isContextDisposed()) { + return CancellationException(); + } + if (message != null && _isApduException()) { final regExp = RegExp( r'^com.yubico.yubikit.core.smartcard.ApduException: APDU error: 0x(.*)$'); From d677dfed69c930fe941fbbceb54c1ca8ed4d020b Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 5 Sep 2024 18:14:43 +0200 Subject: [PATCH 26/50] improve exception handling --- .../com/yubico/authenticator/MainActivity.kt | 30 +++++++++---------- .../authenticator/device/DeviceManager.kt | 6 ++-- .../authenticator/yubikit/ConnectionHelper.kt | 12 +++----- 3 files changed, 20 insertions(+), 28 deletions(-) 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 9a94302f..1e5dfc4e 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -322,10 +322,10 @@ class MainActivity : FlutterFragmentActivity() { appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_STARTED) } - val scpKeyParams : ScpKeyParams? = try { - // If NFC and FIPS check for SCP11b key - if (device.transport == Transport.NFC && deviceInfo.fipsCapable != 0) { - logger.debug("Checking for usable SCP11b key...") + // If NFC and FIPS check for SCP11b key + if (device.transport == Transport.NFC && deviceInfo.fipsCapable != 0) { + logger.debug("Checking for usable SCP11b key...") + deviceManager.scpKeyParams = try { device.withConnection { connection -> val scp = SecurityDomainSession(connection) val keyRef = scp.keyInformation.keys.firstOrNull { it.kid == ScpKid.SCP11b } @@ -339,22 +339,22 @@ class MainActivity : FlutterFragmentActivity() { logger.debug("Found SCP11b key: {}", keyRef) } } - } else null - } catch (e: Exception) { - logger.debug("Exception while getting scp keys: ", e) - if (device is NfcYubiKeyDevice) { - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) + } catch (e: Exception) { + logger.debug("Exception while getting scp keys: ", e) + if (device is NfcYubiKeyDevice) { + appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) + } + null } - null } // this YubiKey provides SCP11b key but the phone cannot perform AESCMAC - if (scpKeyParams != null && !supportsScp11b) { + if (deviceManager.scpKeyParams != null && !supportsScp11b) { deviceManager.setDeviceInfo(noScp11bNfcSupport) return } - deviceManager.setDeviceInfo(deviceInfo, scpKeyParams) + deviceManager.setDeviceInfo(deviceInfo) val supportedContexts = DeviceManager.getSupportedContexts(deviceInfo) logger.debug("Connected key supports: {}", supportedContexts) var switchedContext: Boolean = false @@ -381,12 +381,10 @@ class MainActivity : FlutterFragmentActivity() { appMethodChannel.nfcActivityStateChanged(NfcActivityState.READY) } } - } catch (e: IOException) { - logger.debug("Caught IOException during YubiKey processing: ", e) + } catch (e: Exception) { + logger.debug("Caught Exception during YubiKey processing: ", e) appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) } - - } } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt index 06233ff1..0eab6c77 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt @@ -172,9 +172,9 @@ class DeviceManager( appViewModel.connectedYubiKey.removeObserver(usbObserver) } - fun setDeviceInfo(deviceInfo: Info?, scpKeyParams: ScpKeyParams? = null) { + fun setDeviceInfo(deviceInfo: Info?) { appViewModel.setDeviceInfo(deviceInfo) - this.scpKeyParams = scpKeyParams + this.scpKeyParams = null } fun isUsbKeyConnected(): Boolean { @@ -228,8 +228,6 @@ class DeviceManager( appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) } - - } } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/ConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/ConnectionHelper.kt index 69512c95..0c20ff77 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/ConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/ConnectionHelper.kt @@ -23,13 +23,9 @@ import kotlin.coroutines.suspendCoroutine suspend inline fun YubiKeyDevice.withConnection( crossinline block: (C) -> T ): T = suspendCoroutine { continuation -> - try { - requestConnection(C::class.java) { - continuation.resumeWith(runCatching { - block(it.value) - }) - } - } catch (_: Exception) { - // ignored + requestConnection(C::class.java) { + continuation.resumeWith(runCatching { + block(it.value) + }) } } From 2de217ddf0ac007e4ab8b9e7176c9f1b055b05d3 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 5 Sep 2024 19:13:16 +0200 Subject: [PATCH 27/50] refactor autoclose views --- lib/android/tap_request_dialog.dart | 26 ++-- lib/android/views/nfc/models.dart | 4 +- .../nfc/nfc_activity_command_listener.dart | 21 ++-- .../views/nfc/nfc_activity_overlay.dart | 93 +++----------- .../views/nfc/nfc_auto_close_widget.dart | 53 ++++++++ lib/android/views/nfc/nfc_content_widget.dart | 8 +- .../nfc/nfc_count_down_close_widget.dart | 114 ++++++++++++++++++ lib/app/views/main_page.dart | 1 - lib/l10n/app_en.arb | 2 +- 9 files changed, 213 insertions(+), 109 deletions(-) create mode 100644 lib/android/views/nfc/nfc_auto_close_widget.dart create mode 100644 lib/android/views/nfc/nfc_count_down_close_widget.dart diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 25b0f0a7..8f646018 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -26,6 +26,7 @@ import '../app/state.dart'; import 'state.dart'; import 'views/nfc/models.dart'; import 'views/nfc/nfc_activity_overlay.dart'; +import 'views/nfc/nfc_auto_close_widget.dart'; import 'views/nfc/nfc_content_widget.dart'; import 'views/nfc/nfc_failure_icon.dart'; import 'views/nfc/nfc_progress_bar.dart'; @@ -53,7 +54,7 @@ class _DialogProvider extends Notifier { // setup properties for ad-hoc action viewNotifier.setDialogProperties( operationFailure: l10n.l_nfc_read_key_failure, - showSuccess: false, + showSuccess: true, showCloseButton: false); } @@ -82,24 +83,19 @@ class _DialogProvider extends Notifier { }); break; case NfcActivity.processingFinished: - explicitAction = false; // next action might not be explicit processingTimer?.cancel(); final showSuccess = properties.showSuccess ?? false; allowMessages = !showSuccess; if (showSuccess) { - notifier.sendCommand( - updateNfcView(NfcActivityClosingCountdownWidgetView( - closeInSec: 5, - child: NfcContentWidget( - title: properties.operationSuccess, - subtitle: l10n.s_nfc_remove_key, - icon: const NfcIconSuccess(), - ), - ))); - } else { - // directly hide - notifier.sendCommand(hideNfcView); + notifier.sendCommand(autoClose( + title: properties.operationSuccess, + subtitle: l10n.s_nfc_remove_key, + icon: const NfcIconSuccess(), + )); } + // hide + notifier.sendCommand(hideNfcView(explicitAction ? 5000 : 400)); + explicitAction = false; // next action might not be explicit break; case NfcActivity.processingInterrupted: processingTimer?.cancel(); @@ -148,7 +144,7 @@ class _DialogProvider extends Notifier { } void closeDialog() { - ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView); + ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView()); } void cancelDialog() async { diff --git a/lib/android/views/nfc/models.dart b/lib/android/views/nfc/models.dart index e9722106..057b98bc 100644 --- a/lib/android/views/nfc/models.dart +++ b/lib/android/views/nfc/models.dart @@ -63,8 +63,8 @@ class NfcEventCommand with _$NfcEventCommand { }) = _NfcEventCommand; } -final hideNfcView = - NfcEventCommand(event: const NfcHideViewEvent(timeoutMs: 0)); +NfcEventCommand hideNfcView([int timeoutMs = 0]) => + NfcEventCommand(event: NfcHideViewEvent(timeoutMs: timeoutMs)); NfcEventCommand updateNfcView(Widget child) => NfcEventCommand(event: NfcUpdateViewEvent(child: child)); diff --git a/lib/android/views/nfc/nfc_activity_command_listener.dart b/lib/android/views/nfc/nfc_activity_command_listener.dart index b7cd94d8..b5e1a628 100644 --- a/lib/android/views/nfc/nfc_activity_command_listener.dart +++ b/lib/android/views/nfc/nfc_activity_command_listener.dart @@ -19,6 +19,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; import '../../../app/logging.dart'; +import '../../../app/state.dart'; import '../../tap_request_dialog.dart'; import 'models.dart'; import 'nfc_activity_overlay.dart'; @@ -46,12 +47,12 @@ class _NfcEventCommandListener { case (NfcUpdateViewEvent a): _ref.read(nfcViewNotifier.notifier).update(a.child); break; - case (NfcHideViewEvent _): - _hide(context); + case (NfcHideViewEvent e): + _hide(context, Duration(milliseconds: e.timeoutMs)); break; case (NfcCancelEvent _): _ref.read(androidDialogProvider.notifier).cancelDialog(); - _hide(context); + _hide(context, Duration.zero); break; } }); @@ -75,10 +76,14 @@ class _NfcEventCommandListener { } } - void _hide(BuildContext context) { - if (_ref.read(nfcViewNotifier.select((s) => s.isShowing))) { - Navigator.of(context).pop('HIDDEN'); - _ref.read(nfcViewNotifier.notifier).setShowing(false); - } + void _hide(BuildContext context, Duration timeout) { + Future.delayed(timeout, () { + _ref.read(withContextProvider)((context) async { + if (_ref.read(nfcViewNotifier.select((s) => s.isShowing))) { + Navigator.of(context).pop('HIDDEN'); + _ref.read(nfcViewNotifier.notifier).setShowing(false); + } + }); + }); } } diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/views/nfc/nfc_activity_overlay.dart index 764b8929..52d40895 100644 --- a/lib/android/views/nfc/nfc_activity_overlay.dart +++ b/lib/android/views/nfc/nfc_activity_overlay.dart @@ -1,10 +1,23 @@ -import 'dart:async'; +/* + * 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. + */ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:material_symbols_icons/symbols.dart'; -import '../../state.dart'; import 'models.dart'; final nfcEventCommandNotifier = @@ -25,82 +38,6 @@ class _NfcEventCommandNotifier extends Notifier { final nfcViewNotifier = NotifierProvider<_NfcViewNotifier, NfcView>(_NfcViewNotifier.new); -class NfcActivityClosingCountdownWidgetView extends ConsumerStatefulWidget { - final int closeInSec; - final Widget child; - - const NfcActivityClosingCountdownWidgetView( - {super.key, required this.child, this.closeInSec = 3}); - - @override - ConsumerState createState() => - _NfcActivityClosingCountdownWidgetViewState(); -} - -class _NfcActivityClosingCountdownWidgetViewState - extends ConsumerState { - late int counter; - late Timer? timer; - bool shouldHide = false; - - @override - Widget build(BuildContext context) { - ref.listen(androidNfcActivityProvider, (previous, current) { - if (current == NfcActivity.ready) { - timer?.cancel(); - hideNow(); - } - }); - - return Stack( - fit: StackFit.loose, - children: [ - Center(child: widget.child), - Positioned( - bottom: 0, - right: 0, - child: counter > 0 - ? Padding( - padding: const EdgeInsets.all(8.0), - child: Text('Closing in $counter'), - ) - : const SizedBox(), - ) - ], - ); - } - - @override - void initState() { - super.initState(); - counter = widget.closeInSec; - timer = Timer(const Duration(seconds: 1), onTimer); - } - - @override - void dispose() { - timer?.cancel(); - super.dispose(); - } - - void onTimer() async { - timer?.cancel(); - setState(() { - counter--; - }); - - if (counter > 0) { - timer = Timer(const Duration(seconds: 1), onTimer); - } else { - hideNow(); - } - } - - void hideNow() { - ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView); - } -} - class _NfcViewNotifier extends Notifier { @override NfcView build() { diff --git a/lib/android/views/nfc/nfc_auto_close_widget.dart b/lib/android/views/nfc/nfc_auto_close_widget.dart new file mode 100644 index 00000000..5006bf7a --- /dev/null +++ b/lib/android/views/nfc/nfc_auto_close_widget.dart @@ -0,0 +1,53 @@ +/* + * 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. + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../state.dart'; +import 'models.dart'; +import 'nfc_activity_overlay.dart'; +import 'nfc_content_widget.dart'; + +NfcEventCommand autoClose({ + String? title, + String? subtitle, + Widget? icon, +}) => + updateNfcView(_NfcAutoCloseWidget( + child: NfcContentWidget( + title: title, + subtitle: subtitle, + icon: icon, + ), + )); + +class _NfcAutoCloseWidget extends ConsumerWidget { + final Widget child; + + const _NfcAutoCloseWidget({required this.child}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + ref.listen(androidNfcActivityProvider, (previous, current) { + if (current == NfcActivity.ready) { + ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView(0)); + } + }); + + return child; + } +} diff --git a/lib/android/views/nfc/nfc_content_widget.dart b/lib/android/views/nfc/nfc_content_widget.dart index 4e8fe038..2ba897be 100644 --- a/lib/android/views/nfc/nfc_content_widget.dart +++ b/lib/android/views/nfc/nfc_content_widget.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../app/state.dart'; +import 'nfc_progress_bar.dart'; class NfcContentWidget extends ConsumerWidget { final String? title; final String? subtitle; - final Widget icon; + final Widget? icon; - const NfcContentWidget( - {super.key, this.title, this.subtitle, required this.icon}); + const NfcContentWidget({super.key, this.title, this.subtitle, this.icon}); @override Widget build(BuildContext context, WidgetRef ref) { @@ -26,7 +26,7 @@ class NfcContentWidget extends ConsumerWidget { textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 32), - icon, + icon ?? const NfcIconProgressBar(false), const SizedBox(height: 24) ], ), diff --git a/lib/android/views/nfc/nfc_count_down_close_widget.dart b/lib/android/views/nfc/nfc_count_down_close_widget.dart new file mode 100644 index 00000000..f6cf39ca --- /dev/null +++ b/lib/android/views/nfc/nfc_count_down_close_widget.dart @@ -0,0 +1,114 @@ +/* + * 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. + */ + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../state.dart'; +import 'models.dart'; +import 'nfc_activity_overlay.dart'; +import 'nfc_content_widget.dart'; + +NfcEventCommand countDownClose({ + int closeInSec = 3, + String? title, + String? subtitle, + Widget? icon, +}) => + updateNfcView(_CountDownCloseWidget( + closeInSec: closeInSec, + child: NfcContentWidget( + title: title, + subtitle: subtitle, + icon: icon, + ), + )); + +class _CountDownCloseWidget extends ConsumerStatefulWidget { + final int closeInSec; + final Widget child; + + const _CountDownCloseWidget({required this.child, required this.closeInSec}); + + @override + ConsumerState<_CountDownCloseWidget> createState() => + _CountDownCloseWidgetState(); +} + +class _CountDownCloseWidgetState extends ConsumerState<_CountDownCloseWidget> { + late int counter; + late Timer? timer; + bool shouldHide = false; + + @override + Widget build(BuildContext context) { + ref.listen(androidNfcActivityProvider, (previous, current) { + if (current == NfcActivity.ready) { + timer?.cancel(); + hideNow(); + } + }); + + return Stack( + fit: StackFit.loose, + children: [ + Center(child: widget.child), + Positioned( + bottom: 0, + right: 0, + child: counter > 0 + ? Padding( + padding: const EdgeInsets.all(8.0), + child: Text('Closing in $counter'), + ) + : const SizedBox(), + ) + ], + ); + } + + @override + void initState() { + super.initState(); + counter = widget.closeInSec; + timer = Timer(const Duration(seconds: 0), onTimer); + } + + @override + void dispose() { + timer?.cancel(); + super.dispose(); + } + + void onTimer() async { + timer?.cancel(); + setState(() { + counter--; + }); + + if (counter > 0) { + timer = Timer(const Duration(seconds: 1), onTimer); + } else { + hideNow(); + } + } + + void hideNow() { + ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView(0)); + } +} diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index 9107eb71..74f92123 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -70,7 +70,6 @@ class MainPage extends ConsumerWidget { } else if (next.hasValue && (prev != null && prev.isLoading)) { canPop = false; } - debugPrint('Should pop: $canPop'); if (canPop) { Navigator.of(context).popUntil((route) { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ca7884b5..ec8d55db 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -943,7 +943,7 @@ "l_nfc_read_key_failure": "Failed to scan YubiKey", - "s_nfc_remove_key": "You can remove YubiKey", + "s_nfc_remove_key": "Remove your YubiKey", "s_nfc_ready_to_scan": "Ready to scan", "s_nfc_accessing_yubikey": "Accessing YubiKey", From 9f183bc745499f0a9cbeb6efdec404cb41752a23 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 6 Sep 2024 08:37:34 +0200 Subject: [PATCH 28/50] minor nfc views refactor --- lib/android/oath/state.dart | 1 + lib/android/tap_request_dialog.dart | 105 ++++++++---------- lib/android/views/nfc/models.dart | 27 ++--- .../nfc/nfc_activity_command_listener.dart | 27 +++-- .../views/nfc/nfc_auto_close_widget.dart | 28 ++--- .../nfc/nfc_count_down_close_widget.dart | 4 +- lib/l10n/app_de.arb | 1 + lib/l10n/app_en.arb | 1 + lib/l10n/app_fr.arb | 1 + lib/l10n/app_ja.arb | 1 + lib/l10n/app_pl.arb | 1 + lib/l10n/app_vi.arb | 1 + 12 files changed, 94 insertions(+), 104 deletions(-) diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index de1fe79d..d19450dd 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -360,6 +360,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'callArgs': {'password': password, 'remember': remember}, 'operationSuccess': l10n.s_nfc_unlock_success, 'operationFailure': l10n.s_nfc_unlock_failure, + 'showSuccess': false, }); Future setPassword(String? current, String password) async => diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 8f646018..94949564 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -39,9 +39,11 @@ final androidDialogProvider = NotifierProvider<_DialogProvider, int>(_DialogProvider.new); class _DialogProvider extends Notifier { - Timer? processingTimer; + Timer? processingViewTimeout; bool explicitAction = false; + late final l10n = ref.read(l10nProvider); + @override int build() { final l10n = ref.read(l10nProvider); @@ -53,6 +55,7 @@ class _DialogProvider extends Notifier { if (!explicitAction) { // setup properties for ad-hoc action viewNotifier.setDialogProperties( + operationSuccess: l10n.s_nfc_scan_success, operationFailure: l10n.l_nfc_read_key_failure, showSuccess: true, showCloseButton: false); @@ -62,45 +65,37 @@ class _DialogProvider extends Notifier { switch (current) { case NfcActivity.processingStarted: - viewNotifier.setDialogProperties(showCloseButton: false); - processingTimer?.cancel(); - final timeout = explicitAction ? 300 : 200; - - processingTimer = Timer(Duration(milliseconds: timeout), () { - if (!explicitAction) { - // show the widget - notifier.sendCommand(showNfcView(NfcContentWidget( - title: l10n.s_nfc_accessing_yubikey, - icon: const NfcIconProgressBar(true), - ))); - } else { - // the processing view will only be shown if the timer is still active - notifier.sendCommand(updateNfcView(NfcContentWidget( - title: l10n.s_nfc_accessing_yubikey, - icon: const NfcIconProgressBar(true), - ))); - } + final timeout = explicitAction ? 300 : 500; + processingViewTimeout?.cancel(); + processingViewTimeout = Timer(Duration(milliseconds: timeout), () { + notifier.sendCommand(showAccessingKeyView()); }); break; case NfcActivity.processingFinished: - processingTimer?.cancel(); + processingViewTimeout?.cancel(); final showSuccess = properties.showSuccess ?? false; allowMessages = !showSuccess; if (showSuccess) { notifier.sendCommand(autoClose( - title: properties.operationSuccess, - subtitle: l10n.s_nfc_remove_key, - icon: const NfcIconSuccess(), - )); + title: properties.operationSuccess, + subtitle: explicitAction ? l10n.s_nfc_remove_key : null, + icon: const NfcIconSuccess(), + showIfHidden: false)); + // hide } - // hide - notifier.sendCommand(hideNfcView(explicitAction ? 5000 : 400)); + notifier.sendCommand(hideNfcView(Duration( + milliseconds: !showSuccess + ? 0 + : explicitAction + ? 5000 + : 400))); + explicitAction = false; // next action might not be explicit break; case NfcActivity.processingInterrupted: - processingTimer?.cancel(); + processingViewTimeout?.cancel(); viewNotifier.setDialogProperties(showCloseButton: true); - notifier.sendCommand(updateNfcView(NfcContentWidget( + notifier.sendCommand(setNfcView(NfcContentWidget( title: properties.operationFailure, subtitle: l10n.s_nfc_scan_again, icon: const NfcIconFailure(), @@ -119,14 +114,7 @@ class _DialogProvider extends Notifier { switch (call.method) { case 'show': explicitAction = true; - - // we want to show the close button - viewNotifier.setDialogProperties(showCloseButton: true); - - notifier.sendCommand(showNfcView(NfcContentWidget( - subtitle: l10n.s_nfc_scan_yubikey, - icon: const NfcIconProgressBar(false), - ))); + notifier.sendCommand(showScanKeyView()); break; case 'close': @@ -143,6 +131,26 @@ class _DialogProvider extends Notifier { return 0; } + NfcEventCommand showScanKeyView() { + ref + .read(nfcViewNotifier.notifier) + .setDialogProperties(showCloseButton: true); + return setNfcView(NfcContentWidget( + subtitle: l10n.s_nfc_scan_yubikey, + icon: const NfcIconProgressBar(false), + )); + } + + NfcEventCommand showAccessingKeyView() { + ref + .read(nfcViewNotifier.notifier) + .setDialogProperties(showCloseButton: false); + return setNfcView(NfcContentWidget( + title: l10n.s_nfc_accessing_yubikey, + icon: const NfcIconProgressBar(true), + )); + } + void closeDialog() { ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView()); } @@ -168,28 +176,3 @@ class _DialogProvider extends Notifier { await completer.future; } } - -class MethodChannelHelper { - final ProviderRef _ref; - final MethodChannel _channel; - - const MethodChannelHelper(this._ref, this._channel); - - Future invoke(String method, - {String? operationSuccess, - String? operationFailure, - bool? showSuccess, - bool? showCloseButton, - Map arguments = const {}}) async { - final notifier = _ref.read(nfcViewNotifier.notifier); - notifier.setDialogProperties( - operationSuccess: operationSuccess, - operationFailure: operationFailure, - showSuccess: showSuccess, - showCloseButton: showCloseButton); - - final result = await _channel.invokeMethod(method, arguments); - await _ref.read(androidDialogProvider.notifier).waitForDialogClosed(); - return result; - } -} diff --git a/lib/android/views/nfc/models.dart b/lib/android/views/nfc/models.dart index 057b98bc..d6594304 100644 --- a/lib/android/views/nfc/models.dart +++ b/lib/android/views/nfc/models.dart @@ -23,26 +23,21 @@ class NfcEvent { const NfcEvent(); } -class NfcShowViewEvent extends NfcEvent { - final Widget child; - - const NfcShowViewEvent({required this.child}); -} - class NfcHideViewEvent extends NfcEvent { - final int timeoutMs; + final Duration hideAfter; - const NfcHideViewEvent({required this.timeoutMs}); + const NfcHideViewEvent({required this.hideAfter}); } class NfcCancelEvent extends NfcEvent { const NfcCancelEvent(); } -class NfcUpdateViewEvent extends NfcEvent { +class NfcSetViewEvent extends NfcEvent { final Widget child; + final bool showIfHidden; - const NfcUpdateViewEvent({required this.child}); + const NfcSetViewEvent({required this.child, this.showIfHidden = true}); } @freezed @@ -63,11 +58,9 @@ class NfcEventCommand with _$NfcEventCommand { }) = _NfcEventCommand; } -NfcEventCommand hideNfcView([int timeoutMs = 0]) => - NfcEventCommand(event: NfcHideViewEvent(timeoutMs: timeoutMs)); +NfcEventCommand hideNfcView([Duration hideAfter = Duration.zero]) => + NfcEventCommand(event: NfcHideViewEvent(hideAfter: hideAfter)); -NfcEventCommand updateNfcView(Widget child) => - NfcEventCommand(event: NfcUpdateViewEvent(child: child)); - -NfcEventCommand showNfcView(Widget child) => - NfcEventCommand(event: NfcShowViewEvent(child: child)); +NfcEventCommand setNfcView(Widget child, {bool showIfHidden = true}) => + NfcEventCommand( + event: NfcSetViewEvent(child: child, showIfHidden: showIfHidden)); diff --git a/lib/android/views/nfc/nfc_activity_command_listener.dart b/lib/android/views/nfc/nfc_activity_command_listener.dart index b5e1a628..d821750b 100644 --- a/lib/android/views/nfc/nfc_activity_command_listener.dart +++ b/lib/android/views/nfc/nfc_activity_command_listener.dart @@ -41,14 +41,15 @@ class _NfcEventCommandListener { (previous, action) { _log.debug('Change in command for Overlay: $previous -> $action'); switch (action) { - case (NfcShowViewEvent a): - _show(context, a.child); - break; - case (NfcUpdateViewEvent a): - _ref.read(nfcViewNotifier.notifier).update(a.child); + case (NfcSetViewEvent a): + if (!visible && a.showIfHidden) { + _show(context, a.child); + } else { + _ref.read(nfcViewNotifier.notifier).update(a.child); + } break; case (NfcHideViewEvent e): - _hide(context, Duration(milliseconds: e.timeoutMs)); + _hide(context, e.hideAfter); break; case (NfcCancelEvent _): _ref.read(androidDialogProvider.notifier).cancelDialog(); @@ -61,8 +62,8 @@ class _NfcEventCommandListener { void _show(BuildContext context, Widget child) async { final notifier = _ref.read(nfcViewNotifier.notifier); notifier.update(child); - if (!_ref.read(nfcViewNotifier.select((s) => s.isShowing))) { - notifier.setShowing(true); + if (!visible) { + visible = true; final result = await showModalBottomSheet( context: context, builder: (BuildContext context) { @@ -72,18 +73,22 @@ class _NfcEventCommandListener { // the modal sheet was cancelled by Back button, close button or dismiss _ref.read(androidDialogProvider.notifier).cancelDialog(); } - notifier.setShowing(false); + visible = false; } } void _hide(BuildContext context, Duration timeout) { Future.delayed(timeout, () { _ref.read(withContextProvider)((context) async { - if (_ref.read(nfcViewNotifier.select((s) => s.isShowing))) { + if (visible) { Navigator.of(context).pop('HIDDEN'); - _ref.read(nfcViewNotifier.notifier).setShowing(false); + visible = false; } }); }); } + + bool get visible => _ref.read(nfcViewNotifier.select((s) => s.isShowing)); + set visible(bool showing) => + _ref.read(nfcViewNotifier.notifier).setShowing(showing); } diff --git a/lib/android/views/nfc/nfc_auto_close_widget.dart b/lib/android/views/nfc/nfc_auto_close_widget.dart index 5006bf7a..d0abe3e6 100644 --- a/lib/android/views/nfc/nfc_auto_close_widget.dart +++ b/lib/android/views/nfc/nfc_auto_close_widget.dart @@ -22,18 +22,20 @@ import 'models.dart'; import 'nfc_activity_overlay.dart'; import 'nfc_content_widget.dart'; -NfcEventCommand autoClose({ - String? title, - String? subtitle, - Widget? icon, -}) => - updateNfcView(_NfcAutoCloseWidget( - child: NfcContentWidget( - title: title, - subtitle: subtitle, - icon: icon, - ), - )); +NfcEventCommand autoClose( + {String? title, + String? subtitle, + Widget? icon, + bool showIfHidden = true}) => + setNfcView( + _NfcAutoCloseWidget( + child: NfcContentWidget( + title: title, + subtitle: subtitle, + icon: icon, + ), + ), + showIfHidden: showIfHidden); class _NfcAutoCloseWidget extends ConsumerWidget { final Widget child; @@ -44,7 +46,7 @@ class _NfcAutoCloseWidget extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { ref.listen(androidNfcActivityProvider, (previous, current) { if (current == NfcActivity.ready) { - ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView(0)); + ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView()); } }); diff --git a/lib/android/views/nfc/nfc_count_down_close_widget.dart b/lib/android/views/nfc/nfc_count_down_close_widget.dart index f6cf39ca..6620d893 100644 --- a/lib/android/views/nfc/nfc_count_down_close_widget.dart +++ b/lib/android/views/nfc/nfc_count_down_close_widget.dart @@ -30,7 +30,7 @@ NfcEventCommand countDownClose({ String? subtitle, Widget? icon, }) => - updateNfcView(_CountDownCloseWidget( + setNfcView(_CountDownCloseWidget( closeInSec: closeInSec, child: NfcContentWidget( title: title, @@ -109,6 +109,6 @@ class _CountDownCloseWidgetState extends ConsumerState<_CountDownCloseWidget> { } void hideNow() { - ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView(0)); + ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView()); } } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index b2a60a02..d65a02b2 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -949,6 +949,7 @@ "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, "s_nfc_scan_again": null, + "s_nfc_scan_success": null, "c_nfc_unlock": null, "s_nfc_unlock_processing": null, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ec8d55db..9f9f3191 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -949,6 +949,7 @@ "s_nfc_accessing_yubikey": "Accessing YubiKey", "s_nfc_scan_yubikey": "Scan your YubiKey", "s_nfc_scan_again": "Scan again", + "s_nfc_scan_success": "Scanned your Yubikey", "c_nfc_unlock": "unlock", "s_nfc_unlock_processing": "Unlocking", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d717bc95..fe3b8c42 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -949,6 +949,7 @@ "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, "s_nfc_scan_again": null, + "s_nfc_scan_success": null, "c_nfc_unlock": null, "s_nfc_unlock_processing": null, diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 19fd6718..484c385b 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -949,6 +949,7 @@ "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, "s_nfc_scan_again": null, + "s_nfc_scan_success": null, "c_nfc_unlock": null, "s_nfc_unlock_processing": null, diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 65b8d183..352c4c20 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -949,6 +949,7 @@ "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, "s_nfc_scan_again": null, + "s_nfc_scan_success": null, "c_nfc_unlock": null, "s_nfc_unlock_processing": null, diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index a3036aaa..15247254 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -949,6 +949,7 @@ "s_nfc_accessing_yubikey": null, "s_nfc_scan_yubikey": null, "s_nfc_scan_again": null, + "s_nfc_scan_success": null, "c_nfc_unlock": null, "s_nfc_unlock_processing": null, From 673f5e10eafc34f380883ed53a2bfd7e0821e206 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 6 Sep 2024 08:52:35 +0200 Subject: [PATCH 29/50] remove unused c_ prefix --- check_strings.py | 2 +- lib/l10n/app_de.arb | 4 +--- lib/l10n/app_en.arb | 4 +--- lib/l10n/app_fr.arb | 4 +--- lib/l10n/app_ja.arb | 4 +--- lib/l10n/app_pl.arb | 4 +--- lib/l10n/app_vi.arb | 4 +--- 7 files changed, 7 insertions(+), 19 deletions(-) diff --git a/check_strings.py b/check_strings.py index 2e9f0f0d..cb06fd9a 100755 --- a/check_strings.py +++ b/check_strings.py @@ -68,7 +68,7 @@ def check_misc(k, v): errs = [] if "..." in v: errs.append("'...' should be replaced with '\\u2026'") - if v[0].upper() != v[0] and not k.startswith("c_"): + if v[0].upper() != v[0]: errs.append("Starts with lowercase letter") return errs diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index d65a02b2..93971193 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -11,8 +11,7 @@ "s_": "Ein einzelnes Wort oder wenige Wörter. Sollte kurz genug sein, um auf einer Schaltfläche oder einer Überschrift angezeigt zu werden.", "l_": "Eine einzelne Zeile, kann umbgebrochen werden. Sollte nicht mehr als einen Satz umfassen und nicht mit einem Punkt enden.", "p_": "Ein oder mehrere ganze Sätze mit allen Satzzeichen.", - "q_": "Eine Frage, die mit einem Fragezeichen endet.", - "c_": null + "q_": "Eine Frage, die mit einem Fragezeichen endet." } }, @@ -951,7 +950,6 @@ "s_nfc_scan_again": null, "s_nfc_scan_success": null, - "c_nfc_unlock": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, "s_nfc_unlock_failure": null, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9f9f3191..c1c1bf61 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -11,8 +11,7 @@ "s_": "A single, or few words. Should be short enough to display on a button, or a header.", "l_": "A single line, can be wrapped. Should not be more than one sentence, and not end with a period.", "p_": "One or more full sentences, with proper punctuation.", - "q_": "A question, ending in question mark.", - "c_": "Composable, used in substitutions" + "q_": "A question, ending in question mark." } }, @@ -951,7 +950,6 @@ "s_nfc_scan_again": "Scan again", "s_nfc_scan_success": "Scanned your Yubikey", - "c_nfc_unlock": "unlock", "s_nfc_unlock_processing": "Unlocking", "s_nfc_unlock_success": "Accounts unlocked", "s_nfc_unlock_failure": "Failed to unlock", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index fe3b8c42..129fe16e 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -11,8 +11,7 @@ "s_": "A single, or few words. Should be short enough to display on a button, or a header.", "l_": "A single line, can be wrapped. Should not be more than one sentence, and not end with a period.", "p_": "One or more full sentences, with proper punctuation.", - "q_": "A question, ending in question mark.", - "c_": null + "q_": "A question, ending in question mark." } }, @@ -951,7 +950,6 @@ "s_nfc_scan_again": null, "s_nfc_scan_success": null, - "c_nfc_unlock": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, "s_nfc_unlock_failure": null, diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 484c385b..1a31bb41 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -11,8 +11,7 @@ "s_": "A single, or few words. Should be short enough to display on a button, or a header.", "l_": "A single line, can be wrapped. Should not be more than one sentence, and not end with a period.", "p_": "One or more full sentences, with proper punctuation.", - "q_": "A question, ending in question mark.", - "c_": null + "q_": "A question, ending in question mark." } }, @@ -951,7 +950,6 @@ "s_nfc_scan_again": null, "s_nfc_scan_success": null, - "c_nfc_unlock": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, "s_nfc_unlock_failure": null, diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 352c4c20..88eec6e6 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -11,8 +11,7 @@ "s_": "A single, or few words. Should be short enough to display on a button, or a header.", "l_": "A single line, can be wrapped. Should not be more than one sentence, and not end with a period.", "p_": "One or more full sentences, with proper punctuation.", - "q_": "A question, ending in question mark.", - "c_": null + "q_": "A question, ending in question mark." } }, @@ -951,7 +950,6 @@ "s_nfc_scan_again": null, "s_nfc_scan_success": null, - "c_nfc_unlock": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, "s_nfc_unlock_failure": null, diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index 15247254..b35f8cdb 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -11,8 +11,7 @@ "s_": "Một hoặc vài từ. Nên đủ ngắn để hiển thị trên nút hoặc tiêu đề.", "l_": "Một dòng đơn, có thể xuống dòng. Không nên quá một câu, và không kết thúc bằng dấu chấm.", "p_": "Một hoặc nhiều câu đầy đủ, với dấu chấm câu thích hợp.", - "q_": "Một câu hỏi, kết thúc bằng dấu hỏi chấm.", - "c_": null + "q_": "Một câu hỏi, kết thúc bằng dấu hỏi chấm." } }, @@ -951,7 +950,6 @@ "s_nfc_scan_again": null, "s_nfc_scan_success": null, - "c_nfc_unlock": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, "s_nfc_unlock_failure": null, From abde2646fa302a228c8655bfd852fbbbbeae4591 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 6 Sep 2024 13:14:17 +0200 Subject: [PATCH 30/50] UX updates --- lib/android/fido/state.dart | 21 +---- lib/android/method_channel_notifier.dart | 5 +- lib/android/oath/state.dart | 35 +------- lib/android/tap_request_dialog.dart | 76 ++++++++-------- lib/android/views/nfc/models.dart | 12 ++- lib/android/views/nfc/models.freezed.dart | 87 ++----------------- .../views/nfc/nfc_activity_overlay.dart | 9 +- .../views/nfc/nfc_auto_close_widget.dart | 6 +- lib/android/views/nfc/nfc_content_widget.dart | 32 ++++--- .../nfc/nfc_count_down_close_widget.dart | 6 +- lib/android/views/nfc/nfc_failure_icon.dart | 2 +- lib/android/views/nfc/nfc_progress_bar.dart | 41 ++++++--- lib/app/message.dart | 9 +- lib/l10n/app_de.arb | 45 +--------- lib/l10n/app_en.arb | 45 +--------- lib/l10n/app_fr.arb | 45 +--------- lib/l10n/app_ja.arb | 45 +--------- lib/l10n/app_pl.arb | 45 +--------- lib/l10n/app_vi.arb | 45 +--------- 19 files changed, 137 insertions(+), 474 deletions(-) diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index 9398c20e..f6c91c9d 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -383,37 +383,20 @@ class _FidoMethodChannelNotifier extends MethodChannelNotifier { 'callArgs': { 'rpId': credential.rpId, 'credentialId': credential.credentialId - }, - 'operationSuccess': l10n.s_passkey_deleted, - 'operationFailure': l10n.s_nfc_fido_delete_passkey_failure, - 'showSuccess': true + } }); Future cancelReset() async => invoke('cancelReset'); - Future reset() async => invoke('reset', { - 'operationSuccess': l10n.s_nfc_fido_reset_success, - 'operationFailure': l10n.s_nfc_fido_reset_failure, - 'showSuccess': true - }); + Future reset() async => invoke('reset'); Future setPin(String newPin, {String? oldPin}) async => invoke('setPin', { 'callArgs': {'pin': oldPin, 'newPin': newPin}, - 'operationSuccess': oldPin != null - ? l10n.s_nfc_fido_change_pin_success - : l10n.s_pin_set, - 'operationFailure': oldPin != null - ? l10n.s_nfc_fido_change_pin_failure - : l10n.s_nfc_fido_set_pin_failure, - 'showSuccess': true }); Future unlock(String pin) async => invoke('unlock', { 'callArgs': {'pin': pin}, - 'operationSuccess': l10n.s_nfc_unlock_success, - 'operationFailure': l10n.s_nfc_unlock_failure, - 'showSuccess': false }); Future enableEnterpriseAttestation() async => diff --git a/lib/android/method_channel_notifier.dart b/lib/android/method_channel_notifier.dart index ea07220b..2f6e856f 100644 --- a/lib/android/method_channel_notifier.dart +++ b/lib/android/method_channel_notifier.dart @@ -31,10 +31,7 @@ class MethodChannelNotifier extends Notifier { Future invoke(String name, [Map params = const {}]) async { final notifier = ref.read(nfcViewNotifier.notifier); - notifier.setDialogProperties( - operationSuccess: params['operationSuccess'], - operationFailure: params['operationFailure'], - showSuccess: params['showSuccess']); + notifier.setDialogProperties(); final result = await _channel.invokeMethod(name, params['callArgs']); await ref.read(androidDialogProvider.notifier).waitForDialogClosed(); diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index d19450dd..166a8350 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -349,37 +349,21 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { @override void build() {} - Future reset() async => invoke('reset', { - 'operationSuccess': l10n.s_nfc_oath_reset_success, - 'operationFailure': l10n.s_nfc_oath_reset_failure, - 'showSuccess': true - }); + Future reset() async => invoke('reset'); Future unlock(String password, {bool remember = false}) async => invoke('unlock', { 'callArgs': {'password': password, 'remember': remember}, - 'operationSuccess': l10n.s_nfc_unlock_success, - 'operationFailure': l10n.s_nfc_unlock_failure, - 'showSuccess': false, }); Future setPassword(String? current, String password) async => invoke('setPassword', { 'callArgs': {'current': current, 'password': password}, - 'operationSuccess': current != null - ? l10n.s_nfc_oath_change_password_success - : l10n.s_password_set, - 'operationFailure': current != null - ? l10n.s_nfc_oath_change_password_failure - : l10n.s_nfc_oath_set_password_failure, - 'showSuccess': true }); Future unsetPassword(String current) async => invoke('unsetPassword', { 'callArgs': {'current': current}, - 'operationSuccess': l10n.s_password_removed, - 'operationFailure': l10n.s_nfc_oath_remove_password_failure, }); Future forgetPassword() async => invoke('forgetPassword'); @@ -387,8 +371,6 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { Future calculate(OathCredential credential) async => invoke('calculate', { 'callArgs': {'credentialId': credential.id}, - 'operationSuccess': l10n.s_nfc_oath_calculate_code_success, - 'operationFailure': l10n.s_nfc_oath_calculate_code_failure, }); Future addAccount(Uri credentialUri, @@ -398,9 +380,6 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uri': credentialUri.toString(), 'requireTouch': requireTouch }, - 'operationSuccess': l10n.s_account_added, - 'operationFailure': l10n.s_nfc_oath_add_account_failure, - 'showSuccess': true }); Future addAccounts( @@ -409,9 +388,7 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'callArgs': { 'uris': credentialUris, 'requireTouch': touchRequired, - }, - 'operationSuccess': l10n.s_nfc_oath_add_multiple_accounts_success, - 'operationFailure': l10n.s_nfc_oath_add_multiple_accounts_failure, + } }); Future addAccountToAny(Uri credentialUri, @@ -421,17 +398,11 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'uri': credentialUri.toString(), 'requireTouch': requireTouch }, - 'operationSuccess': l10n.s_account_added, - 'operationFailure': l10n.s_nfc_oath_add_account_failure, - 'showSuccess': true }); Future deleteAccount(OathCredential credential) async => invoke('deleteAccount', { 'callArgs': {'credentialId': credential.id}, - 'operationSuccess': l10n.s_account_deleted, - 'operationFailure': l10n.s_nfc_oath_delete_account_failure, - 'showSuccess': true }); Future renameAccount( @@ -442,7 +413,5 @@ class _OathMethodChannelNotifier extends MethodChannelNotifier { 'name': name, 'issuer': issuer }, - 'operationSuccess': l10n.s_account_renamed, - 'operationFailure': l10n.s_nfc_oath_rename_account_failure, }); } diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 94949564..e4d053db 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -21,12 +21,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; import '../app/logging.dart'; -import '../app/message.dart'; import '../app/state.dart'; import 'state.dart'; import 'views/nfc/models.dart'; import 'views/nfc/nfc_activity_overlay.dart'; -import 'views/nfc/nfc_auto_close_widget.dart'; import 'views/nfc/nfc_content_widget.dart'; import 'views/nfc/nfc_failure_icon.dart'; import 'views/nfc/nfc_progress_bar.dart'; @@ -46,7 +44,6 @@ class _DialogProvider extends Notifier { @override int build() { - final l10n = ref.read(l10nProvider); final viewNotifier = ref.read(nfcViewNotifier.notifier); ref.listen(androidNfcActivityProvider, (previous, current) { @@ -54,52 +51,27 @@ class _DialogProvider extends Notifier { if (!explicitAction) { // setup properties for ad-hoc action - viewNotifier.setDialogProperties( - operationSuccess: l10n.s_nfc_scan_success, - operationFailure: l10n.l_nfc_read_key_failure, - showSuccess: true, - showCloseButton: false); + viewNotifier.setDialogProperties(showCloseButton: false); } - final properties = ref.read(nfcViewNotifier); - switch (current) { case NfcActivity.processingStarted: final timeout = explicitAction ? 300 : 500; processingViewTimeout?.cancel(); processingViewTimeout = Timer(Duration(milliseconds: timeout), () { - notifier.sendCommand(showAccessingKeyView()); + notifier.sendCommand(showScanning()); }); break; case NfcActivity.processingFinished: processingViewTimeout?.cancel(); - final showSuccess = properties.showSuccess ?? false; - allowMessages = !showSuccess; - if (showSuccess) { - notifier.sendCommand(autoClose( - title: properties.operationSuccess, - subtitle: explicitAction ? l10n.s_nfc_remove_key : null, - icon: const NfcIconSuccess(), - showIfHidden: false)); - // hide - } - notifier.sendCommand(hideNfcView(Duration( - milliseconds: !showSuccess - ? 0 - : explicitAction - ? 5000 - : 400))); + notifier.sendCommand(showDone()); + notifier.sendCommand(hideNfcView(const Duration(milliseconds: 400))); explicitAction = false; // next action might not be explicit break; case NfcActivity.processingInterrupted: processingViewTimeout?.cancel(); - viewNotifier.setDialogProperties(showCloseButton: true); - notifier.sendCommand(setNfcView(NfcContentWidget( - title: properties.operationFailure, - subtitle: l10n.s_nfc_scan_again, - icon: const NfcIconFailure(), - ))); + notifier.sendCommand(showFailed()); break; case NfcActivity.notActive: _log.debug('Received not handled notActive'); @@ -114,7 +86,7 @@ class _DialogProvider extends Notifier { switch (call.method) { case 'show': explicitAction = true; - notifier.sendCommand(showScanKeyView()); + notifier.sendCommand(showTapYourYubiKey()); break; case 'close': @@ -131,26 +103,54 @@ class _DialogProvider extends Notifier { return 0; } - NfcEventCommand showScanKeyView() { + NfcEventCommand showTapYourYubiKey() { ref .read(nfcViewNotifier.notifier) .setDialogProperties(showCloseButton: true); return setNfcView(NfcContentWidget( - subtitle: l10n.s_nfc_scan_yubikey, + title: l10n.s_nfc_ready_to_scan, + subtitle: l10n.s_nfc_tap_your_yubikey, icon: const NfcIconProgressBar(false), )); } - NfcEventCommand showAccessingKeyView() { + NfcEventCommand showScanning() { ref .read(nfcViewNotifier.notifier) .setDialogProperties(showCloseButton: false); return setNfcView(NfcContentWidget( - title: l10n.s_nfc_accessing_yubikey, + title: l10n.s_nfc_ready_to_scan, + subtitle: l10n.s_nfc_scanning, icon: const NfcIconProgressBar(true), )); } + NfcEventCommand showDone() { + ref + .read(nfcViewNotifier.notifier) + .setDialogProperties(showCloseButton: true); + return setNfcView( + NfcContentWidget( + title: l10n.s_nfc_ready_to_scan, + subtitle: l10n.s_nfc_done, + icon: const NfcIconSuccess(), + ), + showIfHidden: false); + } + + NfcEventCommand showFailed() { + ref + .read(nfcViewNotifier.notifier) + .setDialogProperties(showCloseButton: true); + return setNfcView( + NfcContentWidget( + title: l10n.s_nfc_ready_to_scan, + subtitle: l10n.l_nfc_failed_to_scan, + icon: const NfcIconFailure(), + ), + showIfHidden: false); + } + void closeDialog() { ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView()); } diff --git a/lib/android/views/nfc/models.dart b/lib/android/views/nfc/models.dart index d6594304..71862e7e 100644 --- a/lib/android/views/nfc/models.dart +++ b/lib/android/views/nfc/models.dart @@ -42,13 +42,11 @@ class NfcSetViewEvent extends NfcEvent { @freezed class NfcView with _$NfcView { - factory NfcView( - {required bool isShowing, - required Widget child, - bool? showCloseButton, - bool? showSuccess, - String? operationSuccess, - String? operationFailure}) = _NfcView; + factory NfcView({ + required bool isShowing, + required Widget child, + bool? showCloseButton, + }) = _NfcView; } @freezed diff --git a/lib/android/views/nfc/models.freezed.dart b/lib/android/views/nfc/models.freezed.dart index eee13f30..eb0d46bc 100644 --- a/lib/android/views/nfc/models.freezed.dart +++ b/lib/android/views/nfc/models.freezed.dart @@ -19,9 +19,6 @@ mixin _$NfcView { bool get isShowing => throw _privateConstructorUsedError; Widget get child => throw _privateConstructorUsedError; bool? get showCloseButton => throw _privateConstructorUsedError; - bool? get showSuccess => throw _privateConstructorUsedError; - String? get operationSuccess => throw _privateConstructorUsedError; - String? get operationFailure => throw _privateConstructorUsedError; /// Create a copy of NfcView /// with the given fields replaced by the non-null parameter values. @@ -34,13 +31,7 @@ abstract class $NfcViewCopyWith<$Res> { factory $NfcViewCopyWith(NfcView value, $Res Function(NfcView) then) = _$NfcViewCopyWithImpl<$Res, NfcView>; @useResult - $Res call( - {bool isShowing, - Widget child, - bool? showCloseButton, - bool? showSuccess, - String? operationSuccess, - String? operationFailure}); + $Res call({bool isShowing, Widget child, bool? showCloseButton}); } /// @nodoc @@ -61,9 +52,6 @@ class _$NfcViewCopyWithImpl<$Res, $Val extends NfcView> Object? isShowing = null, Object? child = null, Object? showCloseButton = freezed, - Object? showSuccess = freezed, - Object? operationSuccess = freezed, - Object? operationFailure = freezed, }) { return _then(_value.copyWith( isShowing: null == isShowing @@ -78,18 +66,6 @@ class _$NfcViewCopyWithImpl<$Res, $Val extends NfcView> ? _value.showCloseButton : showCloseButton // ignore: cast_nullable_to_non_nullable as bool?, - showSuccess: freezed == showSuccess - ? _value.showSuccess - : showSuccess // ignore: cast_nullable_to_non_nullable - as bool?, - operationSuccess: freezed == operationSuccess - ? _value.operationSuccess - : operationSuccess // ignore: cast_nullable_to_non_nullable - as String?, - operationFailure: freezed == operationFailure - ? _value.operationFailure - : operationFailure // ignore: cast_nullable_to_non_nullable - as String?, ) as $Val); } } @@ -101,13 +77,7 @@ abstract class _$$NfcViewImplCopyWith<$Res> implements $NfcViewCopyWith<$Res> { __$$NfcViewImplCopyWithImpl<$Res>; @override @useResult - $Res call( - {bool isShowing, - Widget child, - bool? showCloseButton, - bool? showSuccess, - String? operationSuccess, - String? operationFailure}); + $Res call({bool isShowing, Widget child, bool? showCloseButton}); } /// @nodoc @@ -126,9 +96,6 @@ class __$$NfcViewImplCopyWithImpl<$Res> Object? isShowing = null, Object? child = null, Object? showCloseButton = freezed, - Object? showSuccess = freezed, - Object? operationSuccess = freezed, - Object? operationFailure = freezed, }) { return _then(_$NfcViewImpl( isShowing: null == isShowing @@ -143,18 +110,6 @@ class __$$NfcViewImplCopyWithImpl<$Res> ? _value.showCloseButton : showCloseButton // ignore: cast_nullable_to_non_nullable as bool?, - showSuccess: freezed == showSuccess - ? _value.showSuccess - : showSuccess // ignore: cast_nullable_to_non_nullable - as bool?, - operationSuccess: freezed == operationSuccess - ? _value.operationSuccess - : operationSuccess // ignore: cast_nullable_to_non_nullable - as String?, - operationFailure: freezed == operationFailure - ? _value.operationFailure - : operationFailure // ignore: cast_nullable_to_non_nullable - as String?, )); } } @@ -163,12 +118,7 @@ class __$$NfcViewImplCopyWithImpl<$Res> class _$NfcViewImpl implements _NfcView { _$NfcViewImpl( - {required this.isShowing, - required this.child, - this.showCloseButton, - this.showSuccess, - this.operationSuccess, - this.operationFailure}); + {required this.isShowing, required this.child, this.showCloseButton}); @override final bool isShowing; @@ -176,16 +126,10 @@ class _$NfcViewImpl implements _NfcView { final Widget child; @override final bool? showCloseButton; - @override - final bool? showSuccess; - @override - final String? operationSuccess; - @override - final String? operationFailure; @override String toString() { - return 'NfcView(isShowing: $isShowing, child: $child, showCloseButton: $showCloseButton, showSuccess: $showSuccess, operationSuccess: $operationSuccess, operationFailure: $operationFailure)'; + return 'NfcView(isShowing: $isShowing, child: $child, showCloseButton: $showCloseButton)'; } @override @@ -197,18 +141,12 @@ class _$NfcViewImpl implements _NfcView { other.isShowing == isShowing) && (identical(other.child, child) || other.child == child) && (identical(other.showCloseButton, showCloseButton) || - other.showCloseButton == showCloseButton) && - (identical(other.showSuccess, showSuccess) || - other.showSuccess == showSuccess) && - (identical(other.operationSuccess, operationSuccess) || - other.operationSuccess == operationSuccess) && - (identical(other.operationFailure, operationFailure) || - other.operationFailure == operationFailure)); + other.showCloseButton == showCloseButton)); } @override - int get hashCode => Object.hash(runtimeType, isShowing, child, - showCloseButton, showSuccess, operationSuccess, operationFailure); + int get hashCode => + Object.hash(runtimeType, isShowing, child, showCloseButton); /// Create a copy of NfcView /// with the given fields replaced by the non-null parameter values. @@ -223,10 +161,7 @@ abstract class _NfcView implements NfcView { factory _NfcView( {required final bool isShowing, required final Widget child, - final bool? showCloseButton, - final bool? showSuccess, - final String? operationSuccess, - final String? operationFailure}) = _$NfcViewImpl; + final bool? showCloseButton}) = _$NfcViewImpl; @override bool get isShowing; @@ -234,12 +169,6 @@ abstract class _NfcView implements NfcView { Widget get child; @override bool? get showCloseButton; - @override - bool? get showSuccess; - @override - String? get operationSuccess; - @override - String? get operationFailure; /// Create a copy of NfcView /// with the given fields replaced by the non-null parameter values. diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/views/nfc/nfc_activity_overlay.dart index 52d40895..c3f2b123 100644 --- a/lib/android/views/nfc/nfc_activity_overlay.dart +++ b/lib/android/views/nfc/nfc_activity_overlay.dart @@ -52,15 +52,8 @@ class _NfcViewNotifier extends Notifier { state = state.copyWith(isShowing: value); } - void setDialogProperties( - {String? operationSuccess, - String? operationFailure, - bool? showSuccess, - bool? showCloseButton}) { + void setDialogProperties({bool? showCloseButton}) { state = state.copyWith( - operationSuccess: operationSuccess ?? state.operationSuccess, - operationFailure: operationFailure ?? state.operationFailure, - showSuccess: showSuccess ?? state.showSuccess, showCloseButton: showCloseButton ?? state.showCloseButton); } } diff --git a/lib/android/views/nfc/nfc_auto_close_widget.dart b/lib/android/views/nfc/nfc_auto_close_widget.dart index d0abe3e6..ccd221db 100644 --- a/lib/android/views/nfc/nfc_auto_close_widget.dart +++ b/lib/android/views/nfc/nfc_auto_close_widget.dart @@ -23,9 +23,9 @@ import 'nfc_activity_overlay.dart'; import 'nfc_content_widget.dart'; NfcEventCommand autoClose( - {String? title, - String? subtitle, - Widget? icon, + {required String title, + required String subtitle, + required Widget icon, bool showIfHidden = true}) => setNfcView( _NfcAutoCloseWidget( diff --git a/lib/android/views/nfc/nfc_content_widget.dart b/lib/android/views/nfc/nfc_content_widget.dart index 2ba897be..b101e3c1 100644 --- a/lib/android/views/nfc/nfc_content_widget.dart +++ b/lib/android/views/nfc/nfc_content_widget.dart @@ -1,32 +1,36 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../../app/state.dart'; -import 'nfc_progress_bar.dart'; - class NfcContentWidget extends ConsumerWidget { - final String? title; - final String? subtitle; - final Widget? icon; + final String title; + final String subtitle; + final Widget icon; - const NfcContentWidget({super.key, this.title, this.subtitle, this.icon}); + const NfcContentWidget({ + super.key, + required this.title, + required this.subtitle, + required this.icon, + }); @override Widget build(BuildContext context, WidgetRef ref) { - final l10n = ref.watch(l10nProvider); + final theme = Theme.of(context); + final textTheme = theme.textTheme; + final colorScheme = theme.colorScheme; return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Column( children: [ - Text(title ?? l10n.s_nfc_ready_to_scan, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleLarge), + Text(title, textAlign: TextAlign.center, style: textTheme.titleLarge), const SizedBox(height: 8), - Text(subtitle ?? '', + Text(subtitle, textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium), + style: textTheme.titleMedium!.copyWith( + color: colorScheme.onSurfaceVariant, + )), const SizedBox(height: 32), - icon ?? const NfcIconProgressBar(false), + icon, const SizedBox(height: 24) ], ), diff --git a/lib/android/views/nfc/nfc_count_down_close_widget.dart b/lib/android/views/nfc/nfc_count_down_close_widget.dart index 6620d893..fa9da011 100644 --- a/lib/android/views/nfc/nfc_count_down_close_widget.dart +++ b/lib/android/views/nfc/nfc_count_down_close_widget.dart @@ -25,10 +25,10 @@ import 'nfc_activity_overlay.dart'; import 'nfc_content_widget.dart'; NfcEventCommand countDownClose({ + required String title, + required String subtitle, + required Widget icon, int closeInSec = 3, - String? title, - String? subtitle, - Widget? icon, }) => setNfcView(_CountDownCloseWidget( closeInSec: closeInSec, diff --git a/lib/android/views/nfc/nfc_failure_icon.dart b/lib/android/views/nfc/nfc_failure_icon.dart index 96ce85e0..6942cdb2 100644 --- a/lib/android/views/nfc/nfc_failure_icon.dart +++ b/lib/android/views/nfc/nfc_failure_icon.dart @@ -24,6 +24,6 @@ class NfcIconFailure extends StatelessWidget { Widget build(BuildContext context) => Icon( Symbols.close, size: 64, - color: Theme.of(context).colorScheme.primary, + color: Theme.of(context).colorScheme.error, ); } diff --git a/lib/android/views/nfc/nfc_progress_bar.dart b/lib/android/views/nfc/nfc_progress_bar.dart index 89b5d532..2ea83bee 100644 --- a/lib/android/views/nfc/nfc_progress_bar.dart +++ b/lib/android/views/nfc/nfc_progress_bar.dart @@ -23,18 +23,35 @@ class NfcIconProgressBar extends StatelessWidget { const NfcIconProgressBar(this.inProgress, {super.key}); @override - Widget build(BuildContext context) => Stack( - alignment: AlignmentDirectional.center, - children: [ - Visibility( - visible: inProgress, - child: const SizedBox( - width: 64, - height: 64, - child: CircularProgressIndicator(), + Widget build(BuildContext context) => IconTheme( + data: IconThemeData( + size: 64, + color: Theme.of(context).colorScheme.primary, + ), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + const Opacity( + opacity: 0.5, + child: Icon(Symbols.contactless), ), - ), - const Icon(Symbols.contactless, size: 64) - ], + const ClipOval( + child: SizedBox( + width: 42, + height: 42, + child: OverflowBox( + maxWidth: double.infinity, + maxHeight: double.infinity, + child: Icon(Symbols.contactless), + ), + ), + ), + SizedBox( + width: 50, + height: 50, + child: CircularProgressIndicator(value: inProgress ? null : 1.0), + ), + ], + ), ); } diff --git a/lib/app/message.dart b/lib/app/message.dart index f523f425..431e5d6a 100755 --- a/lib/app/message.dart +++ b/lib/app/message.dart @@ -21,17 +21,12 @@ import 'package:flutter/material.dart'; import '../widgets/toast.dart'; -var allowMessages = true; - void Function() showMessage( BuildContext context, String message, { Duration duration = const Duration(seconds: 2), -}) { - return allowMessages - ? showToast(context, message, duration: duration) - : () {}; -} +}) => + showToast(context, message, duration: duration); Future showBlurDialog({ required BuildContext context, diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 93971193..4f669fc6 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -899,39 +899,6 @@ "l_launch_app_on_usb_off": "Andere Anwendungen können den YubiKey über USB nutzen", "s_allow_screenshots": "Bildschirmfotos erlauben", - "@_nfc_oath_actions": {}, - "s_nfc_oath_reset_success": null, - "s_nfc_oath_reset_failure": null, - - "s_nfc_oath_change_password_success": null, - "s_nfc_oath_set_password_failure": null, - "s_nfc_oath_change_password_failure": null, - - "s_nfc_oath_remove_password_failure": null, - - "s_nfc_oath_add_account_failure": null, - - "s_nfc_oath_rename_account_failure": null, - - "s_nfc_oath_delete_account_failure": null, - - "s_nfc_oath_calculate_code_success": null, - "s_nfc_oath_calculate_code_failure": null, - - "s_nfc_oath_add_multiple_accounts_success": null, - "s_nfc_oath_add_multiple_accounts_failure": null, - - "@_nfc_fido_actions": {}, - "s_nfc_fido_reset_success": null, - "s_nfc_fido_reset_failure": null, - - "s_nfc_fido_set_pin_failure": null, - - "s_nfc_fido_change_pin_success": null, - "s_nfc_fido_change_pin_failure": null, - - "s_nfc_fido_delete_passkey_failure": null, - "@_nfc_actions": {}, "s_nfc_tap_for": null, "@s_nfc_tap_for": { @@ -940,15 +907,11 @@ } }, - "l_nfc_read_key_failure": null, - - "s_nfc_remove_key": null, - "s_nfc_ready_to_scan": null, - "s_nfc_accessing_yubikey": null, - "s_nfc_scan_yubikey": null, - "s_nfc_scan_again": null, - "s_nfc_scan_success": null, + "s_nfc_scanning": null, + "s_nfc_tap_your_yubikey": null, + "l_nfc_failed_to_scan": null, + "s_nfc_done": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index c1c1bf61..ac78f4f3 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -899,39 +899,6 @@ "l_launch_app_on_usb_off": "Other apps can use the YubiKey over USB", "s_allow_screenshots": "Allow screenshots", - "@_nfc_oath_actions": {}, - "s_nfc_oath_reset_success": "Accounts reset", - "s_nfc_oath_reset_failure": "Failed to reset accounts", - - "s_nfc_oath_change_password_success": "Password changed", - "s_nfc_oath_set_password_failure": "Failed to set password", - "s_nfc_oath_change_password_failure": "Failed to change password", - - "s_nfc_oath_remove_password_failure": "Failed to remove password", - - "s_nfc_oath_add_account_failure": "Failed to add account", - - "s_nfc_oath_rename_account_failure": "Failed to rename account", - - "s_nfc_oath_delete_account_failure": "Failed to delete account", - - "s_nfc_oath_calculate_code_success": "Code calculated", - "s_nfc_oath_calculate_code_failure": "Failed to calculate code", - - "s_nfc_oath_add_multiple_accounts_success": "Accounts added", - "s_nfc_oath_add_multiple_accounts_failure": "Failed to add accounts", - - "@_nfc_fido_actions": {}, - "s_nfc_fido_reset_success": "FIDO reset", - "s_nfc_fido_reset_failure": "FIDO reset failed", - - "s_nfc_fido_set_pin_failure": "Failure setting PIN", - - "s_nfc_fido_change_pin_success": "PIN changed", - "s_nfc_fido_change_pin_failure": "Failure changing PIN", - - "s_nfc_fido_delete_passkey_failure": "Failed to delete passkey", - "@_nfc_actions": {}, "s_nfc_tap_for": "Tap YubiKey to {operation}", "@s_nfc_tap_for": { @@ -940,15 +907,11 @@ } }, - "l_nfc_read_key_failure": "Failed to scan YubiKey", - - "s_nfc_remove_key": "Remove your YubiKey", - "s_nfc_ready_to_scan": "Ready to scan", - "s_nfc_accessing_yubikey": "Accessing YubiKey", - "s_nfc_scan_yubikey": "Scan your YubiKey", - "s_nfc_scan_again": "Scan again", - "s_nfc_scan_success": "Scanned your Yubikey", + "s_nfc_scanning": "Scanning\u2026", + "s_nfc_tap_your_yubikey": "Tap your YubiKey", + "l_nfc_failed_to_scan": "Failed to scan, try again", + "s_nfc_done": "Done", "s_nfc_unlock_processing": "Unlocking", "s_nfc_unlock_success": "Accounts unlocked", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 129fe16e..d5e18acb 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -899,39 +899,6 @@ "l_launch_app_on_usb_off": "D'autres applications peuvent utiliser la YubiKey en USB", "s_allow_screenshots": "Autoriser captures d'écran", - "@_nfc_oath_actions": {}, - "s_nfc_oath_reset_success": null, - "s_nfc_oath_reset_failure": null, - - "s_nfc_oath_change_password_success": null, - "s_nfc_oath_set_password_failure": null, - "s_nfc_oath_change_password_failure": null, - - "s_nfc_oath_remove_password_failure": null, - - "s_nfc_oath_add_account_failure": null, - - "s_nfc_oath_rename_account_failure": null, - - "s_nfc_oath_delete_account_failure": null, - - "s_nfc_oath_calculate_code_success": null, - "s_nfc_oath_calculate_code_failure": null, - - "s_nfc_oath_add_multiple_accounts_success": null, - "s_nfc_oath_add_multiple_accounts_failure": null, - - "@_nfc_fido_actions": {}, - "s_nfc_fido_reset_success": null, - "s_nfc_fido_reset_failure": null, - - "s_nfc_fido_set_pin_failure": null, - - "s_nfc_fido_change_pin_success": null, - "s_nfc_fido_change_pin_failure": null, - - "s_nfc_fido_delete_passkey_failure": null, - "@_nfc_actions": {}, "s_nfc_tap_for": null, "@s_nfc_tap_for": { @@ -940,15 +907,11 @@ } }, - "l_nfc_read_key_failure": null, - - "s_nfc_remove_key": null, - "s_nfc_ready_to_scan": null, - "s_nfc_accessing_yubikey": null, - "s_nfc_scan_yubikey": null, - "s_nfc_scan_again": null, - "s_nfc_scan_success": null, + "s_nfc_scanning": null, + "s_nfc_tap_your_yubikey": null, + "l_nfc_failed_to_scan": null, + "s_nfc_done": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 1a31bb41..1563b8c0 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -899,39 +899,6 @@ "l_launch_app_on_usb_off": "他のアプリがUSB経由でYubiKeyを使用できます", "s_allow_screenshots": "スクリーンショットを許可", - "@_nfc_oath_actions": {}, - "s_nfc_oath_reset_success": null, - "s_nfc_oath_reset_failure": null, - - "s_nfc_oath_change_password_success": null, - "s_nfc_oath_set_password_failure": null, - "s_nfc_oath_change_password_failure": null, - - "s_nfc_oath_remove_password_failure": null, - - "s_nfc_oath_add_account_failure": null, - - "s_nfc_oath_rename_account_failure": null, - - "s_nfc_oath_delete_account_failure": null, - - "s_nfc_oath_calculate_code_success": null, - "s_nfc_oath_calculate_code_failure": null, - - "s_nfc_oath_add_multiple_accounts_success": null, - "s_nfc_oath_add_multiple_accounts_failure": null, - - "@_nfc_fido_actions": {}, - "s_nfc_fido_reset_success": null, - "s_nfc_fido_reset_failure": null, - - "s_nfc_fido_set_pin_failure": null, - - "s_nfc_fido_change_pin_success": null, - "s_nfc_fido_change_pin_failure": null, - - "s_nfc_fido_delete_passkey_failure": null, - "@_nfc_actions": {}, "s_nfc_tap_for": null, "@s_nfc_tap_for": { @@ -940,15 +907,11 @@ } }, - "l_nfc_read_key_failure": null, - - "s_nfc_remove_key": null, - "s_nfc_ready_to_scan": null, - "s_nfc_accessing_yubikey": null, - "s_nfc_scan_yubikey": null, - "s_nfc_scan_again": null, - "s_nfc_scan_success": null, + "s_nfc_scanning": null, + "s_nfc_tap_your_yubikey": null, + "l_nfc_failed_to_scan": null, + "s_nfc_done": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 88eec6e6..f6274f1a 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -899,39 +899,6 @@ "l_launch_app_on_usb_off": "Inne aplikacje mogą korzystać z YubiKey przez USB", "s_allow_screenshots": "Zezwalaj na zrzuty ekranu", - "@_nfc_oath_actions": {}, - "s_nfc_oath_reset_success": null, - "s_nfc_oath_reset_failure": null, - - "s_nfc_oath_change_password_success": null, - "s_nfc_oath_set_password_failure": null, - "s_nfc_oath_change_password_failure": null, - - "s_nfc_oath_remove_password_failure": null, - - "s_nfc_oath_add_account_failure": null, - - "s_nfc_oath_rename_account_failure": null, - - "s_nfc_oath_delete_account_failure": null, - - "s_nfc_oath_calculate_code_success": null, - "s_nfc_oath_calculate_code_failure": null, - - "s_nfc_oath_add_multiple_accounts_success": null, - "s_nfc_oath_add_multiple_accounts_failure": null, - - "@_nfc_fido_actions": {}, - "s_nfc_fido_reset_success": null, - "s_nfc_fido_reset_failure": null, - - "s_nfc_fido_set_pin_failure": null, - - "s_nfc_fido_change_pin_success": null, - "s_nfc_fido_change_pin_failure": null, - - "s_nfc_fido_delete_passkey_failure": null, - "@_nfc_actions": {}, "s_nfc_tap_for": null, "@s_nfc_tap_for": { @@ -940,15 +907,11 @@ } }, - "l_nfc_read_key_failure": null, - - "s_nfc_remove_key": null, - "s_nfc_ready_to_scan": null, - "s_nfc_accessing_yubikey": null, - "s_nfc_scan_yubikey": null, - "s_nfc_scan_again": null, - "s_nfc_scan_success": null, + "s_nfc_scanning": null, + "s_nfc_tap_your_yubikey": null, + "l_nfc_failed_to_scan": null, + "s_nfc_done": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index b35f8cdb..f0158bcc 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -899,39 +899,6 @@ "l_launch_app_on_usb_off": "Các ứng dụng khác có thể sử dụng YubiKey qua USB", "s_allow_screenshots": "Cho phép chụp ảnh màn hình", - "@_nfc_oath_actions": {}, - "s_nfc_oath_reset_success": null, - "s_nfc_oath_reset_failure": null, - - "s_nfc_oath_change_password_success": null, - "s_nfc_oath_set_password_failure": null, - "s_nfc_oath_change_password_failure": null, - - "s_nfc_oath_remove_password_failure": null, - - "s_nfc_oath_add_account_failure": null, - - "s_nfc_oath_rename_account_failure": null, - - "s_nfc_oath_delete_account_failure": null, - - "s_nfc_oath_calculate_code_success": null, - "s_nfc_oath_calculate_code_failure": null, - - "s_nfc_oath_add_multiple_accounts_success": null, - "s_nfc_oath_add_multiple_accounts_failure": null, - - "@_nfc_fido_actions": {}, - "s_nfc_fido_reset_success": null, - "s_nfc_fido_reset_failure": null, - - "s_nfc_fido_set_pin_failure": null, - - "s_nfc_fido_change_pin_success": null, - "s_nfc_fido_change_pin_failure": null, - - "s_nfc_fido_delete_passkey_failure": null, - "@_nfc_actions": {}, "s_nfc_tap_for": null, "@s_nfc_tap_for": { @@ -940,15 +907,11 @@ } }, - "l_nfc_read_key_failure": null, - - "s_nfc_remove_key": null, - "s_nfc_ready_to_scan": null, - "s_nfc_accessing_yubikey": null, - "s_nfc_scan_yubikey": null, - "s_nfc_scan_again": null, - "s_nfc_scan_success": null, + "s_nfc_scanning": null, + "s_nfc_tap_your_yubikey": null, + "l_nfc_failed_to_scan": null, + "s_nfc_done": null, "s_nfc_unlock_processing": null, "s_nfc_unlock_success": null, From 63a1d8d20a136d33e935d099918609091c449a62 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 6 Sep 2024 13:19:27 +0200 Subject: [PATCH 31/50] cleanup localizations --- lib/l10n/app_de.arb | 13 ------------- lib/l10n/app_en.arb | 13 ------------- lib/l10n/app_fr.arb | 13 ------------- lib/l10n/app_ja.arb | 13 ------------- lib/l10n/app_pl.arb | 13 ------------- lib/l10n/app_vi.arb | 13 ------------- 6 files changed, 78 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 4f669fc6..866aaef0 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -360,7 +360,6 @@ "s_password": "Passwort", "s_manage_password": "Passwort verwalten", "s_set_password": "Passwort setzen", - "s_change_password": null, "s_password_set": "Passwort gesetzt", "s_show_password": "Passwort anzeigen", "s_hide_password": "Passwort verstekcen", @@ -899,24 +898,12 @@ "l_launch_app_on_usb_off": "Andere Anwendungen können den YubiKey über USB nutzen", "s_allow_screenshots": "Bildschirmfotos erlauben", - "@_nfc_actions": {}, - "s_nfc_tap_for": null, - "@s_nfc_tap_for": { - "placeholders": { - "operation": {} - } - }, - "s_nfc_ready_to_scan": null, "s_nfc_scanning": null, "s_nfc_tap_your_yubikey": null, "l_nfc_failed_to_scan": null, "s_nfc_done": null, - "s_nfc_unlock_processing": null, - "s_nfc_unlock_success": null, - "s_nfc_unlock_failure": null, - "@_ndef": {}, "p_ndef_set_otp": "OTP-Code wurde erfolgreich von Ihrem YubiKey in die Zwischenablage kopiert.", "p_ndef_set_password": "Passwort wurde erfolgreich von Ihrem YubiKey in die Zwischenablage kopiert.", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ac78f4f3..593aa337 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -360,7 +360,6 @@ "s_password": "Password", "s_manage_password": "Manage password", "s_set_password": "Set password", - "s_change_password": "Change password", "s_password_set": "Password set", "s_show_password": "Show password", "s_hide_password": "Hide password", @@ -899,24 +898,12 @@ "l_launch_app_on_usb_off": "Other apps can use the YubiKey over USB", "s_allow_screenshots": "Allow screenshots", - "@_nfc_actions": {}, - "s_nfc_tap_for": "Tap YubiKey to {operation}", - "@s_nfc_tap_for": { - "placeholders": { - "operation": {} - } - }, - "s_nfc_ready_to_scan": "Ready to scan", "s_nfc_scanning": "Scanning\u2026", "s_nfc_tap_your_yubikey": "Tap your YubiKey", "l_nfc_failed_to_scan": "Failed to scan, try again", "s_nfc_done": "Done", - "s_nfc_unlock_processing": "Unlocking", - "s_nfc_unlock_success": "Accounts unlocked", - "s_nfc_unlock_failure": "Failed to unlock", - "@_ndef": {}, "p_ndef_set_otp": "Successfully copied OTP code from YubiKey to clipboard.", "p_ndef_set_password": "Successfully copied password from YubiKey to clipboard.", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d5e18acb..79ef7a23 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -360,7 +360,6 @@ "s_password": "Mot de passe", "s_manage_password": "Gérer mot de passe", "s_set_password": "Créer mot de passe", - "s_change_password": null, "s_password_set": "Mot de passe créé", "s_show_password": "Montrer mot de passe", "s_hide_password": "Masquer mot de passe", @@ -899,24 +898,12 @@ "l_launch_app_on_usb_off": "D'autres applications peuvent utiliser la YubiKey en USB", "s_allow_screenshots": "Autoriser captures d'écran", - "@_nfc_actions": {}, - "s_nfc_tap_for": null, - "@s_nfc_tap_for": { - "placeholders": { - "operation": {} - } - }, - "s_nfc_ready_to_scan": null, "s_nfc_scanning": null, "s_nfc_tap_your_yubikey": null, "l_nfc_failed_to_scan": null, "s_nfc_done": null, - "s_nfc_unlock_processing": null, - "s_nfc_unlock_success": null, - "s_nfc_unlock_failure": null, - "@_ndef": {}, "p_ndef_set_otp": "Code OTP copié de la YubiKey dans le presse-papiers.", "p_ndef_set_password": "Mot de passe copié de la YubiKey dans le presse-papiers.", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 1563b8c0..87df5ac2 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -360,7 +360,6 @@ "s_password": "パスワード", "s_manage_password": "パスワードを管理", "s_set_password": "パスワードを設定", - "s_change_password": null, "s_password_set": "パスワードが設定されました", "s_show_password": "パスワードを表示", "s_hide_password": "パスワードを非表示", @@ -899,24 +898,12 @@ "l_launch_app_on_usb_off": "他のアプリがUSB経由でYubiKeyを使用できます", "s_allow_screenshots": "スクリーンショットを許可", - "@_nfc_actions": {}, - "s_nfc_tap_for": null, - "@s_nfc_tap_for": { - "placeholders": { - "operation": {} - } - }, - "s_nfc_ready_to_scan": null, "s_nfc_scanning": null, "s_nfc_tap_your_yubikey": null, "l_nfc_failed_to_scan": null, "s_nfc_done": null, - "s_nfc_unlock_processing": null, - "s_nfc_unlock_success": null, - "s_nfc_unlock_failure": null, - "@_ndef": {}, "p_ndef_set_otp": "OTPコードがYubiKeyからクリップボードに正常にコピーされました。", "p_ndef_set_password": "パスワードがYubiKeyからクリップボードに正常にコピーされました。", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index f6274f1a..5eb0b2dc 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -360,7 +360,6 @@ "s_password": "Hasło", "s_manage_password": "Zarządzaj hasłem", "s_set_password": "Ustaw hasło", - "s_change_password": null, "s_password_set": "Hasło zostało ustawione", "s_show_password": "Pokaż hasło", "s_hide_password": "Ukryj hasło", @@ -899,24 +898,12 @@ "l_launch_app_on_usb_off": "Inne aplikacje mogą korzystać z YubiKey przez USB", "s_allow_screenshots": "Zezwalaj na zrzuty ekranu", - "@_nfc_actions": {}, - "s_nfc_tap_for": null, - "@s_nfc_tap_for": { - "placeholders": { - "operation": {} - } - }, - "s_nfc_ready_to_scan": null, "s_nfc_scanning": null, "s_nfc_tap_your_yubikey": null, "l_nfc_failed_to_scan": null, "s_nfc_done": null, - "s_nfc_unlock_processing": null, - "s_nfc_unlock_success": null, - "s_nfc_unlock_failure": null, - "@_ndef": {}, "p_ndef_set_otp": "OTP zostało skopiowane do schowka.", "p_ndef_set_password": "Hasło statyczne zostało skopiowane do schowka.", diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index f0158bcc..b925de68 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -360,7 +360,6 @@ "s_password": "Mật khẩu", "s_manage_password": "Quản lý mật khẩu", "s_set_password": "Đặt mật khẩu", - "s_change_password": null, "s_password_set": "Mật khẩu đã được đặt", "s_show_password": "Hiển thị mật khẩu", "s_hide_password": "Ẩn mật khẩu", @@ -899,24 +898,12 @@ "l_launch_app_on_usb_off": "Các ứng dụng khác có thể sử dụng YubiKey qua USB", "s_allow_screenshots": "Cho phép chụp ảnh màn hình", - "@_nfc_actions": {}, - "s_nfc_tap_for": null, - "@s_nfc_tap_for": { - "placeholders": { - "operation": {} - } - }, - "s_nfc_ready_to_scan": null, "s_nfc_scanning": null, "s_nfc_tap_your_yubikey": null, "l_nfc_failed_to_scan": null, "s_nfc_done": null, - "s_nfc_unlock_processing": null, - "s_nfc_unlock_success": null, - "s_nfc_unlock_failure": null, - "@_ndef": {}, "p_ndef_set_otp": "Đã sao chép mã OTP từ YubiKey vào clipboard.", "p_ndef_set_password": "Đã sao chép mật khẩu từ YubiKey vào clipboard.", From 454d36410cb6bbec2d1aa8329eac8bdc4040dcd9 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 6 Sep 2024 16:11:42 +0200 Subject: [PATCH 32/50] Use correct l10n key --- lib/android/tap_request_dialog.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index e631b957..1258482c 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -120,7 +120,7 @@ class _DialogProvider extends Notifier { .setDialogProperties(showCloseButton: false); return setNfcView(NfcContentWidget( title: l10n.s_nfc_ready_to_scan, - subtitle: l10n.s_nfc_scanning, + subtitle: l10n.s_nfc_hold_still, icon: const NfcIconProgressBar(true), )); } From 75073c149b1614c4df1ac0ca78626a98e343b45c Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 6 Sep 2024 16:58:30 +0200 Subject: [PATCH 33/50] refactor --- lib/android/tap_request_dialog.dart | 46 ++-- lib/android/views/nfc/models.dart | 26 +-- lib/android/views/nfc/models.freezed.dart | 212 ++++-------------- .../nfc/nfc_activity_command_listener.dart | 12 +- .../views/nfc/nfc_activity_overlay.dart | 25 +-- .../views/nfc/nfc_auto_close_widget.dart | 8 +- .../nfc/nfc_count_down_close_widget.dart | 7 +- 7 files changed, 92 insertions(+), 244 deletions(-) diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index 1258482c..baddb8a5 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -47,7 +47,8 @@ class _DialogProvider extends Notifier { final viewNotifier = ref.read(nfcViewNotifier.notifier); ref.listen(androidNfcActivityProvider, (previous, current) { - final notifier = ref.read(nfcEventCommandNotifier.notifier); + processingViewTimeout?.cancel(); + final notifier = ref.read(nfcEventNotifier.notifier); if (!explicitAction) { // setup properties for ad-hoc action @@ -57,21 +58,18 @@ class _DialogProvider extends Notifier { switch (current) { case NfcActivity.processingStarted: final timeout = explicitAction ? 300 : 500; - processingViewTimeout?.cancel(); processingViewTimeout = Timer(Duration(milliseconds: timeout), () { - notifier.sendCommand(showScanning()); + notifier.send(showHoldStill()); }); break; case NfcActivity.processingFinished: - processingViewTimeout?.cancel(); - notifier.sendCommand(showDone()); - notifier.sendCommand(hideNfcView(const Duration(milliseconds: 400))); - + notifier.send(showDone()); + notifier + .send(const NfcHideViewEvent(delay: Duration(milliseconds: 400))); explicitAction = false; // next action might not be explicit break; case NfcActivity.processingInterrupted: - processingViewTimeout?.cancel(); - notifier.sendCommand(showFailed()); + notifier.send(showFailed()); break; case NfcActivity.notActive: _log.debug('Received not handled notActive'); @@ -82,11 +80,11 @@ class _DialogProvider extends Notifier { }); _channel.setMethodCallHandler((call) async { - final notifier = ref.read(nfcEventCommandNotifier.notifier); + final notifier = ref.read(nfcEventNotifier.notifier); switch (call.method) { case 'show': explicitAction = true; - notifier.sendCommand(showTapYourYubiKey()); + notifier.send(showTapYourYubiKey()); break; case 'close': @@ -103,34 +101,36 @@ class _DialogProvider extends Notifier { return 0; } - NfcEventCommand showTapYourYubiKey() { + NfcEvent showTapYourYubiKey() { ref .read(nfcViewNotifier.notifier) .setDialogProperties(showCloseButton: true); - return setNfcView(NfcContentWidget( + return NfcSetViewEvent( + child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, subtitle: l10n.s_nfc_tap_your_yubikey, icon: const NfcIconProgressBar(false), )); } - NfcEventCommand showScanning() { + NfcEvent showHoldStill() { ref .read(nfcViewNotifier.notifier) .setDialogProperties(showCloseButton: false); - return setNfcView(NfcContentWidget( + return NfcSetViewEvent( + child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, subtitle: l10n.s_nfc_hold_still, icon: const NfcIconProgressBar(true), )); } - NfcEventCommand showDone() { + NfcEvent showDone() { ref .read(nfcViewNotifier.notifier) .setDialogProperties(showCloseButton: true); - return setNfcView( - NfcContentWidget( + return NfcSetViewEvent( + child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, subtitle: l10n.s_done, icon: const NfcIconSuccess(), @@ -138,12 +138,12 @@ class _DialogProvider extends Notifier { showIfHidden: false); } - NfcEventCommand showFailed() { + NfcEvent showFailed() { ref .read(nfcViewNotifier.notifier) .setDialogProperties(showCloseButton: true); - return setNfcView( - NfcContentWidget( + return NfcSetViewEvent( + child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, subtitle: l10n.l_nfc_failed_to_scan, icon: const NfcIconFailure(), @@ -152,7 +152,7 @@ class _DialogProvider extends Notifier { } void closeDialog() { - ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView()); + ref.read(nfcEventNotifier.notifier).send(const NfcHideViewEvent()); } void cancelDialog() async { @@ -166,7 +166,7 @@ class _DialogProvider extends Notifier { Timer.periodic( const Duration(milliseconds: 200), (timer) { - if (!ref.read(nfcViewNotifier.select((s) => s.isShowing))) { + if (ref.read(nfcViewNotifier.select((s) => !s.visible))) { timer.cancel(); completer.complete(); } diff --git a/lib/android/views/nfc/models.dart b/lib/android/views/nfc/models.dart index 71862e7e..5149528c 100644 --- a/lib/android/views/nfc/models.dart +++ b/lib/android/views/nfc/models.dart @@ -24,13 +24,9 @@ class NfcEvent { } class NfcHideViewEvent extends NfcEvent { - final Duration hideAfter; + final Duration delay; - const NfcHideViewEvent({required this.hideAfter}); -} - -class NfcCancelEvent extends NfcEvent { - const NfcCancelEvent(); + const NfcHideViewEvent({this.delay = Duration.zero}); } class NfcSetViewEvent extends NfcEvent { @@ -43,22 +39,8 @@ class NfcSetViewEvent extends NfcEvent { @freezed class NfcView with _$NfcView { factory NfcView({ - required bool isShowing, required Widget child, - bool? showCloseButton, + @Default(false) bool visible, + @Default(false) bool hasCloseButton, }) = _NfcView; } - -@freezed -class NfcEventCommand with _$NfcEventCommand { - factory NfcEventCommand({ - @Default(NfcEvent()) NfcEvent event, - }) = _NfcEventCommand; -} - -NfcEventCommand hideNfcView([Duration hideAfter = Duration.zero]) => - NfcEventCommand(event: NfcHideViewEvent(hideAfter: hideAfter)); - -NfcEventCommand setNfcView(Widget child, {bool showIfHidden = true}) => - NfcEventCommand( - event: NfcSetViewEvent(child: child, showIfHidden: showIfHidden)); diff --git a/lib/android/views/nfc/models.freezed.dart b/lib/android/views/nfc/models.freezed.dart index eb0d46bc..d7dc49d1 100644 --- a/lib/android/views/nfc/models.freezed.dart +++ b/lib/android/views/nfc/models.freezed.dart @@ -16,9 +16,9 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$NfcView { - bool get isShowing => throw _privateConstructorUsedError; Widget get child => throw _privateConstructorUsedError; - bool? get showCloseButton => throw _privateConstructorUsedError; + bool get visible => throw _privateConstructorUsedError; + bool get hasCloseButton => throw _privateConstructorUsedError; /// Create a copy of NfcView /// with the given fields replaced by the non-null parameter values. @@ -31,7 +31,7 @@ abstract class $NfcViewCopyWith<$Res> { factory $NfcViewCopyWith(NfcView value, $Res Function(NfcView) then) = _$NfcViewCopyWithImpl<$Res, NfcView>; @useResult - $Res call({bool isShowing, Widget child, bool? showCloseButton}); + $Res call({Widget child, bool visible, bool hasCloseButton}); } /// @nodoc @@ -49,23 +49,23 @@ class _$NfcViewCopyWithImpl<$Res, $Val extends NfcView> @pragma('vm:prefer-inline') @override $Res call({ - Object? isShowing = null, Object? child = null, - Object? showCloseButton = freezed, + Object? visible = null, + Object? hasCloseButton = null, }) { return _then(_value.copyWith( - isShowing: null == isShowing - ? _value.isShowing - : isShowing // ignore: cast_nullable_to_non_nullable - as bool, child: null == child ? _value.child : child // ignore: cast_nullable_to_non_nullable as Widget, - showCloseButton: freezed == showCloseButton - ? _value.showCloseButton - : showCloseButton // ignore: cast_nullable_to_non_nullable - as bool?, + visible: null == visible + ? _value.visible + : visible // ignore: cast_nullable_to_non_nullable + as bool, + hasCloseButton: null == hasCloseButton + ? _value.hasCloseButton + : hasCloseButton // ignore: cast_nullable_to_non_nullable + as bool, ) as $Val); } } @@ -77,7 +77,7 @@ abstract class _$$NfcViewImplCopyWith<$Res> implements $NfcViewCopyWith<$Res> { __$$NfcViewImplCopyWithImpl<$Res>; @override @useResult - $Res call({bool isShowing, Widget child, bool? showCloseButton}); + $Res call({Widget child, bool visible, bool hasCloseButton}); } /// @nodoc @@ -93,23 +93,23 @@ class __$$NfcViewImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? isShowing = null, Object? child = null, - Object? showCloseButton = freezed, + Object? visible = null, + Object? hasCloseButton = null, }) { return _then(_$NfcViewImpl( - isShowing: null == isShowing - ? _value.isShowing - : isShowing // ignore: cast_nullable_to_non_nullable - as bool, child: null == child ? _value.child : child // ignore: cast_nullable_to_non_nullable as Widget, - showCloseButton: freezed == showCloseButton - ? _value.showCloseButton - : showCloseButton // ignore: cast_nullable_to_non_nullable - as bool?, + visible: null == visible + ? _value.visible + : visible // ignore: cast_nullable_to_non_nullable + as bool, + hasCloseButton: null == hasCloseButton + ? _value.hasCloseButton + : hasCloseButton // ignore: cast_nullable_to_non_nullable + as bool, )); } } @@ -118,18 +118,20 @@ class __$$NfcViewImplCopyWithImpl<$Res> class _$NfcViewImpl implements _NfcView { _$NfcViewImpl( - {required this.isShowing, required this.child, this.showCloseButton}); + {required this.child, this.visible = false, this.hasCloseButton = false}); - @override - final bool isShowing; @override final Widget child; @override - final bool? showCloseButton; + @JsonKey() + final bool visible; + @override + @JsonKey() + final bool hasCloseButton; @override String toString() { - return 'NfcView(isShowing: $isShowing, child: $child, showCloseButton: $showCloseButton)'; + return 'NfcView(child: $child, visible: $visible, hasCloseButton: $hasCloseButton)'; } @override @@ -137,16 +139,14 @@ class _$NfcViewImpl implements _NfcView { return identical(this, other) || (other.runtimeType == runtimeType && other is _$NfcViewImpl && - (identical(other.isShowing, isShowing) || - other.isShowing == isShowing) && (identical(other.child, child) || other.child == child) && - (identical(other.showCloseButton, showCloseButton) || - other.showCloseButton == showCloseButton)); + (identical(other.visible, visible) || other.visible == visible) && + (identical(other.hasCloseButton, hasCloseButton) || + other.hasCloseButton == hasCloseButton)); } @override - int get hashCode => - Object.hash(runtimeType, isShowing, child, showCloseButton); + int get hashCode => Object.hash(runtimeType, child, visible, hasCloseButton); /// Create a copy of NfcView /// with the given fields replaced by the non-null parameter values. @@ -159,16 +159,16 @@ class _$NfcViewImpl implements _NfcView { abstract class _NfcView implements NfcView { factory _NfcView( - {required final bool isShowing, - required final Widget child, - final bool? showCloseButton}) = _$NfcViewImpl; + {required final Widget child, + final bool visible, + final bool hasCloseButton}) = _$NfcViewImpl; - @override - bool get isShowing; @override Widget get child; @override - bool? get showCloseButton; + bool get visible; + @override + bool get hasCloseButton; /// Create a copy of NfcView /// with the given fields replaced by the non-null parameter values. @@ -177,133 +177,3 @@ abstract class _NfcView implements NfcView { _$$NfcViewImplCopyWith<_$NfcViewImpl> get copyWith => throw _privateConstructorUsedError; } - -/// @nodoc -mixin _$NfcEventCommand { - NfcEvent get event => throw _privateConstructorUsedError; - - /// Create a copy of NfcEventCommand - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $NfcEventCommandCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $NfcEventCommandCopyWith<$Res> { - factory $NfcEventCommandCopyWith( - NfcEventCommand value, $Res Function(NfcEventCommand) then) = - _$NfcEventCommandCopyWithImpl<$Res, NfcEventCommand>; - @useResult - $Res call({NfcEvent event}); -} - -/// @nodoc -class _$NfcEventCommandCopyWithImpl<$Res, $Val extends NfcEventCommand> - implements $NfcEventCommandCopyWith<$Res> { - _$NfcEventCommandCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of NfcEventCommand - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? event = null, - }) { - return _then(_value.copyWith( - event: null == event - ? _value.event - : event // ignore: cast_nullable_to_non_nullable - as NfcEvent, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$NfcEventCommandImplCopyWith<$Res> - implements $NfcEventCommandCopyWith<$Res> { - factory _$$NfcEventCommandImplCopyWith(_$NfcEventCommandImpl value, - $Res Function(_$NfcEventCommandImpl) then) = - __$$NfcEventCommandImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({NfcEvent event}); -} - -/// @nodoc -class __$$NfcEventCommandImplCopyWithImpl<$Res> - extends _$NfcEventCommandCopyWithImpl<$Res, _$NfcEventCommandImpl> - implements _$$NfcEventCommandImplCopyWith<$Res> { - __$$NfcEventCommandImplCopyWithImpl( - _$NfcEventCommandImpl _value, $Res Function(_$NfcEventCommandImpl) _then) - : super(_value, _then); - - /// Create a copy of NfcEventCommand - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? event = null, - }) { - return _then(_$NfcEventCommandImpl( - event: null == event - ? _value.event - : event // ignore: cast_nullable_to_non_nullable - as NfcEvent, - )); - } -} - -/// @nodoc - -class _$NfcEventCommandImpl implements _NfcEventCommand { - _$NfcEventCommandImpl({this.event = const NfcEvent()}); - - @override - @JsonKey() - final NfcEvent event; - - @override - String toString() { - return 'NfcEventCommand(event: $event)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$NfcEventCommandImpl && - (identical(other.event, event) || other.event == event)); - } - - @override - int get hashCode => Object.hash(runtimeType, event); - - /// Create a copy of NfcEventCommand - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$NfcEventCommandImplCopyWith<_$NfcEventCommandImpl> get copyWith => - __$$NfcEventCommandImplCopyWithImpl<_$NfcEventCommandImpl>( - this, _$identity); -} - -abstract class _NfcEventCommand implements NfcEventCommand { - factory _NfcEventCommand({final NfcEvent event}) = _$NfcEventCommandImpl; - - @override - NfcEvent get event; - - /// Create a copy of NfcEventCommand - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$NfcEventCommandImplCopyWith<_$NfcEventCommandImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/android/views/nfc/nfc_activity_command_listener.dart b/lib/android/views/nfc/nfc_activity_command_listener.dart index d821750b..c24f1cb8 100644 --- a/lib/android/views/nfc/nfc_activity_command_listener.dart +++ b/lib/android/views/nfc/nfc_activity_command_listener.dart @@ -37,8 +37,7 @@ class _NfcEventCommandListener { void startListener(BuildContext context) { listener?.close(); - listener = _ref.listen(nfcEventCommandNotifier.select((c) => c.event), - (previous, action) { + listener = _ref.listen(nfcEventNotifier, (previous, action) { _log.debug('Change in command for Overlay: $previous -> $action'); switch (action) { case (NfcSetViewEvent a): @@ -49,11 +48,7 @@ class _NfcEventCommandListener { } break; case (NfcHideViewEvent e): - _hide(context, e.hideAfter); - break; - case (NfcCancelEvent _): - _ref.read(androidDialogProvider.notifier).cancelDialog(); - _hide(context, Duration.zero); + _hide(context, e.delay); break; } }); @@ -88,7 +83,8 @@ class _NfcEventCommandListener { }); } - bool get visible => _ref.read(nfcViewNotifier.select((s) => s.isShowing)); + bool get visible => _ref.read(nfcViewNotifier.select((s) => s.visible)); + set visible(bool showing) => _ref.read(nfcViewNotifier.notifier).setShowing(showing); } diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/views/nfc/nfc_activity_overlay.dart index c3f2b123..cab4bb99 100644 --- a/lib/android/views/nfc/nfc_activity_overlay.dart +++ b/lib/android/views/nfc/nfc_activity_overlay.dart @@ -20,18 +20,17 @@ import 'package:material_symbols_icons/symbols.dart'; import 'models.dart'; -final nfcEventCommandNotifier = - NotifierProvider<_NfcEventCommandNotifier, NfcEventCommand>( - _NfcEventCommandNotifier.new); +final nfcEventNotifier = + NotifierProvider<_NfcEventNotifier, NfcEvent>(_NfcEventNotifier.new); -class _NfcEventCommandNotifier extends Notifier { +class _NfcEventNotifier extends Notifier { @override - NfcEventCommand build() { - return NfcEventCommand(event: const NfcEvent()); + NfcEvent build() { + return const NfcEvent(); } - void sendCommand(NfcEventCommand command) { - state = command; + void send(NfcEvent event) { + state = event; } } @@ -41,7 +40,7 @@ final nfcViewNotifier = class _NfcViewNotifier extends Notifier { @override NfcView build() { - return NfcView(isShowing: false, child: const SizedBox()); + return NfcView(child: const SizedBox()); } void update(Widget child) { @@ -49,12 +48,12 @@ class _NfcViewNotifier extends Notifier { } void setShowing(bool value) { - state = state.copyWith(isShowing: value); + state = state.copyWith(visible: value); } void setDialogProperties({bool? showCloseButton}) { - state = state.copyWith( - showCloseButton: showCloseButton ?? state.showCloseButton); + state = + state.copyWith(hasCloseButton: showCloseButton ?? state.hasCloseButton); } } @@ -65,7 +64,7 @@ class NfcBottomSheet extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final widget = ref.watch(nfcViewNotifier.select((s) => s.child)); final showCloseButton = - ref.watch(nfcViewNotifier.select((s) => s.showCloseButton ?? false)); + ref.watch(nfcViewNotifier.select((s) => s.hasCloseButton)); return Column( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, diff --git a/lib/android/views/nfc/nfc_auto_close_widget.dart b/lib/android/views/nfc/nfc_auto_close_widget.dart index ccd221db..2b00b07b 100644 --- a/lib/android/views/nfc/nfc_auto_close_widget.dart +++ b/lib/android/views/nfc/nfc_auto_close_widget.dart @@ -22,13 +22,13 @@ import 'models.dart'; import 'nfc_activity_overlay.dart'; import 'nfc_content_widget.dart'; -NfcEventCommand autoClose( +NfcEvent autoClose( {required String title, required String subtitle, required Widget icon, bool showIfHidden = true}) => - setNfcView( - _NfcAutoCloseWidget( + NfcSetViewEvent( + child: _NfcAutoCloseWidget( child: NfcContentWidget( title: title, subtitle: subtitle, @@ -46,7 +46,7 @@ class _NfcAutoCloseWidget extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { ref.listen(androidNfcActivityProvider, (previous, current) { if (current == NfcActivity.ready) { - ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView()); + ref.read(nfcEventNotifier.notifier).send(const NfcHideViewEvent()); } }); diff --git a/lib/android/views/nfc/nfc_count_down_close_widget.dart b/lib/android/views/nfc/nfc_count_down_close_widget.dart index fa9da011..530f09d6 100644 --- a/lib/android/views/nfc/nfc_count_down_close_widget.dart +++ b/lib/android/views/nfc/nfc_count_down_close_widget.dart @@ -24,13 +24,14 @@ import 'models.dart'; import 'nfc_activity_overlay.dart'; import 'nfc_content_widget.dart'; -NfcEventCommand countDownClose({ +NfcEvent countDownClose({ required String title, required String subtitle, required Widget icon, int closeInSec = 3, }) => - setNfcView(_CountDownCloseWidget( + NfcSetViewEvent( + child: _CountDownCloseWidget( closeInSec: closeInSec, child: NfcContentWidget( title: title, @@ -109,6 +110,6 @@ class _CountDownCloseWidgetState extends ConsumerState<_CountDownCloseWidget> { } void hideNow() { - ref.read(nfcEventCommandNotifier.notifier).sendCommand(hideNfcView()); + ref.read(nfcEventNotifier.notifier).send(const NfcHideViewEvent()); } } From f14f16eb9f7b5f2301f4e01f3a45ddb1d48673b1 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 6 Sep 2024 17:21:59 +0200 Subject: [PATCH 34/50] refactor --- lib/android/init.dart | 2 +- lib/android/method_channel_notifier.dart | 4 - lib/android/tap_request_dialog.dart | 23 +++--- lib/android/views/nfc/models.dart | 6 +- lib/android/views/nfc/models.freezed.dart | 80 +++++++++++-------- .../nfc/nfc_activity_command_listener.dart | 28 ++++--- .../views/nfc/nfc_activity_overlay.dart | 43 +++++----- 7 files changed, 100 insertions(+), 86 deletions(-) diff --git a/lib/android/init.dart b/lib/android/init.dart index d1a97a16..a771db74 100644 --- a/lib/android/init.dart +++ b/lib/android/init.dart @@ -107,7 +107,7 @@ Future initialize() async { child: DismissKeyboard( child: YubicoAuthenticatorApp(page: Consumer( builder: (context, ref, child) { - ref.read(nfcEventCommandListener).startListener(context); + ref.read(nfcEventNotifierListener).startListener(context); Timer.run(() { ref.read(featureFlagProvider.notifier) diff --git a/lib/android/method_channel_notifier.dart b/lib/android/method_channel_notifier.dart index 2f6e856f..1b423464 100644 --- a/lib/android/method_channel_notifier.dart +++ b/lib/android/method_channel_notifier.dart @@ -18,7 +18,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'tap_request_dialog.dart'; -import 'views/nfc/nfc_activity_overlay.dart'; class MethodChannelNotifier extends Notifier { final MethodChannel _channel; @@ -30,9 +29,6 @@ class MethodChannelNotifier extends Notifier { Future invoke(String name, [Map params = const {}]) async { - final notifier = ref.read(nfcViewNotifier.notifier); - notifier.setDialogProperties(); - final result = await _channel.invokeMethod(name, params['callArgs']); await ref.read(androidDialogProvider.notifier).waitForDialogClosed(); return result; diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index baddb8a5..af6fab90 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -44,7 +44,7 @@ class _DialogProvider extends Notifier { @override int build() { - final viewNotifier = ref.read(nfcViewNotifier.notifier); + final viewNotifier = ref.read(nfcActivityWidgetPropertiesNotifier.notifier); ref.listen(androidNfcActivityProvider, (previous, current) { processingViewTimeout?.cancel(); @@ -52,7 +52,7 @@ class _DialogProvider extends Notifier { if (!explicitAction) { // setup properties for ad-hoc action - viewNotifier.setDialogProperties(showCloseButton: false); + viewNotifier.update(hasCloseButton: false); } switch (current) { @@ -103,8 +103,8 @@ class _DialogProvider extends Notifier { NfcEvent showTapYourYubiKey() { ref - .read(nfcViewNotifier.notifier) - .setDialogProperties(showCloseButton: true); + .read(nfcActivityWidgetPropertiesNotifier.notifier) + .update(hasCloseButton: true); return NfcSetViewEvent( child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, @@ -115,8 +115,8 @@ class _DialogProvider extends Notifier { NfcEvent showHoldStill() { ref - .read(nfcViewNotifier.notifier) - .setDialogProperties(showCloseButton: false); + .read(nfcActivityWidgetPropertiesNotifier.notifier) + .update(hasCloseButton: false); return NfcSetViewEvent( child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, @@ -127,8 +127,8 @@ class _DialogProvider extends Notifier { NfcEvent showDone() { ref - .read(nfcViewNotifier.notifier) - .setDialogProperties(showCloseButton: true); + .read(nfcActivityWidgetPropertiesNotifier.notifier) + .update(hasCloseButton: true); return NfcSetViewEvent( child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, @@ -140,8 +140,8 @@ class _DialogProvider extends Notifier { NfcEvent showFailed() { ref - .read(nfcViewNotifier.notifier) - .setDialogProperties(showCloseButton: true); + .read(nfcActivityWidgetPropertiesNotifier.notifier) + .update(hasCloseButton: true); return NfcSetViewEvent( child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, @@ -166,7 +166,8 @@ class _DialogProvider extends Notifier { Timer.periodic( const Duration(milliseconds: 200), (timer) { - if (ref.read(nfcViewNotifier.select((s) => !s.visible))) { + if (ref.read( + nfcActivityWidgetPropertiesNotifier.select((s) => !s.visible))) { timer.cancel(); completer.complete(); } diff --git a/lib/android/views/nfc/models.dart b/lib/android/views/nfc/models.dart index 5149528c..81bbb75f 100644 --- a/lib/android/views/nfc/models.dart +++ b/lib/android/views/nfc/models.dart @@ -37,10 +37,10 @@ class NfcSetViewEvent extends NfcEvent { } @freezed -class NfcView with _$NfcView { - factory NfcView({ +class NfcActivityWidgetProperties with _$NfcActivityWidgetProperties { + factory NfcActivityWidgetProperties({ required Widget child, @Default(false) bool visible, @Default(false) bool hasCloseButton, - }) = _NfcView; + }) = _NfcActivityWidgetProperties; } diff --git a/lib/android/views/nfc/models.freezed.dart b/lib/android/views/nfc/models.freezed.dart index d7dc49d1..b4f1a051 100644 --- a/lib/android/views/nfc/models.freezed.dart +++ b/lib/android/views/nfc/models.freezed.dart @@ -15,36 +15,41 @@ final _privateConstructorUsedError = UnsupportedError( 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc -mixin _$NfcView { +mixin _$NfcActivityWidgetProperties { Widget get child => throw _privateConstructorUsedError; bool get visible => throw _privateConstructorUsedError; bool get hasCloseButton => throw _privateConstructorUsedError; - /// Create a copy of NfcView + /// Create a copy of NfcActivityWidgetProperties /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $NfcViewCopyWith get copyWith => throw _privateConstructorUsedError; + $NfcActivityWidgetPropertiesCopyWith + get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $NfcViewCopyWith<$Res> { - factory $NfcViewCopyWith(NfcView value, $Res Function(NfcView) then) = - _$NfcViewCopyWithImpl<$Res, NfcView>; +abstract class $NfcActivityWidgetPropertiesCopyWith<$Res> { + factory $NfcActivityWidgetPropertiesCopyWith( + NfcActivityWidgetProperties value, + $Res Function(NfcActivityWidgetProperties) then) = + _$NfcActivityWidgetPropertiesCopyWithImpl<$Res, + NfcActivityWidgetProperties>; @useResult $Res call({Widget child, bool visible, bool hasCloseButton}); } /// @nodoc -class _$NfcViewCopyWithImpl<$Res, $Val extends NfcView> - implements $NfcViewCopyWith<$Res> { - _$NfcViewCopyWithImpl(this._value, this._then); +class _$NfcActivityWidgetPropertiesCopyWithImpl<$Res, + $Val extends NfcActivityWidgetProperties> + implements $NfcActivityWidgetPropertiesCopyWith<$Res> { + _$NfcActivityWidgetPropertiesCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of NfcView + /// Create a copy of NfcActivityWidgetProperties /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override @@ -71,24 +76,28 @@ class _$NfcViewCopyWithImpl<$Res, $Val extends NfcView> } /// @nodoc -abstract class _$$NfcViewImplCopyWith<$Res> implements $NfcViewCopyWith<$Res> { - factory _$$NfcViewImplCopyWith( - _$NfcViewImpl value, $Res Function(_$NfcViewImpl) then) = - __$$NfcViewImplCopyWithImpl<$Res>; +abstract class _$$NfcActivityWidgetPropertiesImplCopyWith<$Res> + implements $NfcActivityWidgetPropertiesCopyWith<$Res> { + factory _$$NfcActivityWidgetPropertiesImplCopyWith( + _$NfcActivityWidgetPropertiesImpl value, + $Res Function(_$NfcActivityWidgetPropertiesImpl) then) = + __$$NfcActivityWidgetPropertiesImplCopyWithImpl<$Res>; @override @useResult $Res call({Widget child, bool visible, bool hasCloseButton}); } /// @nodoc -class __$$NfcViewImplCopyWithImpl<$Res> - extends _$NfcViewCopyWithImpl<$Res, _$NfcViewImpl> - implements _$$NfcViewImplCopyWith<$Res> { - __$$NfcViewImplCopyWithImpl( - _$NfcViewImpl _value, $Res Function(_$NfcViewImpl) _then) +class __$$NfcActivityWidgetPropertiesImplCopyWithImpl<$Res> + extends _$NfcActivityWidgetPropertiesCopyWithImpl<$Res, + _$NfcActivityWidgetPropertiesImpl> + implements _$$NfcActivityWidgetPropertiesImplCopyWith<$Res> { + __$$NfcActivityWidgetPropertiesImplCopyWithImpl( + _$NfcActivityWidgetPropertiesImpl _value, + $Res Function(_$NfcActivityWidgetPropertiesImpl) _then) : super(_value, _then); - /// Create a copy of NfcView + /// Create a copy of NfcActivityWidgetProperties /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override @@ -97,7 +106,7 @@ class __$$NfcViewImplCopyWithImpl<$Res> Object? visible = null, Object? hasCloseButton = null, }) { - return _then(_$NfcViewImpl( + return _then(_$NfcActivityWidgetPropertiesImpl( child: null == child ? _value.child : child // ignore: cast_nullable_to_non_nullable @@ -116,8 +125,9 @@ class __$$NfcViewImplCopyWithImpl<$Res> /// @nodoc -class _$NfcViewImpl implements _NfcView { - _$NfcViewImpl( +class _$NfcActivityWidgetPropertiesImpl + implements _NfcActivityWidgetProperties { + _$NfcActivityWidgetPropertiesImpl( {required this.child, this.visible = false, this.hasCloseButton = false}); @override @@ -131,14 +141,14 @@ class _$NfcViewImpl implements _NfcView { @override String toString() { - return 'NfcView(child: $child, visible: $visible, hasCloseButton: $hasCloseButton)'; + return 'NfcActivityWidgetProperties(child: $child, visible: $visible, hasCloseButton: $hasCloseButton)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$NfcViewImpl && + other is _$NfcActivityWidgetPropertiesImpl && (identical(other.child, child) || other.child == child) && (identical(other.visible, visible) || other.visible == visible) && (identical(other.hasCloseButton, hasCloseButton) || @@ -148,20 +158,22 @@ class _$NfcViewImpl implements _NfcView { @override int get hashCode => Object.hash(runtimeType, child, visible, hasCloseButton); - /// Create a copy of NfcView + /// Create a copy of NfcActivityWidgetProperties /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$NfcViewImplCopyWith<_$NfcViewImpl> get copyWith => - __$$NfcViewImplCopyWithImpl<_$NfcViewImpl>(this, _$identity); + _$$NfcActivityWidgetPropertiesImplCopyWith<_$NfcActivityWidgetPropertiesImpl> + get copyWith => __$$NfcActivityWidgetPropertiesImplCopyWithImpl< + _$NfcActivityWidgetPropertiesImpl>(this, _$identity); } -abstract class _NfcView implements NfcView { - factory _NfcView( +abstract class _NfcActivityWidgetProperties + implements NfcActivityWidgetProperties { + factory _NfcActivityWidgetProperties( {required final Widget child, final bool visible, - final bool hasCloseButton}) = _$NfcViewImpl; + final bool hasCloseButton}) = _$NfcActivityWidgetPropertiesImpl; @override Widget get child; @@ -170,10 +182,10 @@ abstract class _NfcView implements NfcView { @override bool get hasCloseButton; - /// Create a copy of NfcView + /// Create a copy of NfcActivityWidgetProperties /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$NfcViewImplCopyWith<_$NfcViewImpl> get copyWith => - throw _privateConstructorUsedError; + _$$NfcActivityWidgetPropertiesImplCopyWith<_$NfcActivityWidgetPropertiesImpl> + get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/android/views/nfc/nfc_activity_command_listener.dart b/lib/android/views/nfc/nfc_activity_command_listener.dart index c24f1cb8..a1179e14 100644 --- a/lib/android/views/nfc/nfc_activity_command_listener.dart +++ b/lib/android/views/nfc/nfc_activity_command_listener.dart @@ -26,25 +26,27 @@ import 'nfc_activity_overlay.dart'; final _log = Logger('android.nfc_activity_command_listener'); -final nfcEventCommandListener = - Provider<_NfcEventCommandListener>((ref) => _NfcEventCommandListener(ref)); +final nfcEventNotifierListener = Provider<_NfcEventNotifierListener>( + (ref) => _NfcEventNotifierListener(ref)); -class _NfcEventCommandListener { +class _NfcEventNotifierListener { final ProviderRef _ref; ProviderSubscription? listener; - _NfcEventCommandListener(this._ref); + _NfcEventNotifierListener(this._ref); void startListener(BuildContext context) { listener?.close(); listener = _ref.listen(nfcEventNotifier, (previous, action) { - _log.debug('Change in command for Overlay: $previous -> $action'); + _log.debug('Event change: $previous -> $action'); switch (action) { case (NfcSetViewEvent a): if (!visible && a.showIfHidden) { _show(context, a.child); } else { - _ref.read(nfcViewNotifier.notifier).update(a.child); + _ref + .read(nfcActivityWidgetPropertiesNotifier.notifier) + .update(child: a.child); } break; case (NfcHideViewEvent e): @@ -55,14 +57,14 @@ class _NfcEventCommandListener { } void _show(BuildContext context, Widget child) async { - final notifier = _ref.read(nfcViewNotifier.notifier); - notifier.update(child); + final notifier = _ref.read(nfcActivityWidgetPropertiesNotifier.notifier); + notifier.update(child: child); if (!visible) { visible = true; final result = await showModalBottomSheet( context: context, builder: (BuildContext context) { - return const NfcBottomSheet(); + return const NfcActivityWidget(); }); if (result == null) { // the modal sheet was cancelled by Back button, close button or dismiss @@ -83,8 +85,10 @@ class _NfcEventCommandListener { }); } - bool get visible => _ref.read(nfcViewNotifier.select((s) => s.visible)); + bool get visible => + _ref.read(nfcActivityWidgetPropertiesNotifier.select((s) => s.visible)); - set visible(bool showing) => - _ref.read(nfcViewNotifier.notifier).setShowing(showing); + set visible(bool visible) => _ref + .read(nfcActivityWidgetPropertiesNotifier.notifier) + .update(visible: visible); } diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/views/nfc/nfc_activity_overlay.dart index cab4bb99..f7ad0c9b 100644 --- a/lib/android/views/nfc/nfc_activity_overlay.dart +++ b/lib/android/views/nfc/nfc_activity_overlay.dart @@ -34,37 +34,38 @@ class _NfcEventNotifier extends Notifier { } } -final nfcViewNotifier = - NotifierProvider<_NfcViewNotifier, NfcView>(_NfcViewNotifier.new); +final nfcActivityWidgetPropertiesNotifier = NotifierProvider< + _NfcActivityWidgetPropertiesNotifier, + NfcActivityWidgetProperties>(_NfcActivityWidgetPropertiesNotifier.new); -class _NfcViewNotifier extends Notifier { +class _NfcActivityWidgetPropertiesNotifier + extends Notifier { @override - NfcView build() { - return NfcView(child: const SizedBox()); + NfcActivityWidgetProperties build() { + return NfcActivityWidgetProperties(child: const SizedBox()); } - void update(Widget child) { - state = state.copyWith(child: child); - } - - void setShowing(bool value) { - state = state.copyWith(visible: value); - } - - void setDialogProperties({bool? showCloseButton}) { - state = - state.copyWith(hasCloseButton: showCloseButton ?? state.hasCloseButton); + void update({ + Widget? child, + bool? visible, + bool? hasCloseButton, + }) { + state = state.copyWith( + child: child ?? state.child, + visible: visible ?? state.visible, + hasCloseButton: hasCloseButton ?? state.hasCloseButton); } } -class NfcBottomSheet extends ConsumerWidget { - const NfcBottomSheet({super.key}); +class NfcActivityWidget extends ConsumerWidget { + const NfcActivityWidget({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final widget = ref.watch(nfcViewNotifier.select((s) => s.child)); - final showCloseButton = - ref.watch(nfcViewNotifier.select((s) => s.hasCloseButton)); + final widget = + ref.watch(nfcActivityWidgetPropertiesNotifier.select((s) => s.child)); + final showCloseButton = ref.watch( + nfcActivityWidgetPropertiesNotifier.select((s) => s.hasCloseButton)); return Column( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, From ec4288927eb74ec301fd475f2dba1bf9910d9560 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Mon, 9 Sep 2024 08:37:41 +0200 Subject: [PATCH 35/50] remove explicitAction variable --- lib/android/tap_request_dialog.dart | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/lib/android/tap_request_dialog.dart b/lib/android/tap_request_dialog.dart index af6fab90..2f3a8bcc 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/tap_request_dialog.dart @@ -38,27 +38,22 @@ final androidDialogProvider = class _DialogProvider extends Notifier { Timer? processingViewTimeout; - bool explicitAction = false; - late final l10n = ref.read(l10nProvider); @override int build() { - final viewNotifier = ref.read(nfcActivityWidgetPropertiesNotifier.notifier); - ref.listen(androidNfcActivityProvider, (previous, current) { processingViewTimeout?.cancel(); final notifier = ref.read(nfcEventNotifier.notifier); - if (!explicitAction) { - // setup properties for ad-hoc action - viewNotifier.update(hasCloseButton: false); - } - switch (current) { case NfcActivity.processingStarted: - final timeout = explicitAction ? 300 : 500; - processingViewTimeout = Timer(Duration(milliseconds: timeout), () { + // the "Hold still..." view will be shown after this timeout + // if the action is finished before, the timer might be cancelled + // causing the view not to be visible at all + const timeout = 300; + processingViewTimeout = + Timer(const Duration(milliseconds: timeout), () { notifier.send(showHoldStill()); }); break; @@ -66,7 +61,6 @@ class _DialogProvider extends Notifier { notifier.send(showDone()); notifier .send(const NfcHideViewEvent(delay: Duration(milliseconds: 400))); - explicitAction = false; // next action might not be explicit break; case NfcActivity.processingInterrupted: notifier.send(showFailed()); @@ -83,7 +77,6 @@ class _DialogProvider extends Notifier { final notifier = ref.read(nfcEventNotifier.notifier); switch (call.method) { case 'show': - explicitAction = true; notifier.send(showTapYourYubiKey()); break; @@ -128,7 +121,7 @@ class _DialogProvider extends Notifier { NfcEvent showDone() { ref .read(nfcActivityWidgetPropertiesNotifier.notifier) - .update(hasCloseButton: true); + .update(hasCloseButton: false); return NfcSetViewEvent( child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, @@ -156,7 +149,6 @@ class _DialogProvider extends Notifier { } void cancelDialog() async { - explicitAction = false; await _channel.invokeMethod('cancel'); } From 937893ae372345493087fcce40c19d0f2fed1bf3 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Mon, 9 Sep 2024 10:31:03 +0200 Subject: [PATCH 36/50] update flutter file structure --- lib/android/fido/state.dart | 2 +- lib/android/init.dart | 6 +- lib/android/oath/state.dart | 6 +- .../nfc}/method_channel_notifier.dart | 4 +- .../nfc/models.dart} | 18 +-- .../nfc/models.freezed.dart | 86 +++++++------ .../nfc/nfc_event_notifier.dart} | 53 ++++++-- .../nfc/nfc_overlay_provider.dart} | 49 +++----- .../nfc/views}/nfc_content_widget.dart | 16 +++ .../nfc/views/nfc_overlay_icons.dart} | 22 ++++ .../nfc/views/nfc_overlay_widget.dart} | 40 ++---- lib/android/views/nfc/models.dart | 46 ------- .../views/nfc/nfc_auto_close_widget.dart | 55 --------- .../nfc/nfc_count_down_close_widget.dart | 115 ------------------ lib/android/views/nfc/nfc_failure_icon.dart | 29 ----- 15 files changed, 170 insertions(+), 377 deletions(-) rename lib/android/{ => overlay/nfc}/method_channel_notifier.dart (90%) rename lib/android/{views/nfc/nfc_success_icon.dart => overlay/nfc/models.dart} (65%) rename lib/android/{views => overlay}/nfc/models.freezed.dart (64%) rename lib/android/{views/nfc/nfc_activity_command_listener.dart => overlay/nfc/nfc_event_notifier.dart} (66%) rename lib/android/{tap_request_dialog.dart => overlay/nfc/nfc_overlay_provider.dart} (78%) rename lib/android/{views/nfc => overlay/nfc/views}/nfc_content_widget.dart (64%) rename lib/android/{views/nfc/nfc_progress_bar.dart => overlay/nfc/views/nfc_overlay_icons.dart} (78%) rename lib/android/{views/nfc/nfc_activity_overlay.dart => overlay/nfc/views/nfc_overlay_widget.dart} (64%) delete mode 100644 lib/android/views/nfc/models.dart delete mode 100644 lib/android/views/nfc/nfc_auto_close_widget.dart delete mode 100644 lib/android/views/nfc/nfc_count_down_close_widget.dart delete mode 100644 lib/android/views/nfc/nfc_failure_icon.dart diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index f6c91c9d..875ea7a0 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -32,7 +32,7 @@ import '../../exception/no_data_exception.dart'; import '../../exception/platform_exception_decoder.dart'; import '../../fido/models.dart'; import '../../fido/state.dart'; -import '../method_channel_notifier.dart'; +import '../overlay/nfc/method_channel_notifier.dart'; final _log = Logger('android.fido.state'); diff --git a/lib/android/init.dart b/lib/android/init.dart index a771db74..9f3f5442 100644 --- a/lib/android/init.dart +++ b/lib/android/init.dart @@ -40,10 +40,10 @@ import 'logger.dart'; import 'management/state.dart'; import 'oath/otp_auth_link_handler.dart'; import 'oath/state.dart'; +import 'overlay/nfc/nfc_event_notifier.dart'; +import 'overlay/nfc/nfc_overlay_provider.dart'; import 'qr_scanner/qr_scanner_provider.dart'; import 'state.dart'; -import 'tap_request_dialog.dart'; -import 'views/nfc/nfc_activity_command_listener.dart'; import 'window_state_provider.dart'; Future initialize() async { @@ -123,7 +123,7 @@ Future initialize() async { ref.read(androidWindowStateProvider); // initializes global handler for dialogs - ref.read(androidDialogProvider); + ref.read(nfcOverlayProvider); // set context which will handle otpauth links setupOtpAuthLinkHandler(context); diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index 166a8350..6f37befe 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -36,8 +36,8 @@ import '../../exception/platform_exception_decoder.dart'; import '../../oath/models.dart'; import '../../oath/state.dart'; import '../../widgets/toast.dart'; -import '../method_channel_notifier.dart'; -import '../tap_request_dialog.dart'; +import '../overlay/nfc/method_channel_notifier.dart'; +import '../overlay/nfc/nfc_overlay_provider.dart'; final _log = Logger('android.oath.state'); @@ -156,7 +156,7 @@ Exception handlePlatformException( toast(String message, {bool popStack = false}) => withContext((context) async { - ref.read(androidDialogProvider.notifier).closeDialog(); + ref.read(nfcOverlayProvider.notifier).hideOverlay(); if (popStack) { Navigator.of(context).popUntil((route) { return route.isFirst; diff --git a/lib/android/method_channel_notifier.dart b/lib/android/overlay/nfc/method_channel_notifier.dart similarity index 90% rename from lib/android/method_channel_notifier.dart rename to lib/android/overlay/nfc/method_channel_notifier.dart index 1b423464..4aa03838 100644 --- a/lib/android/method_channel_notifier.dart +++ b/lib/android/overlay/nfc/method_channel_notifier.dart @@ -17,7 +17,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'tap_request_dialog.dart'; +import 'nfc_overlay_provider.dart'; class MethodChannelNotifier extends Notifier { final MethodChannel _channel; @@ -30,7 +30,7 @@ class MethodChannelNotifier extends Notifier { Future invoke(String name, [Map params = const {}]) async { final result = await _channel.invokeMethod(name, params['callArgs']); - await ref.read(androidDialogProvider.notifier).waitForDialogClosed(); + await ref.read(nfcOverlayProvider.notifier).waitForHide(); return result; } } diff --git a/lib/android/views/nfc/nfc_success_icon.dart b/lib/android/overlay/nfc/models.dart similarity index 65% rename from lib/android/views/nfc/nfc_success_icon.dart rename to lib/android/overlay/nfc/models.dart index 8b8ce9b1..fe24afa8 100644 --- a/lib/android/views/nfc/nfc_success_icon.dart +++ b/lib/android/overlay/nfc/models.dart @@ -15,15 +15,15 @@ */ import 'package:flutter/material.dart'; -import 'package:material_symbols_icons/symbols.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; -class NfcIconSuccess extends StatelessWidget { - const NfcIconSuccess({super.key}); +part 'models.freezed.dart'; - @override - Widget build(BuildContext context) => Icon( - Symbols.check, - size: 64, - color: Theme.of(context).colorScheme.primary, - ); +@freezed +class NfcOverlayWidgetProperties with _$NfcOverlayWidgetProperties { + factory NfcOverlayWidgetProperties({ + required Widget child, + @Default(false) bool visible, + @Default(false) bool hasCloseButton, + }) = _NfcOverlayWidgetProperties; } diff --git a/lib/android/views/nfc/models.freezed.dart b/lib/android/overlay/nfc/models.freezed.dart similarity index 64% rename from lib/android/views/nfc/models.freezed.dart rename to lib/android/overlay/nfc/models.freezed.dart index b4f1a051..3216a0e4 100644 --- a/lib/android/views/nfc/models.freezed.dart +++ b/lib/android/overlay/nfc/models.freezed.dart @@ -15,41 +15,40 @@ final _privateConstructorUsedError = UnsupportedError( 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc -mixin _$NfcActivityWidgetProperties { +mixin _$NfcOverlayWidgetProperties { Widget get child => throw _privateConstructorUsedError; bool get visible => throw _privateConstructorUsedError; bool get hasCloseButton => throw _privateConstructorUsedError; - /// Create a copy of NfcActivityWidgetProperties + /// Create a copy of NfcOverlayWidgetProperties /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $NfcActivityWidgetPropertiesCopyWith + $NfcOverlayWidgetPropertiesCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $NfcActivityWidgetPropertiesCopyWith<$Res> { - factory $NfcActivityWidgetPropertiesCopyWith( - NfcActivityWidgetProperties value, - $Res Function(NfcActivityWidgetProperties) then) = - _$NfcActivityWidgetPropertiesCopyWithImpl<$Res, - NfcActivityWidgetProperties>; +abstract class $NfcOverlayWidgetPropertiesCopyWith<$Res> { + factory $NfcOverlayWidgetPropertiesCopyWith(NfcOverlayWidgetProperties value, + $Res Function(NfcOverlayWidgetProperties) then) = + _$NfcOverlayWidgetPropertiesCopyWithImpl<$Res, + NfcOverlayWidgetProperties>; @useResult $Res call({Widget child, bool visible, bool hasCloseButton}); } /// @nodoc -class _$NfcActivityWidgetPropertiesCopyWithImpl<$Res, - $Val extends NfcActivityWidgetProperties> - implements $NfcActivityWidgetPropertiesCopyWith<$Res> { - _$NfcActivityWidgetPropertiesCopyWithImpl(this._value, this._then); +class _$NfcOverlayWidgetPropertiesCopyWithImpl<$Res, + $Val extends NfcOverlayWidgetProperties> + implements $NfcOverlayWidgetPropertiesCopyWith<$Res> { + _$NfcOverlayWidgetPropertiesCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of NfcActivityWidgetProperties + /// Create a copy of NfcOverlayWidgetProperties /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override @@ -76,28 +75,28 @@ class _$NfcActivityWidgetPropertiesCopyWithImpl<$Res, } /// @nodoc -abstract class _$$NfcActivityWidgetPropertiesImplCopyWith<$Res> - implements $NfcActivityWidgetPropertiesCopyWith<$Res> { - factory _$$NfcActivityWidgetPropertiesImplCopyWith( - _$NfcActivityWidgetPropertiesImpl value, - $Res Function(_$NfcActivityWidgetPropertiesImpl) then) = - __$$NfcActivityWidgetPropertiesImplCopyWithImpl<$Res>; +abstract class _$$NfcOverlayWidgetPropertiesImplCopyWith<$Res> + implements $NfcOverlayWidgetPropertiesCopyWith<$Res> { + factory _$$NfcOverlayWidgetPropertiesImplCopyWith( + _$NfcOverlayWidgetPropertiesImpl value, + $Res Function(_$NfcOverlayWidgetPropertiesImpl) then) = + __$$NfcOverlayWidgetPropertiesImplCopyWithImpl<$Res>; @override @useResult $Res call({Widget child, bool visible, bool hasCloseButton}); } /// @nodoc -class __$$NfcActivityWidgetPropertiesImplCopyWithImpl<$Res> - extends _$NfcActivityWidgetPropertiesCopyWithImpl<$Res, - _$NfcActivityWidgetPropertiesImpl> - implements _$$NfcActivityWidgetPropertiesImplCopyWith<$Res> { - __$$NfcActivityWidgetPropertiesImplCopyWithImpl( - _$NfcActivityWidgetPropertiesImpl _value, - $Res Function(_$NfcActivityWidgetPropertiesImpl) _then) +class __$$NfcOverlayWidgetPropertiesImplCopyWithImpl<$Res> + extends _$NfcOverlayWidgetPropertiesCopyWithImpl<$Res, + _$NfcOverlayWidgetPropertiesImpl> + implements _$$NfcOverlayWidgetPropertiesImplCopyWith<$Res> { + __$$NfcOverlayWidgetPropertiesImplCopyWithImpl( + _$NfcOverlayWidgetPropertiesImpl _value, + $Res Function(_$NfcOverlayWidgetPropertiesImpl) _then) : super(_value, _then); - /// Create a copy of NfcActivityWidgetProperties + /// Create a copy of NfcOverlayWidgetProperties /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override @@ -106,7 +105,7 @@ class __$$NfcActivityWidgetPropertiesImplCopyWithImpl<$Res> Object? visible = null, Object? hasCloseButton = null, }) { - return _then(_$NfcActivityWidgetPropertiesImpl( + return _then(_$NfcOverlayWidgetPropertiesImpl( child: null == child ? _value.child : child // ignore: cast_nullable_to_non_nullable @@ -125,9 +124,8 @@ class __$$NfcActivityWidgetPropertiesImplCopyWithImpl<$Res> /// @nodoc -class _$NfcActivityWidgetPropertiesImpl - implements _NfcActivityWidgetProperties { - _$NfcActivityWidgetPropertiesImpl( +class _$NfcOverlayWidgetPropertiesImpl implements _NfcOverlayWidgetProperties { + _$NfcOverlayWidgetPropertiesImpl( {required this.child, this.visible = false, this.hasCloseButton = false}); @override @@ -141,14 +139,14 @@ class _$NfcActivityWidgetPropertiesImpl @override String toString() { - return 'NfcActivityWidgetProperties(child: $child, visible: $visible, hasCloseButton: $hasCloseButton)'; + return 'NfcOverlayWidgetProperties(child: $child, visible: $visible, hasCloseButton: $hasCloseButton)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$NfcActivityWidgetPropertiesImpl && + other is _$NfcOverlayWidgetPropertiesImpl && (identical(other.child, child) || other.child == child) && (identical(other.visible, visible) || other.visible == visible) && (identical(other.hasCloseButton, hasCloseButton) || @@ -158,22 +156,22 @@ class _$NfcActivityWidgetPropertiesImpl @override int get hashCode => Object.hash(runtimeType, child, visible, hasCloseButton); - /// Create a copy of NfcActivityWidgetProperties + /// Create a copy of NfcOverlayWidgetProperties /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$NfcActivityWidgetPropertiesImplCopyWith<_$NfcActivityWidgetPropertiesImpl> - get copyWith => __$$NfcActivityWidgetPropertiesImplCopyWithImpl< - _$NfcActivityWidgetPropertiesImpl>(this, _$identity); + _$$NfcOverlayWidgetPropertiesImplCopyWith<_$NfcOverlayWidgetPropertiesImpl> + get copyWith => __$$NfcOverlayWidgetPropertiesImplCopyWithImpl< + _$NfcOverlayWidgetPropertiesImpl>(this, _$identity); } -abstract class _NfcActivityWidgetProperties - implements NfcActivityWidgetProperties { - factory _NfcActivityWidgetProperties( +abstract class _NfcOverlayWidgetProperties + implements NfcOverlayWidgetProperties { + factory _NfcOverlayWidgetProperties( {required final Widget child, final bool visible, - final bool hasCloseButton}) = _$NfcActivityWidgetPropertiesImpl; + final bool hasCloseButton}) = _$NfcOverlayWidgetPropertiesImpl; @override Widget get child; @@ -182,10 +180,10 @@ abstract class _NfcActivityWidgetProperties @override bool get hasCloseButton; - /// Create a copy of NfcActivityWidgetProperties + /// Create a copy of NfcOverlayWidgetProperties /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$NfcActivityWidgetPropertiesImplCopyWith<_$NfcActivityWidgetPropertiesImpl> + _$$NfcOverlayWidgetPropertiesImplCopyWith<_$NfcOverlayWidgetPropertiesImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/android/views/nfc/nfc_activity_command_listener.dart b/lib/android/overlay/nfc/nfc_event_notifier.dart similarity index 66% rename from lib/android/views/nfc/nfc_activity_command_listener.dart rename to lib/android/overlay/nfc/nfc_event_notifier.dart index a1179e14..6f978ca0 100644 --- a/lib/android/views/nfc/nfc_activity_command_listener.dart +++ b/lib/android/overlay/nfc/nfc_event_notifier.dart @@ -20,11 +20,41 @@ import 'package:logging/logging.dart'; import '../../../app/logging.dart'; import '../../../app/state.dart'; -import '../../tap_request_dialog.dart'; -import 'models.dart'; -import 'nfc_activity_overlay.dart'; +import 'nfc_overlay_provider.dart'; +import 'views/nfc_overlay_widget.dart'; -final _log = Logger('android.nfc_activity_command_listener'); +final _log = Logger('android.nfc_event_notifier'); + +class NfcEvent { + const NfcEvent(); +} + +class NfcHideViewEvent extends NfcEvent { + final Duration delay; + + const NfcHideViewEvent({this.delay = Duration.zero}); +} + +class NfcSetViewEvent extends NfcEvent { + final Widget child; + final bool showIfHidden; + + const NfcSetViewEvent({required this.child, this.showIfHidden = true}); +} + +final nfcEventNotifier = + NotifierProvider<_NfcEventNotifier, NfcEvent>(_NfcEventNotifier.new); + +class _NfcEventNotifier extends Notifier { + @override + NfcEvent build() { + return const NfcEvent(); + } + + void send(NfcEvent event) { + state = event; + } +} final nfcEventNotifierListener = Provider<_NfcEventNotifierListener>( (ref) => _NfcEventNotifierListener(ref)); @@ -45,7 +75,7 @@ class _NfcEventNotifierListener { _show(context, a.child); } else { _ref - .read(nfcActivityWidgetPropertiesNotifier.notifier) + .read(nfcOverlayWidgetProperties.notifier) .update(child: a.child); } break; @@ -57,18 +87,18 @@ class _NfcEventNotifierListener { } void _show(BuildContext context, Widget child) async { - final notifier = _ref.read(nfcActivityWidgetPropertiesNotifier.notifier); + final notifier = _ref.read(nfcOverlayWidgetProperties.notifier); notifier.update(child: child); if (!visible) { visible = true; final result = await showModalBottomSheet( context: context, builder: (BuildContext context) { - return const NfcActivityWidget(); + return const NfcOverlayWidget(); }); if (result == null) { // the modal sheet was cancelled by Back button, close button or dismiss - _ref.read(androidDialogProvider.notifier).cancelDialog(); + _ref.read(nfcOverlayProvider.notifier).onCancel(); } visible = false; } @@ -86,9 +116,8 @@ class _NfcEventNotifierListener { } bool get visible => - _ref.read(nfcActivityWidgetPropertiesNotifier.select((s) => s.visible)); + _ref.read(nfcOverlayWidgetProperties.select((s) => s.visible)); - set visible(bool visible) => _ref - .read(nfcActivityWidgetPropertiesNotifier.notifier) - .update(visible: visible); + set visible(bool visible) => + _ref.read(nfcOverlayWidgetProperties.notifier).update(visible: visible); } diff --git a/lib/android/tap_request_dialog.dart b/lib/android/overlay/nfc/nfc_overlay_provider.dart similarity index 78% rename from lib/android/tap_request_dialog.dart rename to lib/android/overlay/nfc/nfc_overlay_provider.dart index 2f3a8bcc..80958942 100755 --- a/lib/android/tap_request_dialog.dart +++ b/lib/android/overlay/nfc/nfc_overlay_provider.dart @@ -20,23 +20,21 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:logging/logging.dart'; -import '../app/logging.dart'; -import '../app/state.dart'; -import 'state.dart'; -import 'views/nfc/models.dart'; -import 'views/nfc/nfc_activity_overlay.dart'; -import 'views/nfc/nfc_content_widget.dart'; -import 'views/nfc/nfc_failure_icon.dart'; -import 'views/nfc/nfc_progress_bar.dart'; -import 'views/nfc/nfc_success_icon.dart'; +import '../../../app/logging.dart'; +import '../../../app/state.dart'; +import '../../state.dart'; +import 'nfc_event_notifier.dart'; +import 'views/nfc_content_widget.dart'; +import 'views/nfc_overlay_icons.dart'; +import 'views/nfc_overlay_widget.dart'; final _log = Logger('android.tap_request_dialog'); const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); -final androidDialogProvider = - NotifierProvider<_DialogProvider, int>(_DialogProvider.new); +final nfcOverlayProvider = + NotifierProvider<_NfcOverlayProvider, int>(_NfcOverlayProvider.new); -class _DialogProvider extends Notifier { +class _NfcOverlayProvider extends Notifier { Timer? processingViewTimeout; late final l10n = ref.read(l10nProvider); @@ -81,7 +79,7 @@ class _DialogProvider extends Notifier { break; case 'close': - closeDialog(); + hideOverlay(); break; default: @@ -95,9 +93,7 @@ class _DialogProvider extends Notifier { } NfcEvent showTapYourYubiKey() { - ref - .read(nfcActivityWidgetPropertiesNotifier.notifier) - .update(hasCloseButton: true); + ref.read(nfcOverlayWidgetProperties.notifier).update(hasCloseButton: true); return NfcSetViewEvent( child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, @@ -107,9 +103,7 @@ class _DialogProvider extends Notifier { } NfcEvent showHoldStill() { - ref - .read(nfcActivityWidgetPropertiesNotifier.notifier) - .update(hasCloseButton: false); + ref.read(nfcOverlayWidgetProperties.notifier).update(hasCloseButton: false); return NfcSetViewEvent( child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, @@ -119,9 +113,7 @@ class _DialogProvider extends Notifier { } NfcEvent showDone() { - ref - .read(nfcActivityWidgetPropertiesNotifier.notifier) - .update(hasCloseButton: false); + ref.read(nfcOverlayWidgetProperties.notifier).update(hasCloseButton: false); return NfcSetViewEvent( child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, @@ -132,9 +124,7 @@ class _DialogProvider extends Notifier { } NfcEvent showFailed() { - ref - .read(nfcActivityWidgetPropertiesNotifier.notifier) - .update(hasCloseButton: true); + ref.read(nfcOverlayWidgetProperties.notifier).update(hasCloseButton: true); return NfcSetViewEvent( child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, @@ -144,22 +134,21 @@ class _DialogProvider extends Notifier { showIfHidden: false); } - void closeDialog() { + void hideOverlay() { ref.read(nfcEventNotifier.notifier).send(const NfcHideViewEvent()); } - void cancelDialog() async { + void onCancel() async { await _channel.invokeMethod('cancel'); } - Future waitForDialogClosed() async { + Future waitForHide() async { final completer = Completer(); Timer.periodic( const Duration(milliseconds: 200), (timer) { - if (ref.read( - nfcActivityWidgetPropertiesNotifier.select((s) => !s.visible))) { + if (ref.read(nfcOverlayWidgetProperties.select((s) => !s.visible))) { timer.cancel(); completer.complete(); } diff --git a/lib/android/views/nfc/nfc_content_widget.dart b/lib/android/overlay/nfc/views/nfc_content_widget.dart similarity index 64% rename from lib/android/views/nfc/nfc_content_widget.dart rename to lib/android/overlay/nfc/views/nfc_content_widget.dart index b101e3c1..fa784005 100644 --- a/lib/android/views/nfc/nfc_content_widget.dart +++ b/lib/android/overlay/nfc/views/nfc_content_widget.dart @@ -1,3 +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. + */ + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; diff --git a/lib/android/views/nfc/nfc_progress_bar.dart b/lib/android/overlay/nfc/views/nfc_overlay_icons.dart similarity index 78% rename from lib/android/views/nfc/nfc_progress_bar.dart rename to lib/android/overlay/nfc/views/nfc_overlay_icons.dart index 2ea83bee..92ed4a76 100644 --- a/lib/android/views/nfc/nfc_progress_bar.dart +++ b/lib/android/overlay/nfc/views/nfc_overlay_icons.dart @@ -55,3 +55,25 @@ class NfcIconProgressBar extends StatelessWidget { ), ); } + +class NfcIconSuccess extends StatelessWidget { + const NfcIconSuccess({super.key}); + + @override + Widget build(BuildContext context) => Icon( + Symbols.check, + size: 64, + color: Theme.of(context).colorScheme.primary, + ); +} + +class NfcIconFailure extends StatelessWidget { + const NfcIconFailure({super.key}); + + @override + Widget build(BuildContext context) => Icon( + Symbols.close, + size: 64, + color: Theme.of(context).colorScheme.error, + ); +} diff --git a/lib/android/views/nfc/nfc_activity_overlay.dart b/lib/android/overlay/nfc/views/nfc_overlay_widget.dart similarity index 64% rename from lib/android/views/nfc/nfc_activity_overlay.dart rename to lib/android/overlay/nfc/views/nfc_overlay_widget.dart index f7ad0c9b..db608ca0 100644 --- a/lib/android/views/nfc/nfc_activity_overlay.dart +++ b/lib/android/overlay/nfc/views/nfc_overlay_widget.dart @@ -18,31 +18,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'models.dart'; +import '../models.dart'; -final nfcEventNotifier = - NotifierProvider<_NfcEventNotifier, NfcEvent>(_NfcEventNotifier.new); +final nfcOverlayWidgetProperties = + NotifierProvider<_NfcOverlayWidgetProperties, NfcOverlayWidgetProperties>( + _NfcOverlayWidgetProperties.new); -class _NfcEventNotifier extends Notifier { +class _NfcOverlayWidgetProperties extends Notifier { @override - NfcEvent build() { - return const NfcEvent(); - } - - void send(NfcEvent event) { - state = event; - } -} - -final nfcActivityWidgetPropertiesNotifier = NotifierProvider< - _NfcActivityWidgetPropertiesNotifier, - NfcActivityWidgetProperties>(_NfcActivityWidgetPropertiesNotifier.new); - -class _NfcActivityWidgetPropertiesNotifier - extends Notifier { - @override - NfcActivityWidgetProperties build() { - return NfcActivityWidgetProperties(child: const SizedBox()); + NfcOverlayWidgetProperties build() { + return NfcOverlayWidgetProperties(child: const SizedBox()); } void update({ @@ -57,15 +42,14 @@ class _NfcActivityWidgetPropertiesNotifier } } -class NfcActivityWidget extends ConsumerWidget { - const NfcActivityWidget({super.key}); +class NfcOverlayWidget extends ConsumerWidget { + const NfcOverlayWidget({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final widget = - ref.watch(nfcActivityWidgetPropertiesNotifier.select((s) => s.child)); - final showCloseButton = ref.watch( - nfcActivityWidgetPropertiesNotifier.select((s) => s.hasCloseButton)); + final widget = ref.watch(nfcOverlayWidgetProperties.select((s) => s.child)); + final showCloseButton = + ref.watch(nfcOverlayWidgetProperties.select((s) => s.hasCloseButton)); return Column( mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, diff --git a/lib/android/views/nfc/models.dart b/lib/android/views/nfc/models.dart deleted file mode 100644 index 81bbb75f..00000000 --- a/lib/android/views/nfc/models.dart +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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. - */ - -import 'package:flutter/material.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'models.freezed.dart'; - -class NfcEvent { - const NfcEvent(); -} - -class NfcHideViewEvent extends NfcEvent { - final Duration delay; - - const NfcHideViewEvent({this.delay = Duration.zero}); -} - -class NfcSetViewEvent extends NfcEvent { - final Widget child; - final bool showIfHidden; - - const NfcSetViewEvent({required this.child, this.showIfHidden = true}); -} - -@freezed -class NfcActivityWidgetProperties with _$NfcActivityWidgetProperties { - factory NfcActivityWidgetProperties({ - required Widget child, - @Default(false) bool visible, - @Default(false) bool hasCloseButton, - }) = _NfcActivityWidgetProperties; -} diff --git a/lib/android/views/nfc/nfc_auto_close_widget.dart b/lib/android/views/nfc/nfc_auto_close_widget.dart deleted file mode 100644 index 2b00b07b..00000000 --- a/lib/android/views/nfc/nfc_auto_close_widget.dart +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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. - */ - -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import '../../state.dart'; -import 'models.dart'; -import 'nfc_activity_overlay.dart'; -import 'nfc_content_widget.dart'; - -NfcEvent autoClose( - {required String title, - required String subtitle, - required Widget icon, - bool showIfHidden = true}) => - NfcSetViewEvent( - child: _NfcAutoCloseWidget( - child: NfcContentWidget( - title: title, - subtitle: subtitle, - icon: icon, - ), - ), - showIfHidden: showIfHidden); - -class _NfcAutoCloseWidget extends ConsumerWidget { - final Widget child; - - const _NfcAutoCloseWidget({required this.child}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - ref.listen(androidNfcActivityProvider, (previous, current) { - if (current == NfcActivity.ready) { - ref.read(nfcEventNotifier.notifier).send(const NfcHideViewEvent()); - } - }); - - return child; - } -} diff --git a/lib/android/views/nfc/nfc_count_down_close_widget.dart b/lib/android/views/nfc/nfc_count_down_close_widget.dart deleted file mode 100644 index 530f09d6..00000000 --- a/lib/android/views/nfc/nfc_count_down_close_widget.dart +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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. - */ - -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; - -import '../../state.dart'; -import 'models.dart'; -import 'nfc_activity_overlay.dart'; -import 'nfc_content_widget.dart'; - -NfcEvent countDownClose({ - required String title, - required String subtitle, - required Widget icon, - int closeInSec = 3, -}) => - NfcSetViewEvent( - child: _CountDownCloseWidget( - closeInSec: closeInSec, - child: NfcContentWidget( - title: title, - subtitle: subtitle, - icon: icon, - ), - )); - -class _CountDownCloseWidget extends ConsumerStatefulWidget { - final int closeInSec; - final Widget child; - - const _CountDownCloseWidget({required this.child, required this.closeInSec}); - - @override - ConsumerState<_CountDownCloseWidget> createState() => - _CountDownCloseWidgetState(); -} - -class _CountDownCloseWidgetState extends ConsumerState<_CountDownCloseWidget> { - late int counter; - late Timer? timer; - bool shouldHide = false; - - @override - Widget build(BuildContext context) { - ref.listen(androidNfcActivityProvider, (previous, current) { - if (current == NfcActivity.ready) { - timer?.cancel(); - hideNow(); - } - }); - - return Stack( - fit: StackFit.loose, - children: [ - Center(child: widget.child), - Positioned( - bottom: 0, - right: 0, - child: counter > 0 - ? Padding( - padding: const EdgeInsets.all(8.0), - child: Text('Closing in $counter'), - ) - : const SizedBox(), - ) - ], - ); - } - - @override - void initState() { - super.initState(); - counter = widget.closeInSec; - timer = Timer(const Duration(seconds: 0), onTimer); - } - - @override - void dispose() { - timer?.cancel(); - super.dispose(); - } - - void onTimer() async { - timer?.cancel(); - setState(() { - counter--; - }); - - if (counter > 0) { - timer = Timer(const Duration(seconds: 1), onTimer); - } else { - hideNow(); - } - } - - void hideNow() { - ref.read(nfcEventNotifier.notifier).send(const NfcHideViewEvent()); - } -} diff --git a/lib/android/views/nfc/nfc_failure_icon.dart b/lib/android/views/nfc/nfc_failure_icon.dart deleted file mode 100644 index 6942cdb2..00000000 --- a/lib/android/views/nfc/nfc_failure_icon.dart +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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. - */ - -import 'package:flutter/material.dart'; -import 'package:material_symbols_icons/symbols.dart'; - -class NfcIconFailure extends StatelessWidget { - const NfcIconFailure({super.key}); - - @override - Widget build(BuildContext context) => Icon( - Symbols.close, - size: 64, - color: Theme.of(context).colorScheme.error, - ); -} From fea3d4b68d46d6d021bda03809558c28ca91f855 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Mon, 9 Sep 2024 11:15:52 +0200 Subject: [PATCH 37/50] cleanup --- lib/app/views/horizontal_shake.dart | 90 ----------------------- lib/app/views/main_page.dart | 1 - lib/oath/views/add_account_page.dart | 7 +- lib/oath/views/rename_account_dialog.dart | 3 +- 4 files changed, 4 insertions(+), 97 deletions(-) delete mode 100644 lib/app/views/horizontal_shake.dart diff --git a/lib/app/views/horizontal_shake.dart b/lib/app/views/horizontal_shake.dart deleted file mode 100644 index adb156ea..00000000 --- a/lib/app/views/horizontal_shake.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -class HorizontalShake extends StatefulWidget { - final Widget child; - final double shakeAmount; - final int shakeCount; - final Duration shakeDuration; - final Duration delayBetweenShakesDuration; - final Duration startupDelay; - - const HorizontalShake( - {super.key, - required this.child, - this.shakeAmount = 2, - this.shakeCount = 3, - this.shakeDuration = const Duration(milliseconds: 50), - this.delayBetweenShakesDuration = const Duration(seconds: 3), - this.startupDelay = const Duration(seconds: 0)}); - - @override - State createState() => _HorizontalShakeState(); -} - -class _HorizontalShakeState extends State - with SingleTickerProviderStateMixin { - late AnimationController _controller; - late Animation _animation; - late Timer delayTimer; - - int _shakeCounter = 0; - - @override - void initState() { - super.initState(); - _controller = - AnimationController(vsync: this, duration: widget.shakeDuration); - - _controller.addListener(() async { - if (_controller.isCompleted || _controller.isDismissed) { - var delay = const Duration(milliseconds: 0); - if (_shakeCounter++ > widget.shakeCount * 2) { - delay = widget.delayBetweenShakesDuration; - _shakeCounter = 0; - } - - if (delayTimer.isActive) { - delayTimer.cancel(); - } - - delayTimer = Timer(delay, () async { - if (_controller.isCompleted) { - await _controller.reverse(); - } else if (_controller.isDismissed) { - await _controller.forward(); - } - }); - } - }); - - _animation = Tween(begin: 0, end: widget.shakeAmount).animate( - CurvedAnimation(parent: _controller, curve: Curves.ease), - ); - - delayTimer = Timer(widget.startupDelay, () { - _controller.forward(); - }); - } - - @override - void dispose() { - delayTimer.cancel(); - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _controller, - builder: (BuildContext context, Widget? child) { - return Transform.translate( - offset: Offset(_animation.value, 0), - child: widget.child, - ); - }, - ); - } -} diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index 74f92123..e4cca258 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -169,7 +169,6 @@ class MainPage extends ConsumerWidget { ); } - debugPrint('showing section $section'); return switch (section) { Section.home => HomeScreen(data), Section.accounts => OathScreen(data.node.path), diff --git a/lib/oath/views/add_account_page.dart b/lib/oath/views/add_account_page.dart index 036765cd..5d8d39c2 100755 --- a/lib/oath/views/add_account_page.dart +++ b/lib/oath/views/add_account_page.dart @@ -389,8 +389,7 @@ class _OathAddAccountPageState extends ConsumerState { decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_issuer_optional, - helperText: '', - // Prevents dialog resizing when disabled + helperText: '', // Prevents dialog resizing when errorText: (byteLength(issuerText) > issuerMaxLength) ? '' // needs empty string to render as error : issuerNoColon @@ -418,8 +417,8 @@ class _OathAddAccountPageState extends ConsumerState { decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_account_name, - helperText: '', - // Prevents dialog resizing when disabled + helperText: + '', // Prevents dialog resizing when disabled errorText: _submitting ? null : (byteLength(nameText) > nameMaxLength) diff --git a/lib/oath/views/rename_account_dialog.dart b/lib/oath/views/rename_account_dialog.dart index 0b8e4618..f5d5d4e1 100755 --- a/lib/oath/views/rename_account_dialog.dart +++ b/lib/oath/views/rename_account_dialog.dart @@ -215,8 +215,7 @@ class _RenameAccountDialogState extends ConsumerState { decoration: AppInputDecoration( border: const OutlineInputBorder(), labelText: l10n.s_account_name, - helperText: '', - // Prevents dialog resizing when disabled + helperText: '', // Prevents dialog resizing when disabled errorText: !nameNotEmpty ? l10n.l_account_name_required : !isUnique From 58552c0102cef05f412143d5bfd0baae073e7ce6 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Mon, 9 Sep 2024 11:39:38 +0200 Subject: [PATCH 38/50] simplify method channel calls --- lib/android/fido/state.dart | 76 +++------- lib/android/oath/state.dart | 131 +++++------------- .../overlay/nfc/method_channel_notifier.dart | 4 +- 3 files changed, 51 insertions(+), 160 deletions(-) diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index 875ea7a0..3afcfc5f 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -80,7 +80,7 @@ class _FidoStateNotifier extends FidoStateNotifier { }); controller.onCancel = () async { - await fido.cancelReset(); + await fido.invoke('cancelReset'); if (!controller.isClosed) { await subscription.cancel(); } @@ -88,7 +88,7 @@ class _FidoStateNotifier extends FidoStateNotifier { controller.onListen = () async { try { - await fido.reset(); + await fido.invoke('reset'); await controller.sink.close(); ref.invalidateSelf(); } catch (e) { @@ -103,7 +103,8 @@ class _FidoStateNotifier extends FidoStateNotifier { @override Future setPin(String newPin, {String? oldPin}) async { try { - final response = jsonDecode(await fido.setPin(newPin, oldPin: oldPin)); + final response = jsonDecode( + await fido.invoke('setPin', {'pin': oldPin, 'newPin': newPin})); if (response['success'] == true) { _log.debug('FIDO PIN set/change successful'); return PinResult.success(); @@ -129,7 +130,7 @@ class _FidoStateNotifier extends FidoStateNotifier { @override Future unlock(String pin) async { try { - final response = jsonDecode(await fido.unlock(pin)); + final response = jsonDecode(await fido.invoke('unlock', {'pin': pin})); if (response['success'] == true) { _log.debug('FIDO applet unlocked'); @@ -157,7 +158,8 @@ class _FidoStateNotifier extends FidoStateNotifier { @override Future enableEnterpriseAttestation() async { try { - final response = jsonDecode(await fido.enableEnterpriseAttestation()); + final response = + jsonDecode(await fido.invoke('enableEnterpriseAttestation')); if (response['success'] == true) { _log.debug('Enterprise attestation enabled'); @@ -235,14 +237,15 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier { controller.onCancel = () async { if (!controller.isClosed) { _log.debug('Cancelling fingerprint registration'); - await fido.cancelFingerprintRegistration(); + await fido.invoke('cancelRegisterFingerprint'); await registerFpSub.cancel(); } }; controller.onListen = () async { try { - final registerFpResult = await fido.registerFingerprint(name); + final registerFpResult = + await fido.invoke('registerFingerprint', {'name': name}); _log.debug('Finished registerFingerprint with: $registerFpResult'); @@ -277,8 +280,9 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier { Future renameFingerprint( Fingerprint fingerprint, String name) async { try { - final renameFingerprintResponse = - jsonDecode(await fido.renameFingerprint(fingerprint, name)); + final renameFingerprintResponse = jsonDecode(await fido.invoke( + 'renameFingerprint', + {'templateId': fingerprint.templateId, 'name': name})); if (renameFingerprintResponse['success'] == true) { _log.debug('FIDO rename fingerprint succeeded'); @@ -302,8 +306,8 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier { @override Future deleteFingerprint(Fingerprint fingerprint) async { try { - final deleteFingerprintResponse = - jsonDecode(await fido.deleteFingerprint(fingerprint)); + final deleteFingerprintResponse = jsonDecode(await fido + .invoke('deleteFingerprint', {'templateId': fingerprint.templateId})); if (deleteFingerprintResponse['success'] == true) { _log.debug('FIDO delete fingerprint succeeded'); @@ -355,7 +359,8 @@ class _FidoCredentialsNotifier extends FidoCredentialsNotifier { @override Future deleteCredential(FidoCredential credential) async { try { - await fido.deleteCredential(credential); + await fido.invoke('deleteCredential', + {'rpId': credential.rpId, 'credentialId': credential.credentialId}); } on PlatformException catch (pe) { var decodedException = pe.decode(); if (decodedException is CancellationException) { @@ -373,51 +378,4 @@ final _fidoMethodsProvider = NotifierProvider<_FidoMethodChannelNotifier, void>( class _FidoMethodChannelNotifier extends MethodChannelNotifier { _FidoMethodChannelNotifier() : super(const MethodChannel('android.fido.methods')); - late final l10n = ref.read(l10nProvider); - - @override - void build() {} - - Future deleteCredential(FidoCredential credential) async => - invoke('deleteCredential', { - 'callArgs': { - 'rpId': credential.rpId, - 'credentialId': credential.credentialId - } - }); - - Future cancelReset() async => invoke('cancelReset'); - - Future reset() async => invoke('reset'); - - Future setPin(String newPin, {String? oldPin}) async => - invoke('setPin', { - 'callArgs': {'pin': oldPin, 'newPin': newPin}, - }); - - Future unlock(String pin) async => invoke('unlock', { - 'callArgs': {'pin': pin}, - }); - - Future enableEnterpriseAttestation() async => - invoke('enableEnterpriseAttestation'); - - Future registerFingerprint(String? name) async => - invoke('registerFingerprint', { - 'callArgs': {'name': name} - }); - - Future cancelFingerprintRegistration() async => - invoke('cancelRegisterFingerprint'); - - Future renameFingerprint( - Fingerprint fingerprint, String name) async => - invoke('renameFingerprint', { - 'callArgs': {'templateId': fingerprint.templateId, 'name': name}, - }); - - Future deleteFingerprint(Fingerprint fingerprint) async => - invoke('deleteFingerprint', { - 'callArgs': {'templateId': fingerprint.templateId}, - }); } diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index 6f37befe..9105bdb8 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -78,7 +78,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier { // await ref // .read(androidAppContextHandler) // .switchAppContext(Application.accounts); - await oath.reset(); + await oath.invoke('reset'); } catch (e) { _log.debug('Calling reset failed with exception: $e'); } @@ -87,8 +87,8 @@ class _AndroidOathStateNotifier extends OathStateNotifier { @override Future<(bool, bool)> unlock(String password, {bool remember = false}) async { try { - final unlockResponse = - jsonDecode(await oath.unlock(password, remember: remember)); + final unlockResponse = jsonDecode(await oath + .invoke('unlock', {'password': password, 'remember': remember})); _log.debug('applet unlocked'); final unlocked = unlockResponse['unlocked'] == true; @@ -109,7 +109,8 @@ class _AndroidOathStateNotifier extends OathStateNotifier { @override Future setPassword(String? current, String password) async { try { - await oath.setPassword(current, password); + await oath + .invoke('setPassword', {'current': current, 'password': password}); return true; } on PlatformException catch (pe) { final decoded = pe.decode(); @@ -125,7 +126,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier { @override Future unsetPassword(String current) async { try { - await oath.unsetPassword(current); + await oath.invoke('unsetPassword', {'current': current}); return true; } on PlatformException catch (pe) { final decoded = pe.decode(); @@ -141,7 +142,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier { @override Future forgetPassword() async { try { - await oath.forgetPassword(); + await oath.invoke('forgetPassword'); } on PlatformException catch (e) { _log.debug('Calling forgetPassword failed with exception: $e'); } @@ -193,10 +194,10 @@ final addCredentialToAnyProvider = Provider((ref) => (Uri credentialUri, {bool requireTouch = false}) async { final oath = ref.watch(_oathMethodsProvider.notifier); try { - var result = jsonDecode(await oath.addAccountToAny( - credentialUri, - requireTouch: requireTouch, - )); + var result = jsonDecode(await oath.invoke('addAccountToAny', { + 'uri': credentialUri.toString(), + 'requireTouch': requireTouch + })); return OathCredential.fromJson(result['credential']); } on PlatformException catch (pe) { _log.error('Received exception: $pe'); @@ -204,20 +205,20 @@ final addCredentialToAnyProvider = } }); -final addCredentialsToAnyProvider = Provider((ref) => - (List credentialUris, List touchRequired) async { - final oath = ref.read(_oathMethodsProvider.notifier); - try { - _log.debug( - 'Calling android with ${credentialUris.length} credentials to be added'); - var result = - jsonDecode(await oath.addAccounts(credentialUris, touchRequired)); - return result['succeeded'] == credentialUris.length; - } on PlatformException catch (pe) { - _log.error('Received exception: $pe'); - throw handlePlatformException(ref, pe); - } - }); +final addCredentialsToAnyProvider = Provider( + (ref) => (List credentialUris, List touchRequired) async { + final oath = ref.read(_oathMethodsProvider.notifier); + try { + _log.debug( + 'Calling android with ${credentialUris.length} credentials to be added'); + var result = jsonDecode(await oath.invoke('addAccountsToAny', + {'uris': credentialUris, 'requireTouch': touchRequired})); + return result['succeeded'] == credentialUris.length; + } on PlatformException catch (pe) { + _log.error('Received exception: $pe'); + throw handlePlatformException(ref, pe); + } + }); final androidCredentialListProvider = StateNotifierProvider.autoDispose .family?, DevicePath>( @@ -281,7 +282,8 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier { } try { - final resultJson = await oath.calculate(credential); + final resultJson = + await oath.invoke('calculate', {'credentialId': credential.id}); _log.debug('Calculate', resultJson); return OathCode.fromJson(jsonDecode(resultJson)); } on PlatformException catch (pe) { @@ -296,8 +298,8 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier { Future addAccount(Uri credentialUri, {bool requireTouch = false}) async { try { - String resultString = - await oath.addAccount(credentialUri, requireTouch: requireTouch); + String resultString = await oath.invoke('addAccount', + {'uri': credentialUri.toString(), 'requireTouch': requireTouch}); var result = jsonDecode(resultString); return OathCredential.fromJson(result['credential']); } on PlatformException catch (pe) { @@ -309,7 +311,8 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier { Future renameAccount( OathCredential credential, String? issuer, String name) async { try { - final response = await oath.renameAccount(credential, issuer, name); + final response = await oath.invoke('renameAccount', + {'credentialId': credential.id, 'name': name, 'issuer': issuer}); _log.debug('Rename response: $response'); var responseJson = jsonDecode(response); @@ -324,7 +327,7 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier { @override Future deleteAccount(OathCredential credential) async { try { - await oath.deleteAccount(credential); + await oath.invoke('deleteAccount', {'credentialId': credential.id}); } on PlatformException catch (e) { var decoded = e.decode(); if (decoded is CancellationException) { @@ -344,74 +347,4 @@ final _oathMethodsProvider = NotifierProvider<_OathMethodChannelNotifier, void>( class _OathMethodChannelNotifier extends MethodChannelNotifier { _OathMethodChannelNotifier() : super(const MethodChannel('android.oath.methods')); - late final l10n = ref.read(l10nProvider); - - @override - void build() {} - - Future reset() async => invoke('reset'); - - Future unlock(String password, {bool remember = false}) async => - invoke('unlock', { - 'callArgs': {'password': password, 'remember': remember}, - }); - - Future setPassword(String? current, String password) async => - invoke('setPassword', { - 'callArgs': {'current': current, 'password': password}, - }); - - Future unsetPassword(String current) async => - invoke('unsetPassword', { - 'callArgs': {'current': current}, - }); - - Future forgetPassword() async => invoke('forgetPassword'); - - Future calculate(OathCredential credential) async => - invoke('calculate', { - 'callArgs': {'credentialId': credential.id}, - }); - - Future addAccount(Uri credentialUri, - {bool requireTouch = false}) async => - invoke('addAccount', { - 'callArgs': { - 'uri': credentialUri.toString(), - 'requireTouch': requireTouch - }, - }); - - Future addAccounts( - List credentialUris, List touchRequired) async => - invoke('addAccountsToAny', { - 'callArgs': { - 'uris': credentialUris, - 'requireTouch': touchRequired, - } - }); - - Future addAccountToAny(Uri credentialUri, - {bool requireTouch = false}) async => - invoke('addAccountToAny', { - 'callArgs': { - 'uri': credentialUri.toString(), - 'requireTouch': requireTouch - }, - }); - - Future deleteAccount(OathCredential credential) async => - invoke('deleteAccount', { - 'callArgs': {'credentialId': credential.id}, - }); - - Future renameAccount( - OathCredential credential, String? issuer, String name) async => - invoke('renameAccount', { - 'callArgs': { - 'credentialId': credential.id, - 'name': name, - 'issuer': issuer - }, - }); } diff --git a/lib/android/overlay/nfc/method_channel_notifier.dart b/lib/android/overlay/nfc/method_channel_notifier.dart index 4aa03838..13f15cbc 100644 --- a/lib/android/overlay/nfc/method_channel_notifier.dart +++ b/lib/android/overlay/nfc/method_channel_notifier.dart @@ -28,8 +28,8 @@ class MethodChannelNotifier extends Notifier { void build() {} Future invoke(String name, - [Map params = const {}]) async { - final result = await _channel.invokeMethod(name, params['callArgs']); + [Map args = const {}]) async { + final result = await _channel.invokeMethod(name, args); await ref.read(nfcOverlayProvider.notifier).waitForHide(); return result; } From 17e383742c4bf931b90f64f2d1ea7013860f9a69 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Mon, 9 Sep 2024 12:49:01 +0200 Subject: [PATCH 39/50] shorten names, refactor --- .../com/yubico/authenticator/MainActivity.kt | 39 +++++++-------- .../authenticator/device/DeviceManager.kt | 8 +-- .../authenticator/fido/FidoResetHelper.kt | 1 - .../{NfcActivityState.kt => NfcState.kt} | 14 +++--- ...ityDispatcher.kt => NfcStateDispatcher.kt} | 36 ++++--------- lib/android/app_methods.dart | 13 +++-- .../overlay/nfc/nfc_overlay_provider.dart | 17 ++++--- lib/android/state.dart | 50 ++++++++++--------- lib/android/window_state_provider.dart | 2 +- lib/app/views/device_picker.dart | 2 +- lib/app/views/main_page.dart | 6 +-- .../views/message_page_not_initialized.dart | 2 +- 12 files changed, 87 insertions(+), 103 deletions(-) rename android/app/src/main/kotlin/com/yubico/authenticator/yubikit/{NfcActivityState.kt => NfcState.kt} (75%) rename android/app/src/main/kotlin/com/yubico/authenticator/yubikit/{NfcActivityDispatcher.kt => NfcStateDispatcher.kt} (57%) 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 1e5dfc4e..968dc671 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -51,9 +51,9 @@ import com.yubico.authenticator.management.ManagementHandler import com.yubico.authenticator.oath.AppLinkMethodChannel import com.yubico.authenticator.oath.OathManager import com.yubico.authenticator.oath.OathViewModel -import com.yubico.authenticator.yubikit.NfcActivityDispatcher -import com.yubico.authenticator.yubikit.NfcActivityListener -import com.yubico.authenticator.yubikit.NfcActivityState +import com.yubico.authenticator.yubikit.NfcStateDispatcher +import com.yubico.authenticator.yubikit.NfcStateListener +import com.yubico.authenticator.yubikit.NfcState import com.yubico.authenticator.yubikit.DeviceInfoHelper.Companion.getDeviceInfo import com.yubico.authenticator.yubikit.withConnection import com.yubico.yubikit.android.YubiKitManager @@ -79,7 +79,6 @@ import kotlinx.coroutines.launch import org.json.JSONObject import org.slf4j.LoggerFactory import java.io.Closeable -import java.io.IOException import java.security.NoSuchAlgorithmException import java.util.concurrent.Executors import javax.crypto.Mac @@ -104,16 +103,16 @@ class MainActivity : FlutterFragmentActivity() { private val logger = LoggerFactory.getLogger(MainActivity::class.java) - private val nfcActivityListener = object : NfcActivityListener { + private val nfcStateListener = object : NfcStateListener { var appMethodChannel : AppMethodChannel? = null - override fun onChange(newState: NfcActivityState) { + override fun onChange(newState: NfcState) { appMethodChannel?.let { - logger.debug("setting nfc activity state to ${newState.name}") - it.nfcActivityStateChanged(newState) + logger.debug("set nfc state to ${newState.name}") + it.nfcStateChanged(newState) } ?: { - logger.warn("cannot set nfc activity state to ${newState.name} - no method channel") + logger.warn("failed set nfc state to ${newState.name} - no method channel") } } } @@ -131,7 +130,7 @@ class MainActivity : FlutterFragmentActivity() { yubikit = YubiKitManager( UsbYubiKeyManager(this), - NfcYubiKeyManager(this, NfcActivityDispatcher(nfcActivityListener)) + NfcYubiKeyManager(this, NfcStateDispatcher(nfcStateListener)) ) } @@ -319,7 +318,7 @@ class MainActivity : FlutterFragmentActivity() { } if (device is NfcYubiKeyDevice) { - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_STARTED) + appMethodChannel.nfcStateChanged(NfcState.ONGOING) } // If NFC and FIPS check for SCP11b key @@ -342,7 +341,7 @@ class MainActivity : FlutterFragmentActivity() { } catch (e: Exception) { logger.debug("Exception while getting scp keys: ", e) if (device is NfcYubiKeyDevice) { - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) + appMethodChannel.nfcStateChanged(NfcState.FAILURE) } null } @@ -376,14 +375,14 @@ class MainActivity : FlutterFragmentActivity() { try { it.processYubiKey(device) if (!switchedContext && device is NfcYubiKeyDevice) { - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_FINISHED) + appMethodChannel.nfcStateChanged(NfcState.SUCCESS) device.remove { - appMethodChannel.nfcActivityStateChanged(NfcActivityState.READY) + appMethodChannel.nfcStateChanged(NfcState.IDLE) } } } catch (e: Exception) { logger.debug("Caught Exception during YubiKey processing: ", e) - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) + appMethodChannel.nfcStateChanged(NfcState.FAILURE) } } } @@ -420,7 +419,7 @@ class MainActivity : FlutterFragmentActivity() { appLinkMethodChannel = AppLinkMethodChannel(messenger) managementHandler = ManagementHandler(messenger, deviceManager) - nfcActivityListener.appMethodChannel = appMethodChannel + nfcStateListener.appMethodChannel = appMethodChannel flutterStreams = listOf( viewModel.deviceInfo.streamTo(this, messenger, "android.devices.deviceInfo"), @@ -486,7 +485,7 @@ class MainActivity : FlutterFragmentActivity() { } override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) { - nfcActivityListener.appMethodChannel = null + nfcStateListener.appMethodChannel = null flutterStreams.forEach { it.close() } contextManager?.dispose() deviceManager.dispose() @@ -626,14 +625,14 @@ class MainActivity : FlutterFragmentActivity() { fun nfcAdapterStateChanged(value: Boolean) { methodChannel.invokeMethod( "nfcAdapterStateChanged", - JSONObject(mapOf("nfcEnabled" to value)).toString() + JSONObject(mapOf("enabled" to value)).toString() ) } - fun nfcActivityStateChanged(activityState: NfcActivityState) { + fun nfcStateChanged(activityState: NfcState) { lifecycleScope.launch(Dispatchers.Main) { methodChannel.invokeMethod( - "nfcActivityChanged", + "nfcStateChanged", JSONObject(mapOf("state" to activityState.value)).toString() ) } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt index 0eab6c77..8eba8e65 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt @@ -25,7 +25,7 @@ import com.yubico.authenticator.DialogManager import com.yubico.authenticator.MainActivity import com.yubico.authenticator.MainViewModel import com.yubico.authenticator.OperationContext -import com.yubico.authenticator.yubikit.NfcActivityState +import com.yubico.authenticator.yubikit.NfcState import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice import com.yubico.yubikit.core.YubiKeyDevice import com.yubico.yubikit.core.smartcard.scp.ScpKeyParams @@ -222,11 +222,11 @@ class DeviceManager( if (e is ContextDisposedException) { // the key does not have the needed context anymore // we cannot continue - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) + appMethodChannel.nfcStateChanged(NfcState.FAILURE) throw e } - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) + appMethodChannel.nfcStateChanged(NfcState.FAILURE) } } } @@ -242,7 +242,7 @@ class DeviceManager( try { return onNfc.invoke().value } catch (e: Exception) { - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) + appMethodChannel.nfcStateChanged(NfcState.FAILURE) throw e } } 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 df6a15fe..badddb11 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 @@ -25,7 +25,6 @@ import com.yubico.authenticator.NULL import com.yubico.authenticator.device.DeviceManager import com.yubico.authenticator.fido.data.Session import com.yubico.authenticator.fido.data.YubiKitFidoSession -import com.yubico.authenticator.yubikit.NfcActivityState import com.yubico.yubikit.core.application.CommandState import com.yubico.yubikit.core.fido.CtapException import kotlinx.coroutines.CoroutineScope diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityState.kt b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcState.kt similarity index 75% rename from android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityState.kt rename to android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcState.kt index 3d6af5fa..670acf66 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityState.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcState.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yubico. + * Copyright (C) 2023-2024 Yubico. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,10 @@ package com.yubico.authenticator.yubikit -enum class NfcActivityState(val value: Int) { - NOT_ACTIVE(0), - READY(1), - PROCESSING_STARTED(2), - PROCESSING_FINISHED(3), - PROCESSING_INTERRUPTED(4) +enum class NfcState(val value: Int) { + DISABLED(0), + IDLE(1), + ONGOING(2), + SUCCESS(3), + FAILURE(4) } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityDispatcher.kt b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcStateDispatcher.kt similarity index 57% rename from android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityDispatcher.kt rename to android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcStateDispatcher.kt index cea74967..d825bb0e 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcActivityDispatcher.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/NfcStateDispatcher.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yubico. + * Copyright (C) 2023-2024 Yubico. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,6 @@ package com.yubico.authenticator.yubikit import android.app.Activity import android.nfc.NfcAdapter -import android.nfc.Tag -import com.yubico.authenticator.yubikit.NfcActivityListener import com.yubico.yubikit.android.transport.nfc.NfcConfiguration import com.yubico.yubikit.android.transport.nfc.NfcDispatcher @@ -27,16 +25,16 @@ import com.yubico.yubikit.android.transport.nfc.NfcReaderDispatcher import org.slf4j.LoggerFactory -interface NfcActivityListener { - fun onChange(newState: NfcActivityState) +interface NfcStateListener { + fun onChange(newState: NfcState) } -class NfcActivityDispatcher(private val listener: NfcActivityListener) : NfcDispatcher { +class NfcStateDispatcher(private val listener: NfcStateListener) : NfcDispatcher { private lateinit var adapter: NfcAdapter private lateinit var yubikitNfcDispatcher: NfcReaderDispatcher - private val logger = LoggerFactory.getLogger(NfcActivityDispatcher::class.java) + private val logger = LoggerFactory.getLogger(NfcStateDispatcher::class.java) override fun enable( activity: Activity, @@ -46,33 +44,17 @@ class NfcActivityDispatcher(private val listener: NfcActivityListener) : NfcDisp adapter = NfcAdapter.getDefaultAdapter(activity) yubikitNfcDispatcher = NfcReaderDispatcher(adapter) - logger.debug("enabling yubikit NFC activity dispatcher") + logger.debug("enabling yubikit NFC state dispatcher") yubikitNfcDispatcher.enable( activity, nfcConfiguration, - TagInterceptor(listener, handler) + handler ) - //listener.onChange(NfcActivityState.READY) } override fun disable(activity: Activity) { - listener.onChange(NfcActivityState.NOT_ACTIVE) + listener.onChange(NfcState.DISABLED) yubikitNfcDispatcher.disable(activity) - logger.debug("disabling yubikit NFC activity dispatcher") - } - - class TagInterceptor( - private val listener: NfcActivityListener, - private val tagHandler: NfcDispatcher.OnTagHandler - ) : NfcDispatcher.OnTagHandler { - - private val logger = LoggerFactory.getLogger(TagInterceptor::class.java) - - override fun onTag(tag: Tag) { - //listener.onChange(NfcActivityState.PROCESSING_STARTED) - logger.debug("forwarding tag") - tagHandler.onTag(tag) - } - + logger.debug("disabling yubikit NFC state dispatcher") } } \ No newline at end of file diff --git a/lib/android/app_methods.dart b/lib/android/app_methods.dart index 5d6c01b9..d152d82a 100644 --- a/lib/android/app_methods.dart +++ b/lib/android/app_methods.dart @@ -18,6 +18,7 @@ import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; + import '../theme.dart'; import 'state.dart'; @@ -73,16 +74,14 @@ void setupAppMethodsChannel(WidgetRef ref) { switch (call.method) { case 'nfcAdapterStateChanged': { - var nfcEnabled = args['nfcEnabled']; - ref.read(androidNfcStateProvider.notifier).setNfcEnabled(nfcEnabled); + var enabled = args['enabled']; + ref.read(androidNfcAdapterState.notifier).enable(enabled); break; } - case 'nfcActivityChanged': + case 'nfcStateChanged': { - var nfcActivityState = args['state']; - ref - .read(androidNfcActivityProvider.notifier) - .setActivityState(nfcActivityState); + var nfcState = args['state']; + ref.read(androidNfcState.notifier).set(nfcState); break; } default: diff --git a/lib/android/overlay/nfc/nfc_overlay_provider.dart b/lib/android/overlay/nfc/nfc_overlay_provider.dart index 80958942..f068017e 100755 --- a/lib/android/overlay/nfc/nfc_overlay_provider.dart +++ b/lib/android/overlay/nfc/nfc_overlay_provider.dart @@ -40,12 +40,12 @@ class _NfcOverlayProvider extends Notifier { @override int build() { - ref.listen(androidNfcActivityProvider, (previous, current) { + ref.listen(androidNfcState, (previous, current) { processingViewTimeout?.cancel(); final notifier = ref.read(nfcEventNotifier.notifier); switch (current) { - case NfcActivity.processingStarted: + case NfcState.ongoing: // the "Hold still..." view will be shown after this timeout // if the action is finished before, the timer might be cancelled // causing the view not to be visible at all @@ -55,19 +55,20 @@ class _NfcOverlayProvider extends Notifier { notifier.send(showHoldStill()); }); break; - case NfcActivity.processingFinished: + case NfcState.success: notifier.send(showDone()); notifier .send(const NfcHideViewEvent(delay: Duration(milliseconds: 400))); break; - case NfcActivity.processingInterrupted: + case NfcState.failure: notifier.send(showFailed()); break; - case NfcActivity.notActive: - _log.debug('Received not handled notActive'); + case NfcState.disabled: + _log.debug('Received state: disabled'); + break; + case NfcState.idle: + _log.debug('Received state: idle'); break; - case NfcActivity.ready: - _log.debug('Received not handled ready'); } }); diff --git a/lib/android/state.dart b/lib/android/state.dart index 606d6c85..4ccc15bd 100644 --- a/lib/android/state.dart +++ b/lib/android/state.dart @@ -69,33 +69,33 @@ class _AndroidClipboard extends AppClipboard { } } -class NfcStateNotifier extends StateNotifier { - NfcStateNotifier() : super(false); +class NfcAdapterState extends StateNotifier { + NfcAdapterState() : super(false); - void setNfcEnabled(bool value) { + void enable(bool value) { state = value; } } -enum NfcActivity { - notActive, - ready, - processingStarted, - processingFinished, - processingInterrupted, +enum NfcState { + disabled, + idle, + ongoing, + success, + failure, } -class NfcActivityNotifier extends StateNotifier { - NfcActivityNotifier() : super(NfcActivity.notActive); +class NfcStateNotifier extends StateNotifier { + NfcStateNotifier() : super(NfcState.disabled); - void setActivityState(int stateValue) { + void set(int stateValue) { var newState = switch (stateValue) { - 0 => NfcActivity.notActive, - 1 => NfcActivity.ready, - 2 => NfcActivity.processingStarted, - 3 => NfcActivity.processingFinished, - 4 => NfcActivity.processingInterrupted, - _ => NfcActivity.notActive + 0 => NfcState.disabled, + 1 => NfcState.idle, + 2 => NfcState.ongoing, + 3 => NfcState.success, + 4 => NfcState.failure, + _ => NfcState.disabled }; state = newState; @@ -108,12 +108,11 @@ final androidSdkVersionProvider = Provider((ref) => -1); final androidNfcSupportProvider = Provider((ref) => false); -final androidNfcStateProvider = - StateNotifierProvider((ref) => NfcStateNotifier()); +final androidNfcAdapterState = + StateNotifierProvider((ref) => NfcAdapterState()); -final androidNfcActivityProvider = - StateNotifierProvider( - (ref) => NfcActivityNotifier()); +final androidNfcState = StateNotifierProvider( + (ref) => NfcStateNotifier()); final androidSupportedThemesProvider = StateProvider>((ref) { if (ref.read(androidSdkVersionProvider) < 29) { @@ -220,6 +219,7 @@ class NfcTapActionNotifier extends StateNotifier { static const _prefNfcOpenApp = 'prefNfcOpenApp'; static const _prefNfcCopyOtp = 'prefNfcCopyOtp'; final SharedPreferences _prefs; + NfcTapActionNotifier._(this._prefs, super._state); factory NfcTapActionNotifier(SharedPreferences prefs) { @@ -261,6 +261,7 @@ class NfcKbdLayoutNotifier extends StateNotifier { static const String _defaultClipKbdLayout = 'US'; static const _prefClipKbdLayout = 'prefClipKbdLayout'; final SharedPreferences _prefs; + NfcKbdLayoutNotifier(this._prefs) : super(_prefs.getString(_prefClipKbdLayout) ?? _defaultClipKbdLayout); @@ -279,6 +280,7 @@ final androidNfcBypassTouchProvider = class NfcBypassTouchNotifier extends StateNotifier { static const _prefNfcBypassTouch = 'prefNfcBypassTouch'; final SharedPreferences _prefs; + NfcBypassTouchNotifier(this._prefs) : super(_prefs.getBool(_prefNfcBypassTouch) ?? false); @@ -297,6 +299,7 @@ final androidNfcSilenceSoundsProvider = class NfcSilenceSoundsNotifier extends StateNotifier { static const _prefNfcSilenceSounds = 'prefNfcSilenceSounds'; final SharedPreferences _prefs; + NfcSilenceSoundsNotifier(this._prefs) : super(_prefs.getBool(_prefNfcSilenceSounds) ?? false); @@ -315,6 +318,7 @@ final androidUsbLaunchAppProvider = class UsbLaunchAppNotifier extends StateNotifier { static const _prefUsbOpenApp = 'prefUsbOpenApp'; final SharedPreferences _prefs; + UsbLaunchAppNotifier(this._prefs) : super(_prefs.getBool(_prefUsbOpenApp) ?? false); diff --git a/lib/android/window_state_provider.dart b/lib/android/window_state_provider.dart index 62788028..162cd713 100644 --- a/lib/android/window_state_provider.dart +++ b/lib/android/window_state_provider.dart @@ -58,7 +58,7 @@ class _WindowStateNotifier extends StateNotifier if (lifeCycleState == AppLifecycleState.resumed) { _log.debug('Reading nfc enabled value'); isNfcEnabled().then((value) => - _ref.read(androidNfcStateProvider.notifier).setNfcEnabled(value)); + _ref.read(androidNfcAdapterState.notifier).enable(value)); } } else { _log.debug('Ignoring appLifecycleStateChange'); diff --git a/lib/app/views/device_picker.dart b/lib/app/views/device_picker.dart index 126c90da..a1046f6a 100644 --- a/lib/app/views/device_picker.dart +++ b/lib/app/views/device_picker.dart @@ -71,7 +71,7 @@ class DevicePickerContent extends ConsumerWidget { Widget? androidNoKeyWidget; if (isAndroid && devices.isEmpty) { var hasNfcSupport = ref.watch(androidNfcSupportProvider); - var isNfcEnabled = ref.watch(androidNfcStateProvider); + var isNfcEnabled = ref.watch(androidNfcAdapterState); final subtitle = hasNfcSupport && isNfcEnabled ? l10n.l_insert_or_tap_yk : l10n.l_insert_yk; diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index e4cca258..ebf52c96 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -52,8 +52,8 @@ class MainPage extends ConsumerWidget { ); if (isAndroid) { - isNfcEnabled().then((value) => - ref.read(androidNfcStateProvider.notifier).setNfcEnabled(value)); + isNfcEnabled().then( + (value) => ref.read(androidNfcAdapterState.notifier).enable(value)); } // If the current device changes, we need to pop any open dialogs. @@ -98,7 +98,7 @@ class MainPage extends ConsumerWidget { if (deviceNode == null) { if (isAndroid) { var hasNfcSupport = ref.watch(androidNfcSupportProvider); - var isNfcEnabled = ref.watch(androidNfcStateProvider); + var isNfcEnabled = ref.watch(androidNfcAdapterState); return HomeMessagePage( centered: true, graphic: noKeyImage, diff --git a/lib/app/views/message_page_not_initialized.dart b/lib/app/views/message_page_not_initialized.dart index 229ed436..df72e53a 100644 --- a/lib/app/views/message_page_not_initialized.dart +++ b/lib/app/views/message_page_not_initialized.dart @@ -46,7 +46,7 @@ class MessagePageNotInitialized extends ConsumerWidget { if (isAndroid) { var hasNfcSupport = ref.watch(androidNfcSupportProvider); - var isNfcEnabled = ref.watch(androidNfcStateProvider); + var isNfcEnabled = ref.watch(androidNfcAdapterState); var isUsbYubiKey = ref.watch(attachedDevicesProvider).firstOrNull?.transport == Transport.usb; From dc8822d54db29009454638007482485fdec837e7 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Mon, 9 Sep 2024 13:13:36 +0200 Subject: [PATCH 40/50] update class name --- lib/android/init.dart | 6 +++--- lib/android/oath/state.dart | 4 ++-- lib/android/overlay/nfc/method_channel_notifier.dart | 4 ++-- lib/android/overlay/nfc/nfc_event_notifier.dart | 4 ++-- .../{nfc_overlay_provider.dart => nfc_overlay.dart} | 10 +++++----- 5 files changed, 14 insertions(+), 14 deletions(-) rename lib/android/overlay/nfc/{nfc_overlay_provider.dart => nfc_overlay.dart} (96%) diff --git a/lib/android/init.dart b/lib/android/init.dart index 9f3f5442..b638df95 100644 --- a/lib/android/init.dart +++ b/lib/android/init.dart @@ -41,7 +41,7 @@ import 'management/state.dart'; import 'oath/otp_auth_link_handler.dart'; import 'oath/state.dart'; import 'overlay/nfc/nfc_event_notifier.dart'; -import 'overlay/nfc/nfc_overlay_provider.dart'; +import 'overlay/nfc/nfc_overlay.dart'; import 'qr_scanner/qr_scanner_provider.dart'; import 'state.dart'; import 'window_state_provider.dart'; @@ -122,8 +122,8 @@ Future initialize() async { // activates window state provider ref.read(androidWindowStateProvider); - // initializes global handler for dialogs - ref.read(nfcOverlayProvider); + // initializes overlay for nfc events + ref.read(nfcOverlay); // set context which will handle otpauth links setupOtpAuthLinkHandler(context); diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index 9105bdb8..bf9032c3 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -37,7 +37,7 @@ import '../../oath/models.dart'; import '../../oath/state.dart'; import '../../widgets/toast.dart'; import '../overlay/nfc/method_channel_notifier.dart'; -import '../overlay/nfc/nfc_overlay_provider.dart'; +import '../overlay/nfc/nfc_overlay.dart'; final _log = Logger('android.oath.state'); @@ -157,7 +157,7 @@ Exception handlePlatformException( toast(String message, {bool popStack = false}) => withContext((context) async { - ref.read(nfcOverlayProvider.notifier).hideOverlay(); + ref.read(nfcOverlay.notifier).hide(); if (popStack) { Navigator.of(context).popUntil((route) { return route.isFirst; diff --git a/lib/android/overlay/nfc/method_channel_notifier.dart b/lib/android/overlay/nfc/method_channel_notifier.dart index 13f15cbc..d5bfa5b8 100644 --- a/lib/android/overlay/nfc/method_channel_notifier.dart +++ b/lib/android/overlay/nfc/method_channel_notifier.dart @@ -17,7 +17,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'nfc_overlay_provider.dart'; +import 'nfc_overlay.dart'; class MethodChannelNotifier extends Notifier { final MethodChannel _channel; @@ -30,7 +30,7 @@ class MethodChannelNotifier extends Notifier { Future invoke(String name, [Map args = const {}]) async { final result = await _channel.invokeMethod(name, args); - await ref.read(nfcOverlayProvider.notifier).waitForHide(); + await ref.read(nfcOverlay.notifier).waitForHide(); return result; } } diff --git a/lib/android/overlay/nfc/nfc_event_notifier.dart b/lib/android/overlay/nfc/nfc_event_notifier.dart index 6f978ca0..ec240170 100644 --- a/lib/android/overlay/nfc/nfc_event_notifier.dart +++ b/lib/android/overlay/nfc/nfc_event_notifier.dart @@ -20,7 +20,7 @@ import 'package:logging/logging.dart'; import '../../../app/logging.dart'; import '../../../app/state.dart'; -import 'nfc_overlay_provider.dart'; +import 'nfc_overlay.dart'; import 'views/nfc_overlay_widget.dart'; final _log = Logger('android.nfc_event_notifier'); @@ -98,7 +98,7 @@ class _NfcEventNotifierListener { }); if (result == null) { // the modal sheet was cancelled by Back button, close button or dismiss - _ref.read(nfcOverlayProvider.notifier).onCancel(); + _ref.read(nfcOverlay.notifier).onCancel(); } visible = false; } diff --git a/lib/android/overlay/nfc/nfc_overlay_provider.dart b/lib/android/overlay/nfc/nfc_overlay.dart similarity index 96% rename from lib/android/overlay/nfc/nfc_overlay_provider.dart rename to lib/android/overlay/nfc/nfc_overlay.dart index f068017e..f01bf262 100755 --- a/lib/android/overlay/nfc/nfc_overlay_provider.dart +++ b/lib/android/overlay/nfc/nfc_overlay.dart @@ -31,10 +31,10 @@ import 'views/nfc_overlay_widget.dart'; final _log = Logger('android.tap_request_dialog'); const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); -final nfcOverlayProvider = - NotifierProvider<_NfcOverlayProvider, int>(_NfcOverlayProvider.new); +final nfcOverlay = + NotifierProvider<_NfcOverlayNotifier, int>(_NfcOverlayNotifier.new); -class _NfcOverlayProvider extends Notifier { +class _NfcOverlayNotifier extends Notifier { Timer? processingViewTimeout; late final l10n = ref.read(l10nProvider); @@ -80,7 +80,7 @@ class _NfcOverlayProvider extends Notifier { break; case 'close': - hideOverlay(); + hide(); break; default: @@ -135,7 +135,7 @@ class _NfcOverlayProvider extends Notifier { showIfHidden: false); } - void hideOverlay() { + void hide() { ref.read(nfcEventNotifier.notifier).send(const NfcHideViewEvent()); } From e5e61648cf07dae0667690f5e6684cca7bfaa8f9 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Mon, 9 Sep 2024 13:21:56 +0200 Subject: [PATCH 41/50] update names for overlay in native code --- .../com/yubico/authenticator/MainActivity.kt | 10 ++-- ...{DialogManager.kt => NfcOverlayManager.kt} | 16 +++--- .../authenticator/device/DeviceManager.kt | 53 ++++++++++--------- .../fido/FidoConnectionHelper.kt | 5 +- .../yubico/authenticator/fido/FidoManager.kt | 6 +-- .../authenticator/fido/FidoResetHelper.kt | 6 +-- .../management/ManagementConnectionHelper.kt | 2 +- .../yubico/authenticator/oath/OathManager.kt | 11 ++-- lib/android/overlay/nfc/nfc_overlay.dart | 2 +- 9 files changed, 53 insertions(+), 58 deletions(-) rename android/app/src/main/kotlin/com/yubico/authenticator/{DialogManager.kt => NfcOverlayManager.kt} (79%) 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 968dc671..c5daf918 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -396,7 +396,7 @@ class MainActivity : FlutterFragmentActivity() { private var contextManager: AppContextManager? = null private lateinit var deviceManager: DeviceManager private lateinit var appContext: AppContext - private lateinit var dialogManager: DialogManager + private lateinit var nfcOverlayManager: NfcOverlayManager private lateinit var appPreferences: AppPreferences private lateinit var flutterLog: FlutterLog private lateinit var flutterStreams: List @@ -411,8 +411,8 @@ class MainActivity : FlutterFragmentActivity() { messenger = flutterEngine.dartExecutor.binaryMessenger flutterLog = FlutterLog(messenger) appMethodChannel = AppMethodChannel(messenger) - dialogManager = DialogManager(messenger, this.lifecycleScope) - deviceManager = DeviceManager(this, viewModel,appMethodChannel, dialogManager) + nfcOverlayManager = NfcOverlayManager(messenger, this.lifecycleScope) + deviceManager = DeviceManager(this, viewModel,appMethodChannel, nfcOverlayManager) appContext = AppContext(messenger, this.lifecycleScope, viewModel) appPreferences = AppPreferences(this) @@ -463,7 +463,7 @@ class MainActivity : FlutterFragmentActivity() { messenger, deviceManager, oathViewModel, - dialogManager, + nfcOverlayManager, appPreferences ) @@ -473,7 +473,7 @@ class MainActivity : FlutterFragmentActivity() { this, deviceManager, appMethodChannel, - dialogManager, + nfcOverlayManager, fidoViewModel, viewModel ) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/DialogManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/NfcOverlayManager.kt similarity index 79% rename from android/app/src/main/kotlin/com/yubico/authenticator/DialogManager.kt rename to android/app/src/main/kotlin/com/yubico/authenticator/NfcOverlayManager.kt index 425f774d..50660bd6 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/DialogManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/NfcOverlayManager.kt @@ -23,35 +23,35 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -typealias OnDialogCancelled = suspend () -> Unit +typealias OnCancelled = suspend () -> Unit -class DialogManager(messenger: BinaryMessenger, private val coroutineScope: CoroutineScope) { +class NfcOverlayManager(messenger: BinaryMessenger, private val coroutineScope: CoroutineScope) { private val channel = - MethodChannel(messenger, "com.yubico.authenticator.channel.dialog") + MethodChannel(messenger, "com.yubico.authenticator.channel.nfc_overlay") - private var onCancelled: OnDialogCancelled? = null + private var onCancelled: OnCancelled? = null init { channel.setHandler(coroutineScope) { method, _ -> when (method) { - "cancel" -> dialogClosed() + "cancel" -> onClosed() else -> throw NotImplementedError() } } } - fun showDialog(cancelled: OnDialogCancelled?) { + fun show(cancelled: OnCancelled?) { onCancelled = cancelled coroutineScope.launch { channel.invoke("show", null) } } - suspend fun closeDialog() { + suspend fun close() { channel.invoke("close", NULL) } - private suspend fun dialogClosed(): String { + private suspend fun onClosed(): String { onCancelled?.let { onCancelled = null withContext(Dispatchers.Main) { diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt index 8eba8e65..664ac246 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt @@ -21,9 +21,9 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.Observer import com.yubico.authenticator.ContextDisposedException -import com.yubico.authenticator.DialogManager import com.yubico.authenticator.MainActivity import com.yubico.authenticator.MainViewModel +import com.yubico.authenticator.NfcOverlayManager import com.yubico.authenticator.OperationContext import com.yubico.authenticator.yubikit.NfcState import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice @@ -48,7 +48,7 @@ class DeviceManager( private val lifecycleOwner: LifecycleOwner, private val appViewModel: MainViewModel, private val appMethodChannel: MainActivity.AppMethodChannel, - private val dialogManager: DialogManager + private val nfcOverlayManager: NfcOverlayManager ) { var clearDeviceInfoOnDisconnect: Boolean = true @@ -189,24 +189,42 @@ class DeviceManager( suspend fun withKey( onUsb: suspend (UsbYubiKeyDevice) -> T, onNfc: suspend () -> com.yubico.yubikit.core.util.Result, - onDialogCancelled: () -> Unit, + onCancelled: () -> Unit, retryOnNfcFailure: Boolean ): T = appViewModel.connectedYubiKey.value?.let { onUsb(it) } ?: if (retryOnNfcFailure == true) { - onNfcWithRetries(onNfc, onDialogCancelled) + onNfcWithRetries(onNfc, onCancelled) } else { - onNfc(onNfc, onDialogCancelled) + onNfc(onNfc, onCancelled) } + private suspend fun onNfc( + onNfc: suspend () -> com.yubico.yubikit.core.util.Result, + onCancelled: () -> Unit + ): T { + nfcOverlayManager.show { + logger.debug("NFC action was cancelled") + onCancelled.invoke() + } + + try { + return onNfc.invoke().value + } catch (e: Exception) { + appMethodChannel.nfcStateChanged(NfcState.FAILURE) + throw e + } + } + private suspend fun onNfcWithRetries( onNfc: suspend () -> com.yubico.yubikit.core.util.Result, - onDialogCancelled: () -> Unit) : T { + onCancelled: () -> Unit + ): T { - dialogManager.showDialog { - logger.debug("Cancelled dialog") - onDialogCancelled.invoke() + nfcOverlayManager.show { + logger.debug("NFC action with retries was cancelled") + onCancelled.invoke() } while (true) { @@ -230,21 +248,4 @@ class DeviceManager( } } } - - private suspend fun onNfc( - onNfc: suspend () -> com.yubico.yubikit.core.util.Result, - onDialogCancelled: () -> Unit) : T { - - dialogManager.showDialog { - onDialogCancelled.invoke() - } - - try { - return onNfc.invoke().value - } catch (e: Exception) { - appMethodChannel.nfcStateChanged(NfcState.FAILURE) - throw e - } - } - } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt index 45130ea3..cdb51367 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt @@ -17,7 +17,6 @@ package com.yubico.authenticator.fido import com.yubico.authenticator.device.DeviceManager -import com.yubico.authenticator.device.Info import com.yubico.authenticator.fido.data.YubiKitFidoSession import com.yubico.authenticator.yubikit.DeviceInfoHelper.Companion.getDeviceInfo import com.yubico.authenticator.yubikit.withConnection @@ -25,11 +24,9 @@ import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice import com.yubico.yubikit.core.fido.FidoConnection import com.yubico.yubikit.core.util.Result import org.slf4j.LoggerFactory -import java.util.Timer import java.util.TimerTask import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.suspendCoroutine -import kotlin.concurrent.schedule class FidoConnectionHelper(private val deviceManager: DeviceManager) { private var pendingAction: FidoAction? = null @@ -59,7 +56,7 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) { return deviceManager.withKey( onUsb = { useSessionUsb(it, updateDeviceInfo, block) }, onNfc = { useSessionNfc(block) }, - onDialogCancelled = { + onCancelled = { pendingAction?.invoke(Result.failure(CancellationException())) pendingAction = null }, 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 56659c37..0c43122d 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 @@ -18,7 +18,7 @@ package com.yubico.authenticator.fido import androidx.lifecycle.LifecycleOwner import com.yubico.authenticator.AppContextManager -import com.yubico.authenticator.DialogManager +import com.yubico.authenticator.NfcOverlayManager import com.yubico.authenticator.MainActivity import com.yubico.authenticator.MainViewModel import com.yubico.authenticator.NULL @@ -72,7 +72,7 @@ class FidoManager( lifecycleOwner: LifecycleOwner, private val deviceManager: DeviceManager, private val appMethodChannel: MainActivity.AppMethodChannel, - private val dialogManager: DialogManager, + private val nfcOverlayManager: NfcOverlayManager, private val fidoViewModel: FidoViewModel, mainViewModel: MainViewModel ) : AppContextManager(), DeviceListener { @@ -120,7 +120,7 @@ class FidoManager( lifecycleOwner, deviceManager, appMethodChannel, - dialogManager, + nfcOverlayManager, fidoViewModel, mainViewModel, connectionHelper, 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 badddb11..9ba6336f 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 @@ -18,7 +18,7 @@ package com.yubico.authenticator.fido import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner -import com.yubico.authenticator.DialogManager +import com.yubico.authenticator.NfcOverlayManager import com.yubico.authenticator.MainActivity import com.yubico.authenticator.MainViewModel import com.yubico.authenticator.NULL @@ -71,7 +71,7 @@ class FidoResetHelper( private val lifecycleOwner: LifecycleOwner, private val deviceManager: DeviceManager, private val appMethodChannel: MainActivity.AppMethodChannel, - private val dialogManager: DialogManager, + private val nfcOverlayManager: NfcOverlayManager, private val fidoViewModel: FidoViewModel, private val mainViewModel: MainViewModel, private val connectionHelper: FidoConnectionHelper, @@ -214,7 +214,7 @@ class FidoResetHelper( private suspend fun resetOverNfc() = suspendCoroutine { continuation -> coroutineScope.launch { - dialogManager.showDialog { + nfcOverlayManager.show { } fidoViewModel.updateResetState(FidoResetState.Touch) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt index cd40aabd..5c7a6c30 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt @@ -36,7 +36,7 @@ class ManagementConnectionHelper( deviceManager.withKey( onUsb = { useSessionUsb(it, block) }, onNfc = { useSessionNfc(block) }, - onDialogCancelled = { + onCancelled = { action?.invoke(Result.failure(CancellationException())) action = null }, 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 e782cd4d..16952fa6 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 @@ -26,7 +26,6 @@ import com.yubico.authenticator.* import com.yubico.authenticator.device.Capabilities import com.yubico.authenticator.device.DeviceListener import com.yubico.authenticator.device.DeviceManager -import com.yubico.authenticator.device.Info import com.yubico.authenticator.device.UnknownDevice import com.yubico.authenticator.oath.data.Code import com.yubico.authenticator.oath.data.CodeType @@ -64,12 +63,10 @@ import kotlinx.serialization.encodeToString import org.slf4j.LoggerFactory import java.io.IOException import java.net.URI -import java.util.Timer import java.util.TimerTask import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicBoolean import kotlin.coroutines.suspendCoroutine -import kotlin.concurrent.schedule typealias OathAction = (Result) -> Unit @@ -78,7 +75,7 @@ class OathManager( messenger: BinaryMessenger, private val deviceManager: DeviceManager, private val oathViewModel: OathViewModel, - private val dialogManager: DialogManager, + private val nfcOverlayManager: NfcOverlayManager, private val appPreferences: AppPreferences ) : AppContextManager(), DeviceListener { @@ -118,10 +115,10 @@ class OathManager( // cancel any pending actions, except for addToAny if (!addToAny) { pendingAction?.let { - logger.debug("Cancelling pending action/closing nfc dialog.") + logger.debug("Cancelling pending action/closing nfc overlay.") it.invoke(Result.failure(CancellationException())) coroutineScope.launch { - dialogManager.closeDialog() + nfcOverlayManager.close() } pendingAction = null } @@ -695,7 +692,7 @@ class OathManager( return deviceManager.withKey( onUsb = { useSessionUsb(it, updateDeviceInfo, block) }, onNfc = { useSessionNfc(block) }, - onDialogCancelled = { + onCancelled = { pendingAction?.invoke(Result.failure(CancellationException())) pendingAction = null }, diff --git a/lib/android/overlay/nfc/nfc_overlay.dart b/lib/android/overlay/nfc/nfc_overlay.dart index f01bf262..53325fda 100755 --- a/lib/android/overlay/nfc/nfc_overlay.dart +++ b/lib/android/overlay/nfc/nfc_overlay.dart @@ -29,7 +29,7 @@ import 'views/nfc_overlay_icons.dart'; import 'views/nfc_overlay_widget.dart'; final _log = Logger('android.tap_request_dialog'); -const _channel = MethodChannel('com.yubico.authenticator.channel.dialog'); +const _channel = MethodChannel('com.yubico.authenticator.channel.nfc_overlay'); final nfcOverlay = NotifierProvider<_NfcOverlayNotifier, int>(_NfcOverlayNotifier.new); From 7b971f14724ef1b632a4e57a1b2be10b2561e138 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 10 Sep 2024 14:09:18 +0200 Subject: [PATCH 42/50] remove nfc retries --- .../yubico/authenticator/AppContextManager.kt | 4 +- .../com/yubico/authenticator/MainActivity.kt | 9 +++- .../authenticator/device/DeviceManager.kt | 48 +++---------------- .../fido/FidoConnectionHelper.kt | 14 +++--- .../yubico/authenticator/fido/FidoManager.kt | 25 +++++++--- .../management/ManagementConnectionHelper.kt | 3 +- .../yubico/authenticator/oath/OathManager.kt | 47 ++++++++++++++---- lib/android/overlay/nfc/nfc_overlay.dart | 7 ++- lib/app/views/main_page.dart | 41 +++++++--------- 9 files changed, 102 insertions(+), 96 deletions(-) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/AppContextManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/AppContextManager.kt index e40c6ef0..4467b6ad 100755 --- a/android/app/src/main/kotlin/com/yubico/authenticator/AppContextManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/AppContextManager.kt @@ -22,11 +22,13 @@ import com.yubico.yubikit.core.YubiKeyDevice * Provides behavior to run when a YubiKey is inserted/tapped for a specific view of the app. */ abstract class AppContextManager { - abstract suspend fun processYubiKey(device: YubiKeyDevice) + abstract suspend fun processYubiKey(device: YubiKeyDevice): Boolean open fun dispose() {} open fun onPause() {} + + open fun onError() {} } class ContextDisposedException : Exception() \ No newline at end of file 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 c5daf918..57301c5f 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -321,6 +321,7 @@ class MainActivity : FlutterFragmentActivity() { appMethodChannel.nfcStateChanged(NfcState.ONGOING) } + deviceManager.scpKeyParams = null // If NFC and FIPS check for SCP11b key if (device.transport == Transport.NFC && deviceInfo.fipsCapable != 0) { logger.debug("Checking for usable SCP11b key...") @@ -340,6 +341,7 @@ class MainActivity : FlutterFragmentActivity() { } } catch (e: Exception) { logger.debug("Exception while getting scp keys: ", e) + contextManager?.onError() if (device is NfcYubiKeyDevice) { appMethodChannel.nfcStateChanged(NfcState.FAILURE) } @@ -373,9 +375,12 @@ class MainActivity : FlutterFragmentActivity() { contextManager?.let { try { - it.processYubiKey(device) - if (!switchedContext && device is NfcYubiKeyDevice) { + val requestHandled = it.processYubiKey(device) + if (requestHandled) { appMethodChannel.nfcStateChanged(NfcState.SUCCESS) + } + if (!switchedContext && device is NfcYubiKeyDevice) { + device.remove { appMethodChannel.nfcStateChanged(NfcState.IDLE) } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt index 664ac246..8968fd79 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/device/DeviceManager.kt @@ -32,6 +32,7 @@ import com.yubico.yubikit.core.smartcard.scp.ScpKeyParams import com.yubico.yubikit.management.Capability import kotlinx.coroutines.CancellationException import org.slf4j.LoggerFactory +import java.io.IOException interface DeviceListener { // a USB device is connected @@ -174,7 +175,6 @@ class DeviceManager( fun setDeviceInfo(deviceInfo: Info?) { appViewModel.setDeviceInfo(deviceInfo) - this.scpKeyParams = null } fun isUsbKeyConnected(): Boolean { @@ -189,16 +189,12 @@ class DeviceManager( suspend fun withKey( onUsb: suspend (UsbYubiKeyDevice) -> T, onNfc: suspend () -> com.yubico.yubikit.core.util.Result, - onCancelled: () -> Unit, - retryOnNfcFailure: Boolean + onCancelled: () -> Unit ): T = appViewModel.connectedYubiKey.value?.let { onUsb(it) - } ?: if (retryOnNfcFailure == true) { - onNfcWithRetries(onNfc, onCancelled) - } else { - onNfc(onNfc, onCancelled) - } + } ?: onNfc(onNfc, onCancelled) + private suspend fun onNfc( onNfc: suspend () -> com.yubico.yubikit.core.util.Result, @@ -210,42 +206,12 @@ class DeviceManager( } try { - return onNfc.invoke().value + return onNfc.invoke().value.also { + appMethodChannel.nfcStateChanged(NfcState.SUCCESS) + } } catch (e: Exception) { appMethodChannel.nfcStateChanged(NfcState.FAILURE) throw e } } - - private suspend fun onNfcWithRetries( - onNfc: suspend () -> com.yubico.yubikit.core.util.Result, - onCancelled: () -> Unit - ): T { - - nfcOverlayManager.show { - logger.debug("NFC action with retries was cancelled") - onCancelled.invoke() - } - - while (true) { - try { - return onNfc.invoke().value - } catch (e: Exception) { - - logger.debug("NFC action failed, asking to try again. Failure: ", e) - if (e is CancellationException) { - throw e - } - - if (e is ContextDisposedException) { - // the key does not have the needed context anymore - // we cannot continue - appMethodChannel.nfcStateChanged(NfcState.FAILURE) - throw e - } - - appMethodChannel.nfcStateChanged(NfcState.FAILURE) - } - } - } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt index cdb51367..d422706f 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt @@ -30,17 +30,19 @@ import kotlin.coroutines.suspendCoroutine class FidoConnectionHelper(private val deviceManager: DeviceManager) { private var pendingAction: FidoAction? = null - private var deviceInfoTimer: TimerTask? = null - fun invokePending(fidoSession: YubiKitFidoSession) { + fun invokePending(fidoSession: YubiKitFidoSession): Boolean { + var requestHandled = true pendingAction?.let { action -> - action.invoke(Result.success(fidoSession)) pendingAction = null + // it is the pending action who handles this request + requestHandled = false + action.invoke(Result.success(fidoSession)) } + return requestHandled } fun cancelPending() { - deviceInfoTimer?.cancel() pendingAction?.let { action -> action.invoke(Result.failure(CancellationException())) pendingAction = null @@ -49,7 +51,6 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) { suspend fun useSession( updateDeviceInfo: Boolean = false, - retryOnNfcFailure: Boolean = true, block: (YubiKitFidoSession) -> T ): T { FidoManager.updateDeviceInfo.set(updateDeviceInfo) @@ -59,8 +60,7 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) { onCancelled = { pendingAction?.invoke(Result.failure(CancellationException())) pendingAction = null - }, - retryOnNfcFailure = retryOnNfcFailure + } ) } 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 0c43122d..b1d648c1 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 @@ -127,8 +127,6 @@ class FidoManager( pinStore ) - - init { pinRetries = null @@ -176,6 +174,12 @@ class FidoManager( } } + override fun onError() { + super.onError() + logger.debug("Cancel any pending action because of upstream error") + connectionHelper.cancelPending() + } + override fun dispose() { super.dispose() deviceManager.removeDeviceListener(this) @@ -186,15 +190,16 @@ class FidoManager( coroutineScope.cancel() } - override suspend fun processYubiKey(device: YubiKeyDevice) { + override suspend fun processYubiKey(device: YubiKeyDevice): Boolean { + var requestHandled = true try { if (device.supportsConnection(FidoConnection::class.java)) { device.withConnection { connection -> - processYubiKey(connection, device) + requestHandled = processYubiKey(connection, device) } } else { device.withConnection { connection -> - processYubiKey(connection, device) + requestHandled = processYubiKey(connection, device) } } @@ -207,10 +212,14 @@ class FidoManager( // Clear any cached FIDO state fidoViewModel.clearSessionState() + throw e } + + return requestHandled } - private fun processYubiKey(connection: YubiKeyConnection, device: YubiKeyDevice) { + private fun processYubiKey(connection: YubiKeyConnection, device: YubiKeyDevice): Boolean { + var requestHandled = true val fidoSession = if (connection is FidoConnection) { YubiKitFidoSession(connection) @@ -229,7 +238,7 @@ class FidoManager( val sameDevice = currentSession == previousSession if (device is NfcYubiKeyDevice && (sameDevice || resetHelper.inProgress)) { - connectionHelper.invokePending(fidoSession) + requestHandled = connectionHelper.invokePending(fidoSession) } else { if (!sameDevice) { @@ -253,6 +262,8 @@ class FidoManager( Session(infoData, pinStore.hasPin(), pinRetries) ) } + + return requestHandled } private fun getPinPermissionsCM(fidoSession: YubiKitFidoSession): Int { diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt index 5c7a6c30..4bacb524 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/management/ManagementConnectionHelper.kt @@ -39,8 +39,7 @@ class ManagementConnectionHelper( onCancelled = { action?.invoke(Result.failure(CancellationException())) action = null - }, - retryOnNfcFailure = false + } ) private suspend fun useSessionUsb( 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 16952fa6..70640f98 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 @@ -110,6 +110,15 @@ class OathManager( private val updateDeviceInfo = AtomicBoolean(false) private var deviceInfoTimer: TimerTask? = null + override fun onError() { + super.onError() + logger.debug("Cancel any pending action because of upstream error") + pendingAction?.let { action -> + action.invoke(Result.failure(CancellationException())) + pendingAction = null + } + } + override fun onPause() { deviceInfoTimer?.cancel() // cancel any pending actions, except for addToAny @@ -217,7 +226,8 @@ class OathManager( coroutineScope.cancel() } - override suspend fun processYubiKey(device: YubiKeyDevice) { + override suspend fun processYubiKey(device: YubiKeyDevice): Boolean { + var requestHandled = true try { device.withConnection { connection -> val session = getOathSession(connection) @@ -227,6 +237,8 @@ class OathManager( if (pendingAction != null) { pendingAction?.let { action -> pendingAction = null + // it is the pending action who handles this request + requestHandled = false action.invoke(Result.success(session)) } } else { @@ -235,7 +247,7 @@ class OathManager( try { oathViewModel.updateCredentials(calculateOathCodes(session)) } catch (error: Exception) { - logger.error("Failed to refresh codes", error) + logger.error("Failed to refresh codes: ", error) throw error } } @@ -263,7 +275,9 @@ class OathManager( if (addToAny) { // Special "add to any YubiKey" action, process addToAny = false + requestHandled = false action.invoke(Result.success(session)) + requestHandled = true } else { // Awaiting an action for a different device? Fail it and stop processing. action.invoke(Result.failure(IllegalStateException("Wrong deviceId"))) @@ -305,8 +319,17 @@ class OathManager( logger.error("Failed to connect to CCID: ", e) // Clear any cached OATH state oathViewModel.clearSession() + // Remove any pending action + pendingAction?.let { action -> + logger.error("Cancelling pending action") + pendingAction = null + action.invoke(Result.failure(CancellationException())) + } + throw e } + + return requestHandled } private suspend fun addAccountToAny( @@ -316,7 +339,7 @@ class OathManager( val credentialData: CredentialData = CredentialData.parseUri(URI.create(uri)) addToAny = true - return useOathSession(retryOnNfcFailure = false) { session -> + return useOathSession { session -> // We need to check for duplicates here since we haven't yet read the credentials if (session.credentials.any { it.id.contentEquals(credentialData.id) }) { throw IllegalArgumentException() @@ -346,7 +369,7 @@ class OathManager( logger.trace("Adding following accounts: {}", uris) addToAny = true - return useOathSession(retryOnNfcFailure = false) { session -> + return useOathSession { session -> var successCount = 0 for (index in uris.indices) { @@ -398,7 +421,16 @@ class OathManager( val remembered = keyManager.isRemembered(it.deviceId) if (unlocked) { oathViewModel.setSessionState(Session(it, remembered)) - oathViewModel.updateCredentials(calculateOathCodes(it)) + + try { + oathViewModel.updateCredentials(calculateOathCodes(it)) + } catch (e: Exception) { + // after unlocking there was problem getting the codes + // to avoid incomplete session, just reset it so that the user has to + // unlock it again + oathViewModel.clearSession() + throw e + } } jsonSerializer.encodeToString(mapOf("unlocked" to unlocked, "remembered" to remembered)) @@ -682,7 +714,6 @@ class OathManager( private suspend fun useOathSession( unlock: Boolean = true, updateDeviceInfo: Boolean = false, - retryOnNfcFailure: Boolean = true, block: (YubiKitOathSession) -> T ): T { // callers can decide whether the session should be unlocked first @@ -695,8 +726,7 @@ class OathManager( onCancelled = { pendingAction?.invoke(Result.failure(CancellationException())) pendingAction = null - }, - retryOnNfcFailure = retryOnNfcFailure + } ) } @@ -722,7 +752,6 @@ class OathManager( block.invoke(it.value) }) } - // here the coroutine is suspended and waits till pendingAction is // invoked - the pending action result will resume this coroutine } diff --git a/lib/android/overlay/nfc/nfc_overlay.dart b/lib/android/overlay/nfc/nfc_overlay.dart index 53325fda..028a4d7b 100755 --- a/lib/android/overlay/nfc/nfc_overlay.dart +++ b/lib/android/overlay/nfc/nfc_overlay.dart @@ -28,7 +28,7 @@ import 'views/nfc_content_widget.dart'; import 'views/nfc_overlay_icons.dart'; import 'views/nfc_overlay_widget.dart'; -final _log = Logger('android.tap_request_dialog'); +final _log = Logger('android.nfc_overlay'); const _channel = MethodChannel('com.yubico.authenticator.channel.nfc_overlay'); final nfcOverlay = @@ -41,6 +41,7 @@ class _NfcOverlayNotifier extends Notifier { @override int build() { ref.listen(androidNfcState, (previous, current) { + _log.debug('Received nfc state: $current'); processingViewTimeout?.cancel(); final notifier = ref.read(nfcEventNotifier.notifier); @@ -62,6 +63,8 @@ class _NfcOverlayNotifier extends Notifier { break; case NfcState.failure: notifier.send(showFailed()); + notifier + .send(const NfcHideViewEvent(delay: Duration(milliseconds: 800))); break; case NfcState.disabled: _log.debug('Received state: disabled'); @@ -125,7 +128,7 @@ class _NfcOverlayNotifier extends Notifier { } NfcEvent showFailed() { - ref.read(nfcOverlayWidgetProperties.notifier).update(hasCloseButton: true); + ref.read(nfcOverlayWidgetProperties.notifier).update(hasCloseButton: false); return NfcSetViewEvent( child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index ebf52c96..4d088453 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -59,33 +59,24 @@ class MainPage extends ConsumerWidget { // If the current device changes, we need to pop any open dialogs. ref.listen>(currentDeviceDataProvider, (prev, next) { - var canPop = true; - if ((next.value != null) && (prev?.value != null)) { - // if there is change only in fipsApproved, don't pop anything - var nextInfo = next.value!.info; - var prevInfo = prev!.value!.info; - - canPop = - prevInfo.copyWith(fipsApproved: nextInfo.fipsApproved) != nextInfo; - } else if (next.hasValue && (prev != null && prev.isLoading)) { - canPop = false; + final serial = next.value?.info.serial; + if (serial != null && serial == prev?.value?.info.serial) { + return; } - if (canPop) { - Navigator.of(context).popUntil((route) { - return route.isFirst || - [ - 'device_picker', - 'settings', - 'about', - 'licenses', - 'user_interaction_prompt', - 'oath_add_account', - 'oath_icon_pack_dialog', - 'android_qr_scanner_view', - ].contains(route.settings.name); - }); - } + Navigator.of(context).popUntil((route) { + return route.isFirst || + [ + 'device_picker', + 'settings', + 'about', + 'licenses', + 'user_interaction_prompt', + 'oath_add_account', + 'oath_icon_pack_dialog', + 'android_qr_scanner_view', + ].contains(route.settings.name); + }); }); final deviceNode = ref.watch(currentDeviceProvider); From 58167e682bcf264fd3c21d7748125117e157c933 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 10 Sep 2024 14:25:10 +0200 Subject: [PATCH 43/50] minor fixes --- .../main/kotlin/com/yubico/authenticator/oath/OathManager.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 70640f98..9b540cf8 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 @@ -277,7 +277,6 @@ class OathManager( addToAny = false requestHandled = false action.invoke(Result.success(session)) - requestHandled = true } else { // Awaiting an action for a different device? Fail it and stop processing. action.invoke(Result.failure(IllegalStateException("Wrong deviceId"))) @@ -426,8 +425,7 @@ class OathManager( oathViewModel.updateCredentials(calculateOathCodes(it)) } catch (e: Exception) { // after unlocking there was problem getting the codes - // to avoid incomplete session, just reset it so that the user has to - // unlock it again + // to avoid inconsistent UI, clear the session oathViewModel.clearSession() throw e } From a6038cad6ef48484940e372ca551cbfb5b13347f Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 10 Sep 2024 14:49:17 +0200 Subject: [PATCH 44/50] emit nfc overlay messages after FIDO reset --- .../kotlin/com/yubico/authenticator/fido/FidoResetHelper.kt | 3 +++ 1 file changed, 3 insertions(+) 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 9ba6336f..00c2c4a2 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 @@ -25,6 +25,7 @@ import com.yubico.authenticator.NULL import com.yubico.authenticator.device.DeviceManager import com.yubico.authenticator.fido.data.Session import com.yubico.authenticator.fido.data.YubiKitFidoSession +import com.yubico.authenticator.yubikit.NfcState import com.yubico.yubikit.core.application.CommandState import com.yubico.yubikit.core.fido.CtapException import kotlinx.coroutines.CoroutineScope @@ -222,11 +223,13 @@ class FidoResetHelper( FidoManager.updateDeviceInfo.set(true) connectionHelper.useSessionNfc { fidoSession -> doReset(fidoSession) + appMethodChannel.nfcStateChanged(NfcState.SUCCESS) continuation.resume(Unit) }.value } catch (e: Throwable) { // on NFC, clean device info in this situation mainViewModel.setDeviceInfo(null) + appMethodChannel.nfcStateChanged(NfcState.FAILURE) logger.error("Failure during FIDO reset:", e) continuation.resumeWithException(e) } From 827c95f72b67e10fab4634c5dcc95e863681b087 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 10 Sep 2024 19:31:06 +0200 Subject: [PATCH 45/50] handle IO exceptions correctly --- .../fido/FidoConnectionHelper.kt | 8 ++++++ .../yubico/authenticator/fido/FidoManager.kt | 9 +++++-- .../yubico/authenticator/oath/OathManager.kt | 25 ++++++++++++++----- lib/app/views/main_page.dart | 3 ++- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt index d422706f..bc81e92e 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/FidoConnectionHelper.kt @@ -42,6 +42,14 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) { return requestHandled } + fun failPending(e: Exception) { + pendingAction?.let { action -> + logger.error("Failing pending action with {}", e.message) + action.invoke(Result.failure(e)) + pendingAction = null + } + } + fun cancelPending() { pendingAction?.let { action -> action.invoke(Result.failure(CancellationException())) 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 b1d648c1..8f6908ae 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 @@ -210,8 +210,13 @@ class FidoManager( // something went wrong, try to get DeviceInfo from any available connection type logger.error("Failure when processing YubiKey: ", e) - // Clear any cached FIDO state - fidoViewModel.clearSessionState() + connectionHelper.failPending(e) + + if (e !is IOException) { + // we don't clear the session on IOExceptions so that the session is ready for + // a possible re-run of a failed action. + fidoViewModel.clearSessionState() + } throw e } 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 9b540cf8..e5b1036b 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 @@ -266,7 +266,15 @@ class OathManager( ) ) if (!session.isLocked) { - oathViewModel.updateCredentials(calculateOathCodes(session)) + try { + oathViewModel.updateCredentials(calculateOathCodes(session)) + } catch (e: IOException) { + // in this situation we clear the session because otherwise + // the credential list would be in loading state + // clearing the session will prompt the user to try again + oathViewModel.clearSession() + throw e + } } // Awaiting an action for a different or no device? @@ -315,14 +323,19 @@ class OathManager( } } catch (e: Exception) { // OATH not enabled/supported, try to get DeviceInfo over other USB interfaces - logger.error("Failed to connect to CCID: ", e) - // Clear any cached OATH state - oathViewModel.clearSession() + logger.error("Exception during SmartCard connection/OATH session creation: ", e) + // Remove any pending action pendingAction?.let { action -> - logger.error("Cancelling pending action") + logger.error("Failing pending action with {}", e.message) + action.invoke(Result.failure(e)) pendingAction = null - action.invoke(Result.failure(CancellationException())) + } + + if (e !is IOException) { + // we don't clear the session on IOExceptions so that the session is ready for + // a possible re-run of a failed action. + oathViewModel.clearSession() } throw e diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index 4d088453..b2e18721 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -60,7 +60,8 @@ class MainPage extends ConsumerWidget { ref.listen>(currentDeviceDataProvider, (prev, next) { final serial = next.value?.info.serial; - if (serial != null && serial == prev?.value?.info.serial) { + if ((serial != null && serial == prev?.value?.info.serial) || + (next.hasValue && (prev != null && prev.isLoading))) { return; } From 90cd67ac650a4c8882d6cbdf7d4a01fc96f73df9 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 10 Sep 2024 19:32:13 +0200 Subject: [PATCH 46/50] preserve connections in addToAny methods --- lib/android/oath/state.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index bf9032c3..36416670 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -36,6 +36,7 @@ import '../../exception/platform_exception_decoder.dart'; import '../../oath/models.dart'; import '../../oath/state.dart'; import '../../widgets/toast.dart'; +import '../app_methods.dart'; import '../overlay/nfc/method_channel_notifier.dart'; import '../overlay/nfc/nfc_overlay.dart'; @@ -194,6 +195,7 @@ final addCredentialToAnyProvider = Provider((ref) => (Uri credentialUri, {bool requireTouch = false}) async { final oath = ref.watch(_oathMethodsProvider.notifier); try { + await preserveConnectedDeviceWhenPaused(); var result = jsonDecode(await oath.invoke('addAccountToAny', { 'uri': credentialUri.toString(), 'requireTouch': requireTouch @@ -209,6 +211,7 @@ final addCredentialsToAnyProvider = Provider( (ref) => (List credentialUris, List touchRequired) async { final oath = ref.read(_oathMethodsProvider.notifier); try { + await preserveConnectedDeviceWhenPaused(); _log.debug( 'Calling android with ${credentialUris.length} credentials to be added'); var result = jsonDecode(await oath.invoke('addAccountsToAny', From 3432835450d1bdba0f0fb0db95434d66469f2a66 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 11 Sep 2024 00:16:27 +0200 Subject: [PATCH 47/50] use correct context/section for AddToAny --- .../yubico/authenticator/oath/OathManager.kt | 35 ++++++++++++++++++- lib/android/oath/state.dart | 3 -- lib/app/views/main_page.dart | 4 +++ 3 files changed, 38 insertions(+), 4 deletions(-) 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 e5b1036b..c8073b38 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 @@ -232,7 +232,31 @@ class OathManager( device.withConnection { connection -> val session = getOathSession(connection) val previousId = oathViewModel.currentSession()?.deviceId - if (session.deviceId == previousId && device is NfcYubiKeyDevice) { + // only run pending action over NFC + // when the device is still the same + // or when there is no previous device, but we have a pending action + if (device is NfcYubiKeyDevice && + ((session.deviceId == previousId) || + (previousId == null && pendingAction != null)) + ) { + // update session if it is null + if (previousId == null) { + oathViewModel.setSessionState( + Session( + session, + keyManager.isRemembered(session.deviceId) + ) + ) + + if (!session.isLocked) { + try { + // only load the accounts without calculating the codes + oathViewModel.updateCredentials(getAccounts(session)) + } catch (e: IOException) { + oathViewModel.updateCredentials(emptyMap()) + } } + } + // Either run a pending action, or just refresh codes if (pendingAction != null) { pendingAction?.let { action -> @@ -686,6 +710,15 @@ class OathManager( return session } + private fun getAccounts(session: YubiKitOathSession): Map { + return session.credentials.map { credential -> + Pair( + Credential(credential, session.deviceId), + null + ) + }.toMap() + } + private fun calculateOathCodes(session: YubiKitOathSession): Map { val isUsbKey = deviceManager.isUsbKeyConnected() var timestamp = System.currentTimeMillis() diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index 36416670..2b47e892 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -76,9 +76,6 @@ class _AndroidOathStateNotifier extends OathStateNotifier { @override Future reset() async { try { - // await ref - // .read(androidAppContextHandler) - // .switchAppContext(Application.accounts); await oath.invoke('reset'); } catch (e) { _log.debug('Calling reset failed with exception: $e'); diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index b2e18721..39176e49 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -109,6 +109,10 @@ class MainPage extends ConsumerWidget { label: Text(l10n.s_add_account), icon: const Icon(Symbols.person_add_alt), onPressed: () async { + // make sure we execute the "Add account" in OATH section + ref + .read(currentSectionProvider.notifier) + .setCurrentSection(Section.accounts); await addOathAccount(context, ref); }) ], From 769aeda6d37cad3be3be87437e067c37c17a8daf Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 11 Sep 2024 07:23:21 +0200 Subject: [PATCH 48/50] improve comparison to handle exception states --- lib/app/views/main_page.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index 39176e49..c94e435a 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -59,8 +59,10 @@ class MainPage extends ConsumerWidget { // If the current device changes, we need to pop any open dialogs. ref.listen>(currentDeviceDataProvider, (prev, next) { - final serial = next.value?.info.serial; - if ((serial != null && serial == prev?.value?.info.serial) || + final serial = next.hasValue == true ? next.value?.info.serial : null; + final prevSerial = + prev?.hasValue == true ? prev?.value?.info.serial : null; + if ((serial != null && serial == prevSerial) || (next.hasValue && (prev != null && prev.isLoading))) { return; } From a79e2dace819009864a3c1cb8c08cb0d67b4809e Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 11 Sep 2024 07:25:11 +0200 Subject: [PATCH 49/50] don't swallow context disposed exceptions --- lib/exception/platform_exception_decoder.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/exception/platform_exception_decoder.dart b/lib/exception/platform_exception_decoder.dart index 1a71812c..f45f56da 100644 --- a/lib/exception/platform_exception_decoder.dart +++ b/lib/exception/platform_exception_decoder.dart @@ -24,17 +24,11 @@ extension Decoder on PlatformException { bool _isApduException() => code == 'ApduException'; - bool _isContextDisposed() => code == 'ContextDisposedException'; - Exception decode() { if (_isCancellation()) { return CancellationException(); } - if (_isContextDisposed()) { - return CancellationException(); - } - if (message != null && _isApduException()) { final regExp = RegExp( r'^com.yubico.yubikit.core.smartcard.ApduException: APDU error: 0x(.*)$'); From 2d394d829c546f98586e00f11d482adc97f70126 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 11 Sep 2024 08:07:00 +0200 Subject: [PATCH 50/50] fix copyright year in changed files --- .../main/kotlin/com/yubico/authenticator/NfcOverlayManager.kt | 2 +- lib/android/window_state_provider.dart | 2 +- lib/oath/views/add_account_page.dart | 2 +- lib/oath/views/rename_account_dialog.dart | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/NfcOverlayManager.kt b/android/app/src/main/kotlin/com/yubico/authenticator/NfcOverlayManager.kt index 50660bd6..c4f29d53 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/NfcOverlayManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/NfcOverlayManager.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. diff --git a/lib/android/window_state_provider.dart b/lib/android/window_state_provider.dart index 162cd713..68353509 100644 --- a/lib/android/window_state_provider.dart +++ b/lib/android/window_state_provider.dart @@ -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. diff --git a/lib/oath/views/add_account_page.dart b/lib/oath/views/add_account_page.dart index 5d8d39c2..6504d000 100755 --- a/lib/oath/views/add_account_page.dart +++ b/lib/oath/views/add_account_page.dart @@ -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. diff --git a/lib/oath/views/rename_account_dialog.dart b/lib/oath/views/rename_account_dialog.dart index f5d5d4e1..cd6995d5 100755 --- a/lib/oath/views/rename_account_dialog.dart +++ b/lib/oath/views/rename_account_dialog.dart @@ -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.