From ec87865643b460b98ed8ec45dc2b79b245295576 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 8 Jun 2023 17:04:17 +0200 Subject: [PATCH 01/71] 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 173eb214b..36e97a580 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 ab88b531b..e8b9d37e2 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 000000000..0bb14899e --- /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 000000000..3d6af5fae --- /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 9b47c8d94..75df6b6ff 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 ea1b46b89..0b3c20cc6 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 1a6c1be1e..34ae7a916 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 000000000..b0526cf93 --- /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 000000000..9b160bb48 --- /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 000000000..703ffa08a --- /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 229272c39..5091dcf60 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/71] 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 c3df2e049..f6cf0eea6 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 2157eba68..9253e5300 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 7e80473c0..fa0ef3145 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 b0526cf93..bc25493c6 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 9b160bb48..7eff20c51 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 000000000..31df2fc4a --- /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/71] 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 000000000..2cd270a10 --- /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 7eff20c51..1e61bf01a 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 31df2fc4a..a1863b1a9 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/71] 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 a1863b1a9..adb156ea1 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/71] 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 925160e1c..78f13f09a 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/71] 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 | 192 +++++---- .../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 | 350 ++++++++------- .../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 | 111 ++++- lib/l10n/app_en.arb | 111 ++++- lib/l10n/app_fr.arb | 111 ++++- lib/l10n/app_ja.arb | 87 +++- lib/l10n/app_pl.arb | 105 ++++- lib/theme.dart | 9 +- lib/widgets/pulsing.dart | 68 +++ 27 files changed, 1826 insertions(+), 542 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 f6cf0eea6..425f774d1 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 62fa7546d..fe941f4d0 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 ae0d8945d..000000000 --- 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 adc2bee73..445067f26 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 6919c2cbf..09b98cb1b 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 a89a8526d..d7f4d3672 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 24ac7b859..4c82c8560 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 ac78d2c5b..000000000 --- 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 44e5bdcbb..1a5802f19 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) + useOathSession { session -> + val credential = getCredential(uri) + val renamed = Credential( + session.renameCredential(credential, name, issuer), + session.deviceId + ) + oathViewModel.renameCredential( Credential(credential, session.deviceId), - renamedCredential + renamed ) - jsonSerializer.encodeToString(renamedCredential) +// // 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,51 +724,43 @@ 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 + 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 } + 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 } - 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() - } + } // while } - 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") - 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 60c45ab9c..b827605f5 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 0bb14899e..cea749675 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 5c254c38e..e462de1b5 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 956c58b78..b7cc7c888 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 6322acd0b..607067abc 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 03b0bdcf2..fbcc6c3eb 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 66fc7fb06..ccc89aeaf 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; - - static _DTitle fromId(int? id) => - const { - 0: _DTitle.tapKey, - 1: _DTitle.operationSuccessful, - 2: _DTitle.operationFailed - }[id] ?? - _DTitle.invalid; -} +final androidDialogProvider = + NotifierProvider<_DialogProvider, int>(_DialogProvider.new); -// _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; - - static const int dialogDescriptionOathIndex = 100; - static const int dialogDescriptionFidoIndex = 200; - - 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; -} +class _DialogProvider extends Notifier { + Timer? processingTimer; + bool explicitAction = false; + + @override + int build() { + final l10n = ref.read(l10nProvider); + ref.listen(androidNfcActivityProvider, (previous, current) { + final notifier = ref.read(nfcActivityCommandNotifier.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, + showSuccess: false, + ); + } -final androidDialogProvider = Provider<_DialogProvider>( - (ref) { - return _DialogProvider(ref.watch(withContextProvider)); - }, -); + final properties = ref.read(nfcActivityWidgetNotifier); -class _DialogProvider { - final WithContext _withContext; - final Widget _icon = const NfcActivityWidget(width: 64, height: 64); - UserInteractionController? _controller; + 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), () { + 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(); + + Timer.periodic( + const Duration(milliseconds: 200), + (timer) { + if (!ref.read(nfcActivityWidgetNotifier.select((s) => s.isShowing))) { + timer.cancel(); + completer.complete(); + } + }, + ); + + await completer.future; } +} + +class _NfcActivityWidgetView extends StatelessWidget { + final bool inProgress; + final String? title; + final String? subtitle; - 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, - _ => '' - }; + 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) + ], + ), + ); } +} - 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), - ); - }); +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; } +} - 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'); - }, - ); - }); +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 000000000..c25d2d3fc --- /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 000000000..985b016e1 --- /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 1f45ebabc..433e3ab3a 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 37629fcb5..52854fb34 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 9400d8859..cf2314cb3 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", - - "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_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", + "@_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_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, + "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 9954b87e0..a3e534d77 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", - - "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_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", + "@_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_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_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 9e43e0887..5089233d5 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", - - "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_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", + "@_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_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, + "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 09ac00a0d..2675abf71 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_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_unset_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_failure": "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 50623db1e..ad1fa188d 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", - - "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", - + "@_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_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 d929fa31f..b2d82e13e 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 000000000..9e941b1ab --- /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/71] 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 | 166 ++++++++++++++-------------- lib/l10n/app_en.arb | 166 ++++++++++++++-------------- lib/l10n/app_fr.arb | 166 ++++++++++++++-------------- lib/l10n/app_ja.arb | 166 ++++++++++++++-------------- lib/l10n/app_pl.arb | 166 ++++++++++++++-------------- 8 files changed, 484 insertions(+), 489 deletions(-) diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index b7cc7c888..a4de36fcf 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 fbcc6c3eb..33937b6be 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 ccc89aeaf..52b9ad13f 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 cf2314cb3..5ea92aded 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, - - "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, - "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": { + "@_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_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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "s_nfc_fido_delete_passkey_processing": null, + "s_nfc_fido_delete_passkey_success": null, + "s_nfc_fido_delete_passkey_failure": null, + + "@_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 a3e534d77..70f7966f3 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", - - "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_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": { + "@_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_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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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", + "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": {}, + "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 5089233d5..57d8662ad 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, - - "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, - "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": { + "@_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_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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "s_nfc_fido_delete_passkey_processing": null, + "s_nfc_fido_delete_passkey_success": null, + "s_nfc_fido_delete_passkey_failure": null, + + "@_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 2675abf71..6c3a180ef 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, - - "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アプリケーションのロックを解除する", - "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_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": { + "@_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_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, + "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, + "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": "アクション:新しいアカウントを追加", + "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": "アクション:アカウント名を変更", + "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": "アクション:アカウントを削除", + "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コードを計算", + "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": "アクション:複数アカウントを追加", + "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アプリケーションをリセット", + "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, + "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, + "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, + "s_nfc_fido_delete_passkey_processing": null, + "s_nfc_fido_delete_passkey_success": null, + "s_nfc_fido_delete_passkey_failure": null, + + "@_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 ad1fa188d..1d3f24773 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, - - "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, - "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": { + "@_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_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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "s_nfc_fido_delete_passkey_processing": null, + "s_nfc_fido_delete_passkey_success": null, + "s_nfc_fido_delete_passkey_failure": null, + + "@_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/71] 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 cb06fd9a2..2e9f0f0df 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 a4de36fcf..fb9b91782 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 33937b6be..f4c0b6f51 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 52b9ad13f..fc5ef4f0a 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 5ea92aded..803b9f452 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 70f7966f3..03ef0c5ed 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 57d8662ad..63bdf4078 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 6c3a180ef..f857f236f 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 1d3f24773..9cbdc4b54 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/71] 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 4a88bcfb3..b742fd617 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 cc77e3081..14c34773f 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 bf6a26ce6..be281cb74 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/71] 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 bb1db2688..fdda7bde7 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 371e077c6..2e7acdea8 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 b742fd617..036765cda 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 be281cb74..ad5bdf54f 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/71] 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 fc5ef4f0a..9b7991f3e 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 000000000..bc917f95e --- /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 000000000..28fbc7d0f --- /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 c25d2d3fc..90fef3ffe 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 985b016e1..288d7bf6e 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 433e3ab3a..1f45ebabc 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 52854fb34..37629fcb5 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/71] 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 9b7991f3e..520a12697 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 000000000..89b5d5321 --- /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 9e941b1ab..000000000 --- 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/71] 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 2cd270a10..000000000 --- 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 f41b4722a..000000000 --- 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 91374042a..000000000 --- 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 703ffa08a..000000000 --- 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 ebf77ad72..7e6cbc25e 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/71] 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 607067abc..d1a97a165 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 520a12697..a9055ad72 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 90fef3ffe..13756d35a 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 288d7bf6e..f9e0c2b89 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/71] 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 fb9b91782..d0229a676 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 000000000..dd8d23272 --- /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 f4c0b6f51..6e6dcaa8b 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 a9055ad72..53032a8c8 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 000000000..37fe96672 --- /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/71] 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 53032a8c8..50e7c170a 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 bc917f95e..b1a822842 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/71] 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 d0229a676..c06fdf4f6 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 6e6dcaa8b..068cd7f2b 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 50e7c170a..08bacc9e2 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 f9e0c2b89..6871e7795 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 37fe96672..6822134ae 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 b34fb4fbe..b068a0340 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 ffdabb098..fe15677db 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 6077ec1ae..9ffd11ba2 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 d08331c15..237228e46 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 2e3d3e995..cda0ae114 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/71] 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 08bacc9e2..65c03abf4 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 6822134ae..95106e7fc 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 b068a0340..6b07a5ce2 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 fe15677db..1012193ac 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 9ffd11ba2..d62e16f01 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 237228e46..c1b66ff4f 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 cda0ae114..5079b7a35 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/71] 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 c06fdf4f6..8e6cd2164 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 dd8d23272..ea07220b8 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 068cd7f2b..b40f634fc 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 65c03abf4..198713bdc 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 b1a822842..e9722106d 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 28fbc7d0f..eee13f30e 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 6871e7795..ee2e1b003 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 95106e7fc..4e8fe0380 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 000000000..8b8ce9b1b --- /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 6b07a5ce2..ce9baeea9 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 1012193ac..ff8914a5a 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 d62e16f01..70b62152b 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 c1b66ff4f..27e96407c 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 5079b7a35..7ebdaae34 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/71] 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 198713bdc..6dc03731d 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 ee2e1b003..0c90af3b3 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/71] 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 fe941f4d0..8ad116aaa 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 01e7f04f8..e58ae5e74 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 445067f26..8418f5888 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 4c82c8560..87212d4af 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 4c5096d12..473eb3ab7 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 1a5802f19..41b7d3a5e 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 b40f634fc..720d9cd58 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 6dc03731d..3413d2785 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 0c90af3b3..b9b0218fc 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 431e5d6ad..f523f4257 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 ce9baeea9..a1ca5b9e0 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 ff8914a5a..ddf0897c3 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 70b62152b..16ad7692d 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 27e96407c..dc66bd8c7 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 7ebdaae34..59f55a961 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 ad5bdf54f..6c9989e47 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/71] support retries on NFC failure --- .../com/yubico/authenticator/MainActivity.kt | 49 +++++++---- .../authenticator/device/DeviceManager.kt | 42 ++++++--- .../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, 203 insertions(+), 150 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 6e4c30728..2f5dfacf8 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 e58ae5e74..1791b4d95 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) + } ?: 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) } - } catch (e: Throwable) { - appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED) - throw e + + 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 8418f5888..061b9c43b 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 09b98cb1b..93d521b78 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 d7f4d3672..8ef6363f2 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 87212d4af..17a91bc69 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 473eb3ab7..abf5542bd 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 41b7d3a5e..0139460db 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 0c20ff77d..69512c957 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 8e6cd2164..9398c20ed 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 3413d2785..b4569855e 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 13756d35a..b7cd94d87 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 b9b0218fc..764b89293 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 000000000..96ce85e02 --- /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 ef76ca63f..49779613d 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 58bacda62..ea8f0c75b 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 0fb875e96..0e5bf7e2e 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 f841392f9..18758565d 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 169b935fc..a8a7aea44 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 05fc3cc1d..1991b756a 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 6c9989e47..0b8e46186 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/71] 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 1791b4d95..769988073 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 4d5f86aaf..bb0ede5f3 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 56659c377..f9381c842 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 895fa1bdc..c453c4893 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 9398c20ed..b2850c238 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/71] 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 de1a30ae8..e40c6ef02 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 769988073..6af9b4dff 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 bb0ede5f3..45130ea3e 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 f9381c842..56659c377 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 17a91bc69..cd40aabdf 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 c453c4893..b7b3714e0 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 b2850c238..9398c20ed 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 a58895ebf..de1fe79d7 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 63afad570..9107eb718 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/71] 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 2f5dfacf8..9a94302f4 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 6af9b4dff..06233ff14 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 b7b3714e0..e782cd4d1 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 f45f56da1..1a71812cd 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/71] 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 9a94302f4..1e5dfc4e1 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 06233ff14..0eab6c775 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 69512c957..0c20ff77d 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/71] 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 25b0f0a7a..8f6460189 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 e9722106d..057b98bc8 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 b7cd94d87..b5e1a6284 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 764b89293..52d408956 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 000000000..5006bf7a7 --- /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 4e8fe0380..2ba897be7 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 000000000..f6cf39ca2 --- /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 9107eb718..74f921231 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 ca7884b58..ec8d55db7 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/71] 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 de1fe79d7..d19450dd9 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 8f6460189..94949564e 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 057b98bc8..d6594304c 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 updateNfcView(Widget child) => - NfcEventCommand(event: NfcUpdateViewEvent(child: child)); +NfcEventCommand hideNfcView([Duration hideAfter = Duration.zero]) => + NfcEventCommand(event: NfcHideViewEvent(hideAfter: hideAfter)); -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 b5e1a6284..d821750b2 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 5006bf7a7..d0abe3e6d 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 f6cf39ca2..6620d8934 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 b2a60a02f..d65a02b2d 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 ec8d55db7..9f9f3191d 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 d717bc950..fe3b8c427 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 19fd6718d..484c385b6 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 65b8d1839..352c4c20d 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 a3036aaa8..15247254f 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/71] 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 2e9f0f0df..cb06fd9a2 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 d65a02b2d..939711938 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 9f9f3191d..c1c1bf610 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 fe3b8c427..129fe16e0 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 484c385b6..1a31bb419 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 352c4c20d..88eec6e6c 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 15247254f..b35f8cdb6 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/71] 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 9398c20ed..f6c91c9df 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 ea07220b8..2f6e856fe 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 d19450dd9..166a8350b 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 94949564e..e4d053db1 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 d6594304c..71862e7eb 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 eee13f30e..eb0d46bc0 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 52d408956..c3f2b1236 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 d0abe3e6d..ccd221db8 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 2ba897be7..b101e3c1e 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 6620d8934..fa9da011c 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 96ce85e02..6942cdb28 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 89b5d5321..2ea83beed 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 f523f4257..431e5d6ad 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 939711938..4f669fc6c 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 c1c1bf610..ac78f4f3b 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 129fe16e0..d5e18acb2 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 1a31bb419..1563b8c08 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 88eec6e6c..f6274f1a5 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 b35f8cdb6..f0158bcc9 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/71] 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 4f669fc6c..866aaef00 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 ac78f4f3b..593aa337b 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 d5e18acb2..79ef7a235 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 1563b8c08..87df5ac29 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 f6274f1a5..5eb0b2dca 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 f0158bcc9..b925de685 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/71] 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 e631b9577..1258482c8 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/71] 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 1258482c8..baddb8a52 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 71862e7eb..5149528c3 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 eb0d46bc0..d7dc49d16 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 d821750b2..c24f1cb8d 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 c3f2b1236..cab4bb99e 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 ccd221db8..2b00b07b4 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 fa9da011c..530f09d6e 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/71] 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 d1a97a165..a771db74d 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 2f6e856fe..1b423464a 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 baddb8a52..af6fab902 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 5149528c3..81bbb75f4 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 d7dc49d16..b4f1a051b 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 c24f1cb8d..a1179e14b 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 cab4bb99e..f7ad0c9b7 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/71] 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 af6fab902..2f3a8bcc2 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/71] 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 f6c91c9df..875ea7a0d 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 a771db74d..9f3f5442b 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 166a8350b..6f37befe9 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 1b423464a..4aa038387 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 8b8ce9b1b..fe24afa85 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 b4f1a051b..3216a0e47 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 a1179e14b..6f978ca0a 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 2f3a8bcc2..80958942a 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 b101e3c1e..fa784005a 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 2ea83beed..92ed4a76f 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 f7ad0c9b7..db608ca0d 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 81bbb75f4..000000000 --- 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 2b00b07b4..000000000 --- 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 530f09d6e..000000000 --- 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 6942cdb28..000000000 --- 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/71] 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 adb156ea1..000000000 --- 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 74f921231..e4cca2586 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 036765cda..5d8d39c23 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 0b8e46186..f5d5d4e1e 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/71] 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 875ea7a0d..3afcfc5f5 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 6f37befe9..9105bdb8d 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 4aa038387..13f15cbcd 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/71] 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 1e5dfc4e1..968dc6714 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 0eab6c775..8eba8e654 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 df6a15fe5..badddb118 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 3d6af5fae..670acf668 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 cea749675..d825bb0ea 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 5d6c01b97..d152d82a3 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 80958942a..f068017ef 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 606d6c85b..4ccc15bd3 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 627880288..162cd713a 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 126c90da3..a1046f6a1 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 e4cca2586..ebf52c960 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 229ed436d..df72e53ad 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/71] 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 9f3f5442b..b638df950 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 9105bdb8d..bf9032c37 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 13f15cbcd..d5bfa5b8e 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 6f978ca0a..ec2401705 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 f068017ef..f01bf262a 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/71] 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 968dc6714..c5daf918b 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 425f774d1..50660bd63 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 8eba8e654..664ac2468 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 45130ea3e..cdb51367b 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 56659c377..0c43122de 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 badddb118..9ba6336fe 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 cd40aabdf..5c7a6c301 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 e782cd4d1..16952fa6e 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 f01bf262a..53325fda8 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/71] 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 e40c6ef02..4467b6ad8 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 c5daf918b..57301c5f6 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 664ac2468..8968fd794 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 cdb51367b..d422706fa 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 0c43122de..b1d648c16 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 5c7a6c301..4bacb5246 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 16952fa6e..70640f981 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 53325fda8..028a4d7b4 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 ebf52c960..4d088453e 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/71] 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 70640f981..9b540cf85 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/71] 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 9ba6336fe..00c2c4a2b 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 1ce97978e39f5acd9545b6346f15507b55b3ebb5 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 10 Sep 2024 15:06:29 +0200 Subject: [PATCH 45/71] Bump version to 7.1.0 --- helper/version_info.txt | 8 ++++---- lib/version.dart | 4 ++-- pubspec.yaml | 2 +- resources/win/release-win.ps1 | 2 +- resources/win/yubioath-desktop.wxs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/helper/version_info.txt b/helper/version_info.txt index c4c014f7d..9968024d1 100755 --- a/helper/version_info.txt +++ b/helper/version_info.txt @@ -6,8 +6,8 @@ VSVersionInfo( ffi=FixedFileInfo( # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) # Set not needed items to zero 0. - filevers=(7, 0, 2, 0), - prodvers=(7, 0, 2, 0), + filevers=(7, 1, 0, 0), + prodvers=(7, 1, 0, 0), # Contains a bitmask that specifies the valid bits 'flags'r mask=0x3f, # Contains a bitmask that specifies the Boolean attributes of the file. @@ -31,11 +31,11 @@ VSVersionInfo( '040904b0', [StringStruct('CompanyName', 'Yubico'), StringStruct('FileDescription', 'Yubico Authenticator Helper'), - StringStruct('FileVersion', '7.0.2-dev.0'), + StringStruct('FileVersion', '7.1.0'), StringStruct('LegalCopyright', 'Copyright (c) Yubico'), StringStruct('OriginalFilename', 'authenticator-helper.exe'), StringStruct('ProductName', 'Yubico Authenticator'), - StringStruct('ProductVersion', '7.0.2-dev.0')]) + StringStruct('ProductVersion', '7.1.0')]) ]), VarFileInfo([VarStruct('Translation', [1033, 1200])]) ] diff --git a/lib/version.dart b/lib/version.dart index 936a73055..c641b61d6 100755 --- a/lib/version.dart +++ b/lib/version.dart @@ -1,5 +1,5 @@ // GENERATED CODE - DO NOT MODIFY BY HAND // This file is generated by running ./set-version.py -const String version = '7.0.2-dev.0'; -const int build = 70002; +const String version = '7.1.0'; +const int build = 70100; diff --git a/pubspec.yaml b/pubspec.yaml index aa71eb5bf..f55badb03 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # This field is updated by running ./set-version.py # DO NOT MANUALLY EDIT THIS! -version: 7.0.2-dev.0+70002 +version: 7.1.0+70100 environment: sdk: '>=3.4.3 <4.0.0' diff --git a/resources/win/release-win.ps1 b/resources/win/release-win.ps1 index f9bf9bc9a..f15e00fb1 100644 --- a/resources/win/release-win.ps1 +++ b/resources/win/release-win.ps1 @@ -1,4 +1,4 @@ -$version="7.0.2-dev.0" +$version="7.1.0" echo "Clean-up of old files" rm *.msi diff --git a/resources/win/yubioath-desktop.wxs b/resources/win/yubioath-desktop.wxs index fc7895cc1..f11183b1c 100644 --- a/resources/win/yubioath-desktop.wxs +++ b/resources/win/yubioath-desktop.wxs @@ -1,7 +1,7 @@ - + From 4e25fca6ed33668f5a14c0d1061d64c7dddf2205 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Tue, 10 Sep 2024 15:09:29 +0200 Subject: [PATCH 46/71] Update l10n from Crowdin --- .../app/src/main/res/values-pl/strings.xml | 8 +- lib/l10n/app_de.arb | 52 +- lib/l10n/app_fr.arb | 122 ++-- lib/l10n/app_ja.arb | 112 +-- lib/l10n/app_pl.arb | 660 +++++++++--------- lib/l10n/app_vi.arb | 32 +- 6 files changed, 493 insertions(+), 493 deletions(-) diff --git a/android/app/src/main/res/values-pl/strings.xml b/android/app/src/main/res/values-pl/strings.xml index 9bb0debc9..355069db1 100644 --- a/android/app/src/main/res/values-pl/strings.xml +++ b/android/app/src/main/res/values-pl/strings.xml @@ -1,7 +1,7 @@ - OTP zostało skopiowane do schowka. - Hasło statyczne zostało skopiowane do schowka. - Błąd czytania OTP z YubiKey. - Błąd kopiowania OTP do schowka. + Kod OTP został skopiowany do schowka. + Hasło zostało skopiowane do schowka. + Nie udało się odczytać kodu OTP z klucza YubiKey. + Wystąpił błąd podczas kopiowania kodu OTP do schowka. \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 0441f018b..d58eb4792 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -38,7 +38,7 @@ "s_calculate": "Berechnen", "s_import": "Importieren", "s_overwrite": "Überschreiben", - "s_done": null, + "s_done": "Erledigt", "s_label": "Beschriftung", "s_name": "Name", "s_usb": "USB", @@ -47,11 +47,11 @@ "s_details": "Details", "s_show_window": "Fenster anzeigen", "s_hide_window": "Fenster verstecken", - "s_show_navigation": null, + "s_show_navigation": "Navigation anzeigen", "s_expand_navigation": "Navigation erweitern", "s_collapse_navigation": "Navigation einklappen", - "s_show_menu": null, - "s_hide_menu": null, + "s_show_menu": "Menü anzeigen", + "s_hide_menu": "Menü ausblenden", "q_rename_target": "{label} umbenennen?", "@q_rename_target": { "placeholders": { @@ -230,15 +230,15 @@ "l_no_yk_present": "Kein YubiKey vorhanden", "s_unknown_type": "Unbekannter Typ", "s_unknown_device": "Unbekanntes Gerät", - "s_restricted_nfc": null, - "l_deactivate_restricted_nfc": null, - "p_deactivate_restricted_nfc_desc": null, - "p_deactivate_restricted_nfc_footer": null, + "s_restricted_nfc": "NFC-Aktivierung", + "l_deactivate_restricted_nfc": "So aktivieren Sie NFC", + "p_deactivate_restricted_nfc_desc": "Verbinden Sie Ihren YubiKey für mindestens 3 Sekunden mit einer USB-Stromquelle, z. B. einem Computer.\n\nSobald er mit Strom versorgt wird, wird NFC aktiviert und ist einsatzbereit.", + "p_deactivate_restricted_nfc_footer": "Ihr YubiKey ist mit Restricted NFC ausgestattet, einer Funktion zum Schutz vor drahtloser Manipulation während des Transports. Dies bedeutet, dass die NFC-Funktionen vorübergehend deaktiviert sind, bis Sie sie aktivieren.", "s_unsupported_yk": "Nicht unterstützter YubiKey", "s_yk_not_recognized": "Geräte nicht erkannt", "p_operation_failed_try_again": "Die Aktion ist fehlgeschlagen, bitte versuchen Sie es erneut.", - "l_configuration_unsupported": null, - "p_scp_unsupported": null, + "l_configuration_unsupported": "Konfiguration nicht unterstützt", + "p_scp_unsupported": "Um über NFC zu kommunizieren, benötigt der YubiKey eine Technologie, die von diesem Telefon nicht unterstützt wird. Bitte schließen Sie den YubiKey an den USB-Anschluss des Telefons an.", "@_general_errors": {}, "l_error_occurred": "Es ist ein Fehler aufgetreten", @@ -325,7 +325,7 @@ "s_ep_attestation_enabled": "Unternehmens-Beglaubigung aktiviert", "s_enable_ep_attestation": "Unternehmens-Beglaubigung aktivieren", "p_enable_ep_attestation_desc": "Dies aktiviert die Unternehmens-Beglaubigung, die es berechtigten Domains ermöglicht Ihren YubiKey eindeutig zu identifizieren.", - "p_enable_ep_attestation_disable_with_factory_reset": null, + "p_enable_ep_attestation_disable_with_factory_reset": "Einmal aktiviert, kann Enterprise Attestation nur durch einen FIDO-Werksreset deaktiviert werden.", "s_pin_required": "PIN erforderlich", "p_pin_required_desc": "Die ausgeführte Aktion erfordert die Eingabe des PIV PINs.", "l_piv_pin_blocked": "Gesperrt, verwenden Sie den PUK zum Zurücksetzen", @@ -430,10 +430,10 @@ "message": {} } }, - "l_add_account_password_required": null, - "l_add_account_unlock_required": null, - "l_add_account_already_exists": null, - "l_add_account_func_missing": null, + "l_add_account_password_required": "Passwort erforderlich", + "l_add_account_unlock_required": "Freischaltung erforderlich", + "l_add_account_already_exists": "Konto existiert bereits", + "l_add_account_func_missing": "Funktionalitäten fehlen oder sind deaktiviert", "l_account_name_required": "Ihr Konto muss einen Namen haben", "l_name_already_exists": "Für diesen Aussteller existiert dieser Name bereits", "l_account_already_exists": "Dieses Konto existiert bereits auf dem YubiKey", @@ -447,7 +447,7 @@ "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": "Umbenennung des Kontos fehlgeschlagen: {message}", "@l_rename_account_failed": { "placeholders": { "message": {} @@ -517,7 +517,7 @@ } }, "@_fingerprints": {}, - "s_biometrics": null, + "s_biometrics": "Biometrische Daten", "l_fingerprint": "Fingerabdruck: {label}", "@l_fingerprint": { "placeholders": { @@ -600,7 +600,7 @@ "l_delete_certificate_or_key_desc": "Entfernen Sie das Zertifikat oder den Schlüssel von Ihrem YubiKey", "l_move_key": "Schlüssel verschieben", "l_move_key_desc": "Verschieben Sie einen Schlüssel von einem PIV-Slot in einen anderen", - "l_change_defaults": null, + "l_change_defaults": "Standard-Zugangscodes ändern", "s_issuer": "Aussteller", "s_serial": "Serial", "s_certificate_fingerprint": "Fingerprint", @@ -676,10 +676,10 @@ "p_subject_desc": "Ein Distinguished Name (DN) formtiert nach RFC 4514 Spezifikationen.", "l_rfc4514_invalid": "Ungültiges RFC 4514-Format", "rfc4514_examples": "Beispiele:\nCN=Beispielname\nCN=jschmitt,DC=beispiel,DC=net", - "s_allow_fingerprint": null, + "s_allow_fingerprint": "Fingerabdruck zulassen", "p_cert_options_desc": "Verwendeter Schlüssel-Algorithmus, Ausgabeformat und Ablaufdatum (nur Zertifikat).", - "p_cert_options_bio_desc": null, - "p_key_options_bio_desc": null, + "p_cert_options_bio_desc": "Zu verwendender Schlüsselalgorithmus, Ausgabeformat, Ablaufdatum (nur bei Zertifikaten) und ob biometrische Daten anstelle der PIN verwendet werden können.", + "p_key_options_bio_desc": "Erlauben Sie die Verwendung biometrischer Daten anstelle der PIN.", "s_overwrite_slot": "Slot überschreiben", "p_overwrite_slot_desc": "Damit wird vorhandener Inhalt im Slot {slot} dauerhaft überschrieben.", "@p_overwrite_slot_desc": { @@ -822,7 +822,7 @@ "s_reset": "Zurücksetzen", "s_factory_reset": "Werkseinstellungen", "l_factory_reset_desc": "YubiKey-Standardeinstellungen wiederherstellen", - "l_factory_reset_required": null, + "l_factory_reset_required": "Werksreset erforderlich", "l_oath_application_reset": "OATH Anwendung zurücksetzen", "l_fido_app_reset": "FIDO Anwendung zurückgesetzt", "l_reset_failed": "Fehler beim Zurücksetzen: {message}", @@ -923,10 +923,10 @@ "s_nfc_dialog_fido_failure": "FIDO-Operation fehlgeschlagen", "@_nfc": {}, - "s_nfc_ready_to_scan": null, - "s_nfc_hold_still": null, - "s_nfc_tap_your_yubikey": null, - "l_nfc_failed_to_scan": null, + "s_nfc_ready_to_scan": "Bereit zum Scannen", + "s_nfc_hold_still": "Stillhalten\u2026", + "s_nfc_tap_your_yubikey": "Tippen Sie auf Ihren YubiKey", + "l_nfc_failed_to_scan": "Scanvorgang fehlgeschlagen, erneut versuchen", "@_ndef": {}, "p_ndef_set_otp": "OTP-Code wurde erfolgreich von Ihrem YubiKey in die Zwischenablage kopiert.", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 52b271dec..a30e0a98e 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -30,15 +30,15 @@ "s_delete": "Supprimer", "s_move": "Déplacer", "s_quit": "Quitter", - "s_enable": null, - "s_enabled": null, - "s_disabled": null, + "s_enable": "Activer", + "s_enabled": "Activé", + "s_disabled": "Handicapés", "s_status": "État", "s_unlock": "Déverrouiller", "s_calculate": "Calculer", "s_import": "Importer", "s_overwrite": "Écraser", - "s_done": null, + "s_done": "Terminé", "s_label": "Étiquette", "s_name": "Nom", "s_usb": "USB", @@ -47,11 +47,11 @@ "s_details": "Détails", "s_show_window": "Montrer fenêtre", "s_hide_window": "Masquer fenêtre", - "s_show_navigation": null, + "s_show_navigation": "Afficher la navigation", "s_expand_navigation": "Développer la navigation", "s_collapse_navigation": "Réduire la navigation", - "s_show_menu": null, - "s_hide_menu": null, + "s_show_menu": "Afficher le menu", + "s_hide_menu": "Cacher le menu", "q_rename_target": "Renommer {label}\u00a0?", "@q_rename_target": { "placeholders": { @@ -128,10 +128,10 @@ "s_dark_mode": "Thème sombre", "@_layout": {}, - "s_list_layout": null, - "s_grid_layout": null, - "s_mixed_layout": null, - "s_select_layout": null, + "s_list_layout": "Présentation de la liste", + "s_grid_layout": "Disposition de la grille", + "s_mixed_layout": "Disposition mixte", + "s_select_layout": "Sélectionner la mise en page", "@_yubikey_selection": {}, "s_select_to_scan": "Sélectionner pour scanner", @@ -161,8 +161,8 @@ } }, "l_firmware_version": "Version du firmware : {version}", - "l_fips_capable": null, - "l_fips_approved": null, + "l_fips_capable": "Compatible FIPS", + "l_fips_approved": "Approuvé par le FIPS", "@_yubikey_interactions": {}, "l_insert_yk": "Insérez votre YubiKey", @@ -230,15 +230,15 @@ "l_no_yk_present": "Aucune YubiKey présente", "s_unknown_type": "Type inconnu", "s_unknown_device": "Appareil non reconnu", - "s_restricted_nfc": null, - "l_deactivate_restricted_nfc": null, - "p_deactivate_restricted_nfc_desc": null, - "p_deactivate_restricted_nfc_footer": null, + "s_restricted_nfc": "Activation NFC", + "l_deactivate_restricted_nfc": "Comment activer la NFC", + "p_deactivate_restricted_nfc_desc": "Connectez votre YubiKey à une source d'alimentation USB, telle qu'un ordinateur, pendant au moins 3 secondes.\n\nUne fois alimentée, la NFC sera activée et prête à l'emploi.", + "p_deactivate_restricted_nfc_footer": "Votre YubiKey est équipé d'une fonction NFC restreinte, conçue pour éviter les manipulations sans fil pendant le transport. Cela signifie que les opérations NFC sont temporairement désactivées jusqu'à ce que vous les activiez.", "s_unsupported_yk": "YubiKey non prise en charge", "s_yk_not_recognized": "Appareil non reconnu", "p_operation_failed_try_again": "L'opération a échoué, veuillez réessayer.", - "l_configuration_unsupported": null, - "p_scp_unsupported": null, + "l_configuration_unsupported": "La configuration n'est pas prise en charge", + "p_scp_unsupported": "Pour communiquer par NFC, le YubiKey nécessite une technologie qui n'est pas prise en charge par ce téléphone. Veuillez brancher la YubiKey sur le port USB du téléphone.", "@_general_errors": {}, "l_error_occurred": "Une erreur s'est produite", @@ -298,21 +298,21 @@ "l_enter_fido2_pin": "Saisissez le PIN FIDO2 de votre YubiKey", "l_pin_blocked_reset": "PIN bloqué, réinitialisez FIDO aux paramètres d'usine", "l_pin_blocked": "Le code PIN est bloqué", - "l_set_pin_first": "Un PIN est d'abord requis", - "l_unlock_pin_first": "Débloquer d'abord avec PIN", + "l_set_pin_first": "Un code PIN est nécessaire", + "l_unlock_pin_first": "Déverrouiller avec un code PIN", "l_pin_soft_locked": "PIN bloqué jusqu'à ce que la YubiKey soit retirée et réinsérée", "l_pin_change_required_desc": "Vous devez créer un nouveau PIN avant d'utiliser cette appli", "p_enter_current_pin_or_reset": "Saisissez votre PIN actuel. Vous ne connaissez pas votre PIN\u00a0? Débloquez-le avec le PUK ou réinitialisez la YubiKey.", "p_enter_current_pin_or_reset_no_puk": "Saisissez votre PIN actuel. Vous ne connaissez pas votre PIN\u00a0? Réinitialisez la YubiKey.", "p_enter_current_puk_or_reset": "Saisissez votre PUK actuel. Vous ne connaissez pas votre PUK\u00a0? Réinitialisez la YubiKey.", - "p_enter_new_fido2_pin": null, + "p_enter_new_fido2_pin": "Saisissez votre nouveau code PIN. Le code PIN doit être composé de {min_length}-{max_length} caractères et peut contenir des lettres, des chiffres et des caractères spéciaux.", "@p_enter_new_fido2_pin": { "placeholders": { "min_length": {}, "max_length": {} } }, - "p_enter_new_fido2_pin_complexity_active": null, + "p_enter_new_fido2_pin_complexity_active": "Saisissez votre nouveau code PIN. Le code PIN doit être composé de {min_length}-{max_length} caractères, contenir au moins {unique_characters} caractères uniques et ne pas être un code PIN couramment utilisé, comme \"{common_pin}\". Il peut contenir des lettres, des chiffres et des caractères spéciaux.", "@p_enter_new_fido2_pin_complexity_active": { "placeholders": { "min_length": {}, @@ -321,23 +321,23 @@ "common_pin": {} } }, - "s_ep_attestation": null, - "s_ep_attestation_enabled": null, - "s_enable_ep_attestation": null, - "p_enable_ep_attestation_desc": null, - "p_enable_ep_attestation_disable_with_factory_reset": null, + "s_ep_attestation": "Attestation d'entreprise", + "s_ep_attestation_enabled": "Attestation d'entreprise activée", + "s_enable_ep_attestation": "Permettre l'attestation de l'entreprise", + "p_enable_ep_attestation_desc": "Cela permettra l'Attestation d'Entreprise, permettant aux domaines autorisés d'identifier votre YubiKey de manière unique.", + "p_enable_ep_attestation_disable_with_factory_reset": "Une fois activée, l'attestation d'entreprise ne peut être désactivée qu'en procédant à une réinitialisation d'usine de FIDO.", "s_pin_required": "PIN requis", "p_pin_required_desc": "L'action que vous allez effectuer nécessite la saisie du PIN PIV.", "l_piv_pin_blocked": "Bloqué, utilisez PUK pour réinitialiser", "l_piv_pin_puk_blocked": "Bloqué, réinitialisation aux paramètres d'usine nécessaire", - "p_enter_new_piv_pin_puk": null, + "p_enter_new_piv_pin_puk": "Entrez un nouveau {name} à définir. Il doit comporter au moins {length} caractères.", "@p_enter_new_piv_pin_puk": { "placeholders": { "name": {}, "length": {} } }, - "p_enter_new_piv_pin_puk_complexity_active": null, + "p_enter_new_piv_pin_puk_complexity_active": "Saisissez un nouveau {name} à définir. Il doit être composé d'au moins {length} caractères, contenir au moins 2 caractères uniques et ne pas être un {name}couramment utilisé, comme \"{common}\".", "@p_enter_new_piv_pin_puk_complexity_active": { "placeholders": { "name": {}, @@ -355,7 +355,7 @@ "l_warning_default_puk": "Attention : PUK par défaut utilisé", "l_default_pin_used": "Code PIN par défaut utilisé", "l_default_puk_used": "PUK par défaut utilisé", - "l_pin_complexity": null, + "l_pin_complexity": "Complexité du code PIN appliquée", "@_passwords": {}, "s_password": "Mot de passe", @@ -365,7 +365,7 @@ "s_show_password": "Montrer mot de passe", "s_hide_password": "Masquer mot de passe", "l_optional_password_protection": "Protection par mot de passe facultative", - "l_password_protection": null, + "l_password_protection": "Protection des comptes par mot de passe", "s_new_password": "Nouveau mot de passe", "s_current_password": "Mot de passe actuel", "s_confirm_password": "Confirmer mot de passe", @@ -378,8 +378,8 @@ "s_password_forgotten": "Mot de passe oublié", "l_keystore_unavailable": "OS Keystore indisponible", "l_remember_pw_failed": "Mémorisation mot de passe impossible", - "l_unlock_first": "Débloquez d'abord avec mot de passe", - "l_set_password_first": null, + "l_unlock_first": "Déverrouillage par mot de passe", + "l_set_password_first": "Définir un mot de passe", "l_enter_oath_pw": "Saisissez le mot de passe OATH de votre YubiKey", "p_enter_current_password_or_reset": "Saisissez votre mot de passe actuel. Vous ne connaissez votre mot de passe\u00a0? Réinitialisez la YubiKey.", "p_enter_new_password": "Saisissez votre nouveau mot de passe. Un mot de passe peut inclure des lettres, chiffres et caractères spéciaux.", @@ -430,10 +430,10 @@ "message": {} } }, - "l_add_account_password_required": null, - "l_add_account_unlock_required": null, - "l_add_account_already_exists": null, - "l_add_account_func_missing": null, + "l_add_account_password_required": "Mot de passe requis", + "l_add_account_unlock_required": "Déverrouillage requis", + "l_add_account_already_exists": "Le compte existe déjà", + "l_add_account_func_missing": "Fonctionnalité manquante ou désactivée", "l_account_name_required": "Votre compte doit avoir un nom", "l_name_already_exists": "Ce nom existe déjà pour l'émetteur", "l_account_already_exists": "Ce compte existe déjà sur la YubiKey", @@ -442,12 +442,12 @@ "s_pin_account": "Épingler compte", "s_unpin_account": "Détacher compte", "s_no_pinned_accounts": "Aucun compte épinglé", - "s_pinned": null, + "s_pinned": "Épinglé", "l_pin_account_desc": "Conserver vos comptes importants ensemble", "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": "Échec du renommage du compte : {message}", "@l_rename_account_failed": { "placeholders": { "message": {} @@ -458,7 +458,7 @@ "l_delete_account_desc": "Supprimer le compte de votre YubiKey", "s_account_deleted": "Compte supprimé", "p_warning_delete_account": "Attention\u00a0! Cela supprimera le compte de votre YubiKey.", - "p_warning_disable_credential": "Vous ne pourrez plus générer d'OTP pour ce compte. Vous devez désactiver ces infos d'identification du site Web pour éviter que votre compte soit bloqué.", + "p_warning_disable_credential": "Vous ne pourrez plus générer d'OTP pour ce compte. Veillez à désactiver cet identifiant à partir du site web afin d'éviter que votre compte ne soit verrouillé.", "s_account_name": "Nom du compte", "s_search_accounts": "Rechercher comptes", "l_accounts_used": "{used} comptes sur {capacity} utilisés", @@ -488,7 +488,7 @@ "@_fido_credentials": {}, "s_rp_id": "RP ID", - "s_user_id": "Identifiant de l'utilisateur", + "s_user_id": "ID d'utilisateur", "s_credential_id": "Credential ID", "s_display_name": "Nom affiché", "s_user_name": "Nom d'utilisateur", @@ -498,17 +498,17 @@ "label": {} } }, - "s_passkeys": "Clés d'accès", + "s_passkeys": "Passkeys", "s_no_passkeys": "Aucun mot de passe", "l_ready_to_use": "Prête à l'emploi", "l_register_sk_on_websites": "Enregistrer comme clé de sécurité sur le Web", - "l_no_discoverable_accounts": "Pas de passkey enregistrée", - "p_non_passkeys_note": "Les identifiants de non-mot de passe peuvent exister, mais ne peuvent pas être listés.", + "l_no_discoverable_accounts": "Pas de passkey enregistré", + "p_non_passkeys_note": "Des identifiants non-passkey peuvent exister, mais ne peuvent pas être listés.", "s_delete_passkey": "Supprimer passkey", "l_delete_passkey_desc": "Supprimer passkey de la YubiKey", - "s_passkey_deleted": "Passkey supprimée", + "s_passkey_deleted": "Passkey supprimé", "p_warning_delete_passkey": "Cela supprimera la passkey de votre YubiKey.", - "s_search_passkeys": "Rechercher des mots de passe", + "s_search_passkeys": "Rechercher des passkeys", "p_passkeys_used": "{used} des clés {max} utilisées.", "@p_passkeys_used": { "placeholders": { @@ -517,7 +517,7 @@ } }, "@_fingerprints": {}, - "s_biometrics": null, + "s_biometrics": "Biométrie", "l_fingerprint": "Empreinte digitale\u00a0: {label}", "@l_fingerprint": { "placeholders": { @@ -600,7 +600,7 @@ "l_delete_certificate_or_key_desc": "Supprimer le certificat ou la clé de votre YubiKey", "l_move_key": "Déplacer la clé", "l_move_key_desc": "Déplacer une clé d'un emplacement PIV vers un autre", - "l_change_defaults": null, + "l_change_defaults": "Modifier les codes d'accès par défaut", "s_issuer": "Émetteur", "s_serial": "Série", "s_certificate_fingerprint": "Empreinte digitale", @@ -676,10 +676,10 @@ "p_subject_desc": "DN (nom distinctif) formaté conformément à la spécification RFC 4514.", "l_rfc4514_invalid": "Format RFC 4514 non valide", "rfc4514_examples": "Exemples\u00a0:\nCN=exemple de nom\nCN=jsmith,DC=exemple,DC=net", - "s_allow_fingerprint": null, + "s_allow_fingerprint": "Autoriser les empreintes digitales", "p_cert_options_desc": "Algorithme clé à utiliser, format de sortie et date d'expiration (certificat uniquement).", - "p_cert_options_bio_desc": null, - "p_key_options_bio_desc": null, + "p_cert_options_bio_desc": "Algorithme de clé à utiliser, format de sortie, date d'expiration (certificat uniquement) et possibilité d'utiliser des données biométriques à la place du code PIN.", + "p_key_options_bio_desc": "Permettre l'utilisation de données biométriques au lieu d'un code PIN.", "s_overwrite_slot": "Écraser slot", "p_overwrite_slot_desc": "Cela écrasera définitivement le contenu du slot {slot}.", "@p_overwrite_slot_desc": { @@ -822,7 +822,7 @@ "s_reset": "Réinitialiser", "s_factory_reset": "Réinitialisation usine", "l_factory_reset_desc": "Restaurer les paramètres par défaut de la YubiKey", - "l_factory_reset_required": null, + "l_factory_reset_required": "Réinitialisation d'usine nécessaire", "l_oath_application_reset": "Réinitialisation OATH", "l_fido_app_reset": "Réinitialisation FIDO", "l_reset_failed": "Erreur de réinitialisation\u00a0: {message}", @@ -835,13 +835,13 @@ "p_factory_reset_an_app": "Réinitialisation d'usine d'une appli sur votre YubiKey.", "p_factory_reset_desc": "Les données sont stockées dans plusieurs applis de la YubiKey. Certaines peuvent être réinitialisées indépendamment.\n\nChoisissez une appli ci-dessus à réinitialiser.", "p_warning_factory_reset": "Attention\u00a0! Cela supprimera définitivement tous les comptes OATH TOTP/HOTP de votre YubiKey.", - "p_warning_disable_credentials": "Vos identifiants OATH, ainsi que tout mot de passe défini, seront supprimés de cette YubiKey. Assurez-vous de les désactiver d'abord de leurs sites Web respectifs pour que vos comptes ne soient pas bloqués.", + "p_warning_disable_credentials": "Vos informations d'identification OATH, ainsi que tout mot de passe défini, seront supprimés de cette clé YubiKey. Veillez à les désactiver à partir de leurs sites web respectifs afin d'éviter que vos comptes ne soient verrouillés.", "p_warning_deletes_accounts": "Attention\u00a0! Cela supprimera définitivement tous les comptes U2F et FIDO2, notamment les passkeys, de votre YubiKey.", - "p_warning_disable_accounts": "Vos identifiants, ainsi que tout code PIN défini, seront supprimés de cette YubiKey. Assurez-vous de les désactiver d'abord de leurs sites Web respectifs pour que vos comptes ne soient pas bloqués.", + "p_warning_disable_accounts": "Vos informations d'identification, ainsi que tout code PIN défini, seront supprimés de cette YubiKey. Veillez à les désactiver à partir de leurs sites web respectifs pour éviter que vos comptes ne soient verrouillés.", "p_warning_piv_reset": "Attention\u00a0! Toutes les données PIV seront définitivement supprimées de votre YubiKey.", "p_warning_piv_reset_desc": "Cela inclut les clés privées et les certificats. Vos PIN, PUK et clé de gestion seront réinitialisés à leurs valeurs d'usine.", "p_warning_global_reset": "Attention\u00a0! Cela supprimera définitivement toutes les données enregistrées, notamment les identifiants, de votre YubiKey.", - "p_warning_global_reset_desc": "Réinitialisez les applications de votre YubiKey. Le PIN sera réinitialisé à sa valeur d'usine et les empreintes enregistrées seront supprimées. Les clés, certificats et autres identifiants seront définitivement supprimés.", + "p_warning_global_reset_desc": "Réinitialiser les applications de votre YubiKey. Le code PIN sera réinitialisé à sa valeur d'usine par défaut et les empreintes digitales enregistrées seront supprimées. Les clés, certificats ou autres informations d'identification seront tous définitivement supprimés.", "@_copy_to_clipboard": {}, "l_copy_to_clipboard": "Copier dans presse-papiers", @@ -923,10 +923,10 @@ "s_nfc_dialog_fido_failure": "Échec de l'opération FIDO", "@_nfc": {}, - "s_nfc_ready_to_scan": null, - "s_nfc_hold_still": null, - "s_nfc_tap_your_yubikey": null, - "l_nfc_failed_to_scan": null, + "s_nfc_ready_to_scan": "Prêt à numériser", + "s_nfc_hold_still": "Ne bougez pas\u2026", + "s_nfc_tap_your_yubikey": "Tapez sur votre YubiKey", + "l_nfc_failed_to_scan": "Échec de l'analyse, réessayez", "@_ndef": {}, "p_ndef_set_otp": "Code OTP copié de la YubiKey dans le presse-papiers.", @@ -936,7 +936,7 @@ "@_key_customization": {}, "s_set_label": "Définir l'étiquette", - "s_set_color": null, + "s_set_color": "Définir la couleur", "s_change_label": "Modifier l'étiquette", "s_color": "Couleur", "p_set_will_add_custom_name": "Cela donnera un nom personnalisé à votre YubiKey.", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index fb55e0d06..5ef3ebce9 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -30,15 +30,15 @@ "s_delete": "削除", "s_move": "移動", "s_quit": "終了", - "s_enable": null, - "s_enabled": null, - "s_disabled": null, + "s_enable": "有効にする", + "s_enabled": "有効", + "s_disabled": "無効", "s_status": "ステータス", "s_unlock": "ロック解除", "s_calculate": "計算", "s_import": "インポート", "s_overwrite": "上書き", - "s_done": null, + "s_done": "完了", "s_label": "ラベル", "s_name": "名前", "s_usb": "USB", @@ -47,11 +47,11 @@ "s_details": "詳細", "s_show_window": "ウィンドウを表示", "s_hide_window": "ウィンドウを非表示", - "s_show_navigation": null, + "s_show_navigation": "ナビゲーションを表示する", "s_expand_navigation": "ナビゲーションを展開", "s_collapse_navigation": "ナビゲーションを閉じる", - "s_show_menu": null, - "s_hide_menu": null, + "s_show_menu": "メニューを表示する", + "s_hide_menu": "メニューを隠す", "q_rename_target": "{label}の名前を変更しますか?", "@q_rename_target": { "placeholders": { @@ -128,10 +128,10 @@ "s_dark_mode": "ダークモード", "@_layout": {}, - "s_list_layout": null, - "s_grid_layout": null, - "s_mixed_layout": null, - "s_select_layout": null, + "s_list_layout": "リストレイアウト", + "s_grid_layout": "グリッドレイアウト", + "s_mixed_layout": "ミックスレイアウト", + "s_select_layout": "レイアウトを選択", "@_yubikey_selection": {}, "s_select_to_scan": "選択してスキャン", @@ -161,8 +161,8 @@ } }, "l_firmware_version": "ファームウェアバージョン: {version}", - "l_fips_capable": null, - "l_fips_approved": null, + "l_fips_capable": "FIPS対応", + "l_fips_approved": "FIPS承認済み", "@_yubikey_interactions": {}, "l_insert_yk": "YubiKeyを挿入してください", @@ -230,15 +230,15 @@ "l_no_yk_present": "YubiKeyがありません", "s_unknown_type": "不明なタイプ", "s_unknown_device": "認識されないデバイス", - "s_restricted_nfc": null, - "l_deactivate_restricted_nfc": null, - "p_deactivate_restricted_nfc_desc": null, - "p_deactivate_restricted_nfc_footer": null, + "s_restricted_nfc": "NFCアクティベーション", + "l_deactivate_restricted_nfc": "NFCの起動方法", + "p_deactivate_restricted_nfc_desc": "YubiKeyをコンピュータなどのUSB電源に3秒以上接続してください。\n\n電源が入ると、NFCが有効になり、使用できるようになります。", + "p_deactivate_restricted_nfc_footer": "YubiKeyには制限付きNFCが搭載されています。これは、配送中の無線操作から保護するために設計された機能です。つまり、NFC 操作は起動するまで一時的に無効になります。", "s_unsupported_yk": "サポートされていないYubiKey", "s_yk_not_recognized": "デバイスが認識されません", "p_operation_failed_try_again": "操作に失敗しました。もう一度やり直してください。", - "l_configuration_unsupported": null, - "p_scp_unsupported": null, + "l_configuration_unsupported": "コンフィギュレーションがサポートされていない", + "p_scp_unsupported": "YubiKeyがNFCで通信するには、この携帯電話ではサポートされていない技術が必要です。YubiKeyを携帯電話のUSBポートに接続してください。", "@_general_errors": {}, "l_error_occurred": "エラーが発生しました", @@ -298,21 +298,21 @@ "l_enter_fido2_pin": "YubiKeyのFIDO2 PINを入力してください", "l_pin_blocked_reset": "PINがブロックされています。FIDOアプリケーションを工場出荷時の状態にリセットしてください", "l_pin_blocked": "PINがブロックされています", - "l_set_pin_first": "最初にPINの入力が必要です", - "l_unlock_pin_first": "最初にPINでロックを解除してください", + "l_set_pin_first": "暗証番号が必要", + "l_unlock_pin_first": "PINロック解除", "l_pin_soft_locked": "YubiKeyを取り外して再挿入するまで、PINがブロックされています", "l_pin_change_required_desc": "このアプリケーションを使用する前に新しいPINを設定する必要があります", "p_enter_current_pin_or_reset": "現在のPINを入力してください。PINがわからない場合は、PUKでブロックを解除するか、YubiKeyをリセットする必要があります。", "p_enter_current_pin_or_reset_no_puk": "現在のPINを入力してください。PINがわからない場合は、YubiKeyをリセットする必要があります。", "p_enter_current_puk_or_reset": "現在のPUKを入力してください。PUKがわからない場合は、YubiKeyをリセットする必要があります。", - "p_enter_new_fido2_pin": null, + "p_enter_new_fido2_pin": "新しいPINを入力してください。PIN は {min_length}-{max_length} 文字で、英字、数字、特殊文字を含むことができます。", "@p_enter_new_fido2_pin": { "placeholders": { "min_length": {}, "max_length": {} } }, - "p_enter_new_fido2_pin_complexity_active": null, + "p_enter_new_fido2_pin_complexity_active": "新しいPINを入力してください。PINは {min_length}-{max_length} 文字で、少なくとも {unique_characters} のユニークな文字を含み、\"{common_pin}\"のような一般的に使用されているPINであってはなりません。文字、数字、特殊文字を含むことができます。", "@p_enter_new_fido2_pin_complexity_active": { "placeholders": { "min_length": {}, @@ -321,23 +321,23 @@ "common_pin": {} } }, - "s_ep_attestation": null, - "s_ep_attestation_enabled": null, - "s_enable_ep_attestation": null, - "p_enable_ep_attestation_desc": null, - "p_enable_ep_attestation_disable_with_factory_reset": null, + "s_ep_attestation": "企業認証", + "s_ep_attestation_enabled": "エンタープライズ認証に対応", + "s_enable_ep_attestation": "エンタープライズ認証の有効化", + "p_enable_ep_attestation_desc": "これによりEnterprise Attestationが有効になり、認証されたドメインがYubiKeyを一意に識別できるようになります。", + "p_enable_ep_attestation_disable_with_factory_reset": "一旦有効になると、Enterprise Attestation は、FIDO の工場出荷時リセットを実行することによってのみ無効にすることができます。", "s_pin_required": "PINが必要", "p_pin_required_desc": "実行しようとしているアクションでは、PIV PINを入力する必要があります。", "l_piv_pin_blocked": "ブロックされています。リセットするにはPUKを使用してください", "l_piv_pin_puk_blocked": "ブロックされています。工場出荷時の状態にリセットする必要があります", - "p_enter_new_piv_pin_puk": null, + "p_enter_new_piv_pin_puk": "設定する新しい {name} を入力します。 {length} 文字以上でなければなりません。", "@p_enter_new_piv_pin_puk": { "placeholders": { "name": {}, "length": {} } }, - "p_enter_new_piv_pin_puk_complexity_active": null, + "p_enter_new_piv_pin_puk_complexity_active": "設定する新しい {name} を入力する。少なくとも {length} 文字の長さで、少なくとも 2 つのユニークな文字を含み、\"{common}\"のような一般的に使用される {name}ではない必要があります。", "@p_enter_new_piv_pin_puk_complexity_active": { "placeholders": { "name": {}, @@ -355,7 +355,7 @@ "l_warning_default_puk": "警告: デフォルトのPUKが使用されています", "l_default_pin_used": "デフォルトのPINが使用されています", "l_default_puk_used": "既定のPUKを使用", - "l_pin_complexity": null, + "l_pin_complexity": "暗証番号の複雑さ", "@_passwords": {}, "s_password": "パスワード", @@ -365,7 +365,7 @@ "s_show_password": "パスワードを表示", "s_hide_password": "パスワードを非表示", "l_optional_password_protection": "オプションのパスワード保護", - "l_password_protection": null, + "l_password_protection": "アカウントのパスワード保護", "s_new_password": "新しいパスワード", "s_current_password": "現在のパスワード", "s_confirm_password": "パスワードを確認", @@ -378,8 +378,8 @@ "s_password_forgotten": "パスワードが消去されました", "l_keystore_unavailable": "OSのキーストアを利用できません", "l_remember_pw_failed": "パスワードを記憶できませんでした", - "l_unlock_first": "最初にパスワードでロックを解除", - "l_set_password_first": null, + "l_unlock_first": "パスワードによるロック解除", + "l_set_password_first": "パスワードの設定", "l_enter_oath_pw": "YubiKeyのOATHパスワードを入力", "p_enter_current_password_or_reset": "現在のパスワードを入力してください。パスワードがわからない場合は、YubiKeyをリセットする必要があります。", "p_enter_new_password": "新しいパスワードを入力してください。パスワードには文字、数字、特殊文字を含めることができます。", @@ -430,10 +430,10 @@ "message": {} } }, - "l_add_account_password_required": null, - "l_add_account_unlock_required": null, - "l_add_account_already_exists": null, - "l_add_account_func_missing": null, + "l_add_account_password_required": "パスワードが必要", + "l_add_account_unlock_required": "ロック解除が必要", + "l_add_account_already_exists": "アカウントはすでに存在する", + "l_add_account_func_missing": "機能の欠落または無効化", "l_account_name_required": "アカウントには名前が必要です", "l_name_already_exists": "この名前は発行者にすでに存在します", "l_account_already_exists": "このアカウントはYubiKeyにすでに存在します", @@ -442,12 +442,12 @@ "s_pin_account": "アカウントをピン留めする", "s_unpin_account": "アカウントのピン留めを解除", "s_no_pinned_accounts": "ピン留めされたアカウントはありません", - "s_pinned": null, + "s_pinned": "ピン留め", "l_pin_account_desc": "重要なアカウントをまとめて保持", "s_rename_account": "アカウント名を変更", "l_rename_account_desc": "アカウントの発行者/名前を編集", "s_account_renamed": "アカウントの名前が変更されました", - "l_rename_account_failed": null, + "l_rename_account_failed": "アカウント名の変更に失敗しました: {message}", "@l_rename_account_failed": { "placeholders": { "message": {} @@ -458,7 +458,7 @@ "l_delete_account_desc": "YubiKeyからアカウントを削除", "s_account_deleted": "アカウントが削除されました", "p_warning_delete_account": "警告!このアクションにより、YubiKeyからアカウントが削除されます。", - "p_warning_disable_credential": "このアカウントのOTPを生成できなくなります。アカウントからロックアウトされないよう、初めにWebサイトからこの認証情報を無効にしてください。", + "p_warning_disable_credential": "このアカウントでOTPを生成することはできなくなります。アカウントからロックアウトされないように、必ずウェブサイトからこのクレデンシャルを無効にしてください。", "s_account_name": "アカウント名", "s_search_accounts": "アカウントを検索", "l_accounts_used": "{used}/{capacity}のアカウントが使用済み", @@ -509,7 +509,7 @@ "s_passkey_deleted": "パスキーが削除されました", "p_warning_delete_passkey": "これにより、YubiKeyからパスキーが削除されます。", "s_search_passkeys": "パスキーを検索", - "p_passkeys_used": "{used} の {max} 使用されたパスキー", + "p_passkeys_used": "{used} の {max} 使用されたパスキー。", "@p_passkeys_used": { "placeholders": { "used": {}, @@ -517,7 +517,7 @@ } }, "@_fingerprints": {}, - "s_biometrics": null, + "s_biometrics": "バイオメトリクス", "l_fingerprint": "指紋:{label}", "@l_fingerprint": { "placeholders": { @@ -600,7 +600,7 @@ "l_delete_certificate_or_key_desc": "YubiKey から証明書または鍵を削除する", "l_move_key": "キーを移動", "l_move_key_desc": "あるPIVスロットから別のスロットにキーを移動する", - "l_change_defaults": null, + "l_change_defaults": "デフォルトのアクセスコードを変更する", "s_issuer": "発行者", "s_serial": "シリアル", "s_certificate_fingerprint": "フィンガープリント", @@ -676,10 +676,10 @@ "p_subject_desc": "RFC 4514仕様に準拠した形式の識別名(DN)。", "l_rfc4514_invalid": "無効なRFC 4514形式", "rfc4514_examples": "例:\nCN=Example Name CN=jsmith,DC=example,\nDC=net", - "s_allow_fingerprint": null, + "s_allow_fingerprint": "指紋認証", "p_cert_options_desc": "使用する鍵アルゴリズム、出力形式、および有効期限(証明書のみ)。", - "p_cert_options_bio_desc": null, - "p_key_options_bio_desc": null, + "p_cert_options_bio_desc": "使用する鍵アルゴリズム、出力形式、有効期限(証明書のみ)、PINの代わりに生体認証を使用できるかどうか。", + "p_key_options_bio_desc": "暗証番号の代わりに生体認証を使用できるようにする。", "s_overwrite_slot": "スロットを上書き", "p_overwrite_slot_desc": "これにより、スロット{slot}内の既存コンテンツが完全に上書きされます。", "@p_overwrite_slot_desc": { @@ -822,7 +822,7 @@ "s_reset": "リセット", "s_factory_reset": "工場出荷時の状態にリセット", "l_factory_reset_desc": "YubiKey の既定値を復元", - "l_factory_reset_required": null, + "l_factory_reset_required": "工場出荷時のリセットが必要", "l_oath_application_reset": "OATHアプリケーションのリセット", "l_fido_app_reset": "FIDOアプリケーションのリセット", "l_reset_failed": "リセットの実行エラー:{message}", @@ -835,13 +835,13 @@ "p_factory_reset_an_app": "YubiKeyのアプリケーションを工場出荷時の状態にリセットします。", "p_factory_reset_desc": "データは、YubiKeyの複数のアプリケーションに保存されています。一部のアプリケーションは、単独で工場出荷時の状態にリセットできます。\n\nリセットするには上のアプリケーションを選択してください。", "p_warning_factory_reset": "警告!これにより、すべてのOATH TOTP/HOTPアカウントがYubiKeyから削除されます。削除は取り消すことができません。", - "p_warning_disable_credentials": "OATHの認証情報とパスワードセットがこのYubiKeyから削除されます。アカウントからロックアウトされないよう、初めに各Webサイトからこれらを無効にしてください。", + "p_warning_disable_credentials": "あなたの OATH 認証情報、および設定されているパスワードは、この YubiKey から削除されます。アカウントからロックアウトされないように、それぞれのウェブサイトからこれらを無効にしてください。", "p_warning_deletes_accounts": "警告!これにより、すべてのU2FおよびFIDO2アカウントがパスキーを含めてYubiKeyから削除されます。削除は取り消すことができません。", - "p_warning_disable_accounts": "認証情報とPINセットがこのYubiKeyから削除されます。アカウントからロックアウトされないよう、初めに各Webサイトからこれらを無効にしてください。", + "p_warning_disable_accounts": "あなたの認証情報および設定された PIN は、この YubiKey から削除されます。アカウントからロックアウトされないように、それぞれのウェブサイトからこれらを無効にしてください。", "p_warning_piv_reset": "警告!PIVに関連して保存されたすべてのデータがYubiKeyから削除されます。削除は取り消すことができません。", "p_warning_piv_reset_desc": "これには、秘密鍵と証明書が含まれます。PIN、PUK、および管理キーが工場出荷時のデフォルト値にリセットされます。", "p_warning_global_reset": "警告!これにより、すべての保存済みデータが認証情報を含めてYubiKeyから削除されます。削除は取り消すことができません。", - "p_warning_global_reset_desc": "YubiKeyのアプリケーションを工場出荷時の状態にリセットします。PINは工場出荷時のデフォルト値にリセットされ、登録された指紋は削除されます。すべての鍵、証明書、またはその他の認証情報が完全に削除されます。", + "p_warning_global_reset_desc": "YubiKeyのアプリケーションをファクトリーリセットします。PINは工場出荷時の値にリセットされ、登録されていた指紋は削除されます。鍵、証明書、その他の認証情報はすべて永久に削除されます。", "@_copy_to_clipboard": {}, "l_copy_to_clipboard": "クリップボードにコピー", @@ -923,10 +923,10 @@ "s_nfc_dialog_fido_failure": "FIDO操作に失敗しました", "@_nfc": {}, - "s_nfc_ready_to_scan": null, - "s_nfc_hold_still": null, - "s_nfc_tap_your_yubikey": null, - "l_nfc_failed_to_scan": null, + "s_nfc_ready_to_scan": "スキャン準備完了", + "s_nfc_hold_still": "そのまま\u2026", + "s_nfc_tap_your_yubikey": "YubiKeyをタップする", + "l_nfc_failed_to_scan": "スキャンに失敗しました。", "@_ndef": {}, "p_ndef_set_otp": "OTPコードがYubiKeyからクリップボードに正常にコピーされました。", @@ -936,7 +936,7 @@ "@_key_customization": {}, "s_set_label": "ラベルを設定", - "s_set_color": null, + "s_set_color": "設定色", "s_change_label": "ラベルを変更", "s_color": "色", "p_set_will_add_custom_name": "これにより、YubiKey にカスタム名を付けることができます。", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 2aa42f669..3a0371177 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -28,17 +28,17 @@ "s_cancel": "Anuluj", "s_close": "Zamknij", "s_delete": "Usuń", - "s_move": null, + "s_move": "Przenieś", "s_quit": "Wyjdź", - "s_enable": null, - "s_enabled": null, - "s_disabled": null, + "s_enable": "Włącz", + "s_enabled": "Włączone", + "s_disabled": "Wyłączony", "s_status": "Status", "s_unlock": "Odblokuj", "s_calculate": "Oblicz", "s_import": "Importuj", "s_overwrite": "Nadpisz", - "s_done": null, + "s_done": "Gotowe", "s_label": "Etykieta", "s_name": "Nazwa", "s_usb": "USB", @@ -47,11 +47,11 @@ "s_details": "Szczegóły", "s_show_window": "Pokaż okno", "s_hide_window": "Ukryj okno", - "s_show_navigation": null, - "s_expand_navigation": null, - "s_collapse_navigation": null, - "s_show_menu": null, - "s_hide_menu": null, + "s_show_navigation": "Pokaż nawigację", + "s_expand_navigation": "Rozwiń nawigację", + "s_collapse_navigation": "Zwiń nawigację", + "s_show_menu": "Pokaż menu", + "s_hide_menu": "Ukryj menu", "q_rename_target": "Zmienić nazwę {label}?", "@q_rename_target": { "placeholders": { @@ -64,38 +64,38 @@ "item": {} } }, - "s_none": null, + "s_none": "", "s_about": "O aplikacji", "s_algorithm": "Algorytm", "s_appearance": "Wygląd", "s_actions": "Działania", - "s_manage": "Zarządzaj", - "s_setup": "Konfiguruj", - "s_device": null, - "s_application": null, + "s_manage": "Zarządzanie", + "s_setup": "Konfiguracja", + "s_device": "Urządzenie", + "s_application": "Aplikacja", "s_settings": "Ustawienia", - "l_settings_desc": null, + "l_settings_desc": "Zmień ustawienia aplikacji", "s_certificates": "Certyfikaty", - "s_security_key": null, + "s_security_key": "Klucz bezpieczeństwa", "s_slots": "Sloty", "s_help_and_about": "Pomoc i informacje", - "l_help_and_about_desc": null, + "l_help_and_about_desc": "Rozwiązywanie problemów i wsparcie", "s_help_and_feedback": "Pomoc i opinie", - "s_home": null, - "s_user_guide": null, + "s_home": "Strona główna", + "s_user_guide": "Przewodnik użytkownika", "s_i_need_help": "Pomoc", "s_troubleshooting": "Rozwiązywanie problemów", "s_terms_of_use": "Warunki użytkowania", "s_privacy_policy": "Polityka prywatności", "s_open_src_licenses": "Licencje open source", - "s_please_wait": "Proszę czekać\u2026", + "s_please_wait": "Poczekaj\u2026", "s_secret_key": "Tajny klucz", "s_show_secret_key": "Pokaż tajny klucz", "s_hide_secret_key": "Ukryj tajny klucz", "s_private_key": "Klucz prywatny", - "s_public_key": null, - "s_invalid_length": "Nieprawidłowa długość", + "s_public_key": "Klucz publiczny", + "s_invalid_length": "Długość jest nieprawidłowa", "l_invalid_format_allowed_chars": "Nieprawidłowy format, dozwolone znaki: {characters}", "@l_invalid_format_allowed_chars": { "placeholders": { @@ -106,7 +106,7 @@ "s_require_touch": "Wymagaj dotknięcia", "q_have_account_info": "Masz dane konta?", "s_run_diagnostics": "Uruchom diagnostykę", - "s_log_level": "Poziom logowania: {level}", + "s_log_level": "Poziom logów: {level}", "@s_log_level": { "placeholders": { "level": {} @@ -117,24 +117,24 @@ "@_language": {}, "s_language": "Język", - "l_enable_community_translations": "Tłumaczenia społecznościowe", - "p_community_translations_desc": "Są dostarczane i utrzymywane przez społeczność. Mogą zawierać błędy lub być niekompletne.", + "l_enable_community_translations": "Włącz tłumaczenia społecznościowe", + "p_community_translations_desc": "Tłumaczenia są dostarczane i utrzymywane przez społeczność. Mogą zawierać błędy lub być niekompletne.", "@_theme": {}, "s_app_theme": "Motyw aplikacji", - "s_choose_app_theme": "Wybierz motyw aplikacji", - "s_system_default": "Zgodny z systemem", + "s_choose_app_theme": "Wybierz motyw", + "s_system_default": "Domyślny systemu", "s_light_mode": "Jasny", "s_dark_mode": "Ciemny", "@_layout": {}, - "s_list_layout": null, - "s_grid_layout": null, - "s_mixed_layout": null, - "s_select_layout": null, + "s_list_layout": "Układ listy", + "s_grid_layout": "Układ siatki", + "s_mixed_layout": "Układ mieszany", + "s_select_layout": "Wybierz układ", "@_yubikey_selection": {}, - "s_select_to_scan": "Wybierz, aby skanować", + "s_select_to_scan": "Wybierz, aby zeskanować", "s_hide_device": "Ukryj urządzenie", "s_show_hidden_devices": "Pokaż ukryte urządzenia", "s_sn_serial": "S/N: {serial}", @@ -154,27 +154,27 @@ "serial": {} } }, - "l_serial_number": null, + "l_serial_number": "Numer seryjny: {serial}", "@l_firmware_version": { "placeholders": { "version": {} } }, - "l_firmware_version": null, - "l_fips_capable": null, - "l_fips_approved": null, + "l_firmware_version": "Wersja oprogramowania: {version}", + "l_fips_capable": "Zgodność ze standardem FIPS", + "l_fips_approved": "Zatwierdzony przez FIPS", "@_yubikey_interactions": {}, "l_insert_yk": "Podłącz klucz YubiKey", - "l_insert_or_tap_yk": "Podłącz lub przystaw YubiKey", + "l_insert_or_tap_yk": "Podłącz lub zbliż klucz YubiKey", "l_unplug_yk": "Odłącz klucz YubiKey", - "l_reinsert_yk": "Ponownie podłącz YubiKey", - "l_place_on_nfc_reader": "Przyłóż klucz YubiKey do czytnika NFC", - "l_replace_yk_on_reader": "Umieść klucz YubiKey z powrotem na czytniku", + "l_reinsert_yk": "Podłącz ponownie klucz YubiKey", + "l_place_on_nfc_reader": "Zbliż klucz YubiKey do czytnika NFC", + "l_replace_yk_on_reader": "Zbliż klucz YubiKey do czytnika", "l_remove_yk_from_reader": "Odsuń klucz YubiKey od czytnika NFC", - "p_try_reinsert_yk": "Spróbuj ponownie podłączyć klucz YubiKey.", - "s_touch_required": "Wymagane dotknięcie", - "l_touch_button_now": "Dotknij teraz przycisku na kluczu YubiKey", + "p_try_reinsert_yk": "Spróbuj podłączyć ponownie klucz YubiKey.", + "s_touch_required": "Dotknięcie wymagane", + "l_touch_button_now": "Dotknij przycisku na kluczu YubiKey", "l_keep_touching_yk": "Wielokrotnie dotykaj klucza YubiKey\u2026", "@_capabilities": {}, @@ -184,91 +184,91 @@ "s_capability_oath": "OATH", "s_capability_piv": "PIV", "s_capability_openpgp": "OpenPGP", - "s_capability_hsmauth": "Aut. YubiHSM", + "s_capability_hsmauth": "YubiHSM Auth", "@_app_configuration": {}, - "s_toggle_applications": "Przełączanie funkcji", - "s_toggle_interfaces": "Przełącz interfejsy", - "p_toggle_applications_desc": null, - "p_toggle_interfaces_desc": null, - "l_toggle_applications_desc": null, - "l_toggle_interfaces_desc": null, - "s_reconfiguring_yk": "Rekonfigurowanie YubiKey\u2026", - "s_config_updated": "Zaktualizowano konfigurację", - "l_config_updated_reinsert": "Zaktualizowano konfigurację, podłącz ponownie klucz YubiKey", - "s_app_not_supported": "Funkcja nie jest obsługiwana", - "l_app_not_supported_on_yk": "Używany klucz YubiKey nie obsługuje funkcji '{app}'", + "s_toggle_applications": "Przełączanie aplikacji", + "s_toggle_interfaces": "Przełączanie interfejsów", + "p_toggle_applications_desc": "Włącz lub wyłącz aplikacje przez dostępne interfejsy.", + "p_toggle_interfaces_desc": "Włącz lub wyłącz interfejs USB.", + "l_toggle_applications_desc": "Włącz / wyłącz aplikacje", + "l_toggle_interfaces_desc": "Włącz / wyłącz interfejsy", + "s_reconfiguring_yk": "Rekonfigurowanie klucza YubiKey\u2026", + "s_config_updated": "Konfiguracja została zaktualizowana", + "l_config_updated_reinsert": "Konfiguracja została zaktualizowana. Podłącz ponownie klucz YubiKey", + "s_app_not_supported": "Aplikacja nie jest obsługiwana", + "l_app_not_supported_on_yk": "Używany klucz YubiKey nie obsługuje aplikacji '{app}'", "@l_app_not_supported_on_yk": { "placeholders": { "app": {} } }, - "s_app_disabled": "Wyłączona funkcja", - "l_app_disabled_desc": "Włącz funkcję '{app}' w kluczu YubiKey, aby uzyskać dostęp", + "s_app_disabled": "Aplikacja została wyłączona", + "l_app_disabled_desc": "Włącz aplikację '{app}' w kluczu YubiKey, aby uzyskać do niej dostęp", "@l_app_disabled_desc": { "placeholders": { "app": {} } }, - "s_fido_disabled": "FIDO2 wyłączone", - "l_webauthn_req_fido2": "WebAuthn wymaga włączenia funkcji FIDO2 w kluczu YubiKey", - "s_lock_code": null, - "l_wrong_lock_code": null, - "s_show_lock_code": null, - "s_hide_lock_code": null, - "p_lock_code_required_desc": null, + "s_fido_disabled": "Aplikacja FIDO2 została wyłączona", + "l_webauthn_req_fido2": "WebAuthn wymaga włączenia apliakcji FIDO2 w kluczu YubiKey", + "s_lock_code": "Kod blokady", + "l_wrong_lock_code": "Kod blokady jest nieprawidłowy", + "s_show_lock_code": "Pokaż kod blokady", + "s_hide_lock_code": "Ukryj kod blokady", + "p_lock_code_required_desc": "Działanie wymaga wpisania kodu blokady.", "@_connectivity_issues": {}, "l_helper_not_responding": "Proces pomocnika nie odpowiada", "l_yk_no_access": "Dostęp do tego klucza YubiKey jest niemożliwy", - "s_yk_inaccessible": "Urządzenie niedostępne", + "s_yk_inaccessible": "Urządzenie jest niedostępne", "l_open_connection_failed": "Nie udało się nawiązać połączenia", "l_ccid_connection_failed": "Nie udało się nawiązać połączenia z kartą inteligentną", - "p_ccid_service_unavailable": "Upewnij się, że usługa kart inteligentnych działa.", - "p_pcscd_unavailable": "Upewnij się, że pcscd jest zainstalowany i uruchomiony.", - "l_no_yk_present": "Nie wykryto YubiKey", + "p_ccid_service_unavailable": "Upewnij się, że aplikacja kart inteligentnych działa.", + "p_pcscd_unavailable": "Upewnij się, że pakiet pcscd jest zainstalowany i uruchomiony.", + "l_no_yk_present": "Nie wykryto klucza YubiKey", "s_unknown_type": "Nieznany typ", "s_unknown_device": "Nierozpoznane urządzenie", - "s_restricted_nfc": null, - "l_deactivate_restricted_nfc": null, - "p_deactivate_restricted_nfc_desc": null, - "p_deactivate_restricted_nfc_footer": null, - "s_unsupported_yk": "Nieobsługiwany klucz YubiKey", - "s_yk_not_recognized": "Urządzenie nie rozpoznane", - "p_operation_failed_try_again": null, - "l_configuration_unsupported": null, - "p_scp_unsupported": null, + "s_restricted_nfc": "Aktywacja NFC", + "l_deactivate_restricted_nfc": "Jak aktywować NFC", + "p_deactivate_restricted_nfc_desc": "Podłącz YubiKey do dowolnego źródła zasilania USB, takiego jak komputer, na co najmniej 3 sekundy.\n\nPo włączeniu zasilania, NFC zostanie aktywowane i będzie gotowe do użycia.", + "p_deactivate_restricted_nfc_footer": "Urządzenie YubiKey jest wyposażone w funkcję Restricted NFC, zaprojektowaną w celu zabezpieczenia przed manipulacją bezprzewodową podczas transportu. Oznacza to, że operacje NFC są tymczasowo wyłączone do momentu ich aktywacji.", + "s_unsupported_yk": "Klucz YubiKey jest nieobsługiwany", + "s_yk_not_recognized": "Urządzenie nie zostało rozpoznane", + "p_operation_failed_try_again": "Operacja nie powiodła się. Spróbuj ponownie.", + "l_configuration_unsupported": "Konfiguracja nie jest obsługiwana", + "p_scp_unsupported": "Aby komunikować się przez NFC, YubiKey wymaga technologii, która nie jest obsługiwana przez ten telefon. Podłącz klawiaturę YubiKey do portu USB telefonu.", "@_general_errors": {}, "l_error_occurred": "Wystąpił błąd", - "s_application_error": "Błąd funkcji", + "s_application_error": "Błąd aplikacji", "l_import_error": "Błąd importowania", - "l_file_not_found": "Nie odnaleziono pliku", - "l_file_too_big": "Zbyt duży rozmiar pliku", + "l_file_not_found": "Plik nie został znaleziony", + "l_file_too_big": "Plik jest zbyt duży", "l_filesystem_error": "Błąd operacji systemu plików", "@_pins": {}, "s_pin": "PIN", "s_puk": "PUK", - "s_set_pin": "Ustaw PIN", - "s_change_pin": "Zmień PIN", - "s_change_puk": "Zmień PUK", - "s_show_pin": "Pokaż PIN", - "s_hide_pin": "Ukryj PIN", - "s_show_puk": "Pokaż PUK", - "s_hide_puk": "Ukryj PUK", - "s_current_pin": "Aktualny PIN", - "s_current_puk": "Aktualny PUK", - "s_new_pin": "Nowy PIN", - "s_new_puk": "Nowy PUK", - "s_confirm_pin": "Potwierdź PIN", - "s_confirm_puk": "Potwierdź PUK", - "s_unblock_pin": "Odblokuj PIN", - "l_pin_mismatch": null, - "l_puk_mismatch": null, - "s_pin_set": "PIN ustawiony", - "s_puk_set": "PUK ustawiony", + "s_set_pin": "Ustaw kod PIN", + "s_change_pin": "Zmień kod PIN", + "s_change_puk": "Zmień kod PUK", + "s_show_pin": "Pokaż kod PIN", + "s_hide_pin": "Ukryj kod PIN", + "s_show_puk": "Pokaż kod PUK", + "s_hide_puk": "Ukryj kod PUK", + "s_current_pin": "Obecny kod PIN", + "s_current_puk": "Obecny kod PUK", + "s_new_pin": "Nowy kod PIN", + "s_new_puk": "Nowy kod PUK", + "s_confirm_pin": "Potwierdź kod PIN", + "s_confirm_puk": "Potwierdź kod PUK", + "s_unblock_pin": "Odblokuj kod PIN", + "l_pin_mismatch": "Kody PIN nie pasują do siebie", + "l_puk_mismatch": "Kody PUK nie pasują do siebie", + "s_pin_set": "Kod PIN został ustawiony", + "s_puk_set": "Kod PUK został ustawiony", "l_set_pin_failed": "Nie udało się ustawić kodu PIN: {message}", "@l_set_pin_failed": { "placeholders": { @@ -294,25 +294,25 @@ } }, "s_fido_pin_protection": "Ochrona FIDO kodem PIN", - "s_pin_change_required": "Wymagana zmiana PINu", - "l_enter_fido2_pin": "Wprowadź kod PIN FIDO2 klucza YubiKey", - "l_pin_blocked_reset": "PIN jest zablokowany; przywróć ustawienia fabryczne funkcji FIDO", - "l_pin_blocked": null, + "s_pin_change_required": "Wymagana zmiana kodu PIN", + "l_enter_fido2_pin": "Wpisz kod PIN FIDO2 klucza YubiKey", + "l_pin_blocked_reset": "Kod PIN jest zablokowany. Przywróć ustawienia fabryczne aplikacji FIDO", + "l_pin_blocked": "Kod PIN jest zablokowany", "l_set_pin_first": "Najpierw wymagany jest kod PIN", "l_unlock_pin_first": "Najpierw odblokuj kodem PIN", - "l_pin_soft_locked": "PIN został zablokowany do momentu ponownego podłączenia klucza YubiKey", - "l_pin_change_required_desc": "Wymagane ustawienie nowego kodu PIN przed użyciem tej funkcji", - "p_enter_current_pin_or_reset": "Wprowadź aktualny kod PIN. Jeśli go nie znasz, musisz zresetować klucz YubiKey.", - "p_enter_current_pin_or_reset_no_puk": "Wprowadź aktualny PIN. Jeśli go nie znasz, musisz zresetować klucz YubiKey.", - "p_enter_current_puk_or_reset": "Wprowadź aktualny kod PUK. Jeśli go nie znasz, musisz zresetować klucz YubiKey.", - "p_enter_new_fido2_pin": null, + "l_pin_soft_locked": "Kod PIN został zablokowany do momentu ponownego podłączenia klucza YubiKey", + "l_pin_change_required_desc": "Przed użyciem tej aplikacji ustaw nowy kod PIN", + "p_enter_current_pin_or_reset": "Wpisz obecny kod PIN. Jeśli go nie pamiętasz, zresetuj klucz YubiKey lub odblokuj go za pomocą kodu PUK.", + "p_enter_current_pin_or_reset_no_puk": "Wpisz obecny kod PIN. Jeśli go nie pamiętasz, zresetuj klucz YubiKey.", + "p_enter_current_puk_or_reset": "Wpisz obecny kod PUK. Jeśli go nie pamiętasz, zresetuj klucz YubiKey.", + "p_enter_new_fido2_pin": "Wprowadź nowy kod PIN. Kod PIN musi mieć długość {min_length}-{max_length} znaków i może zawierać litery, cyfry i znaki specjalne.", "@p_enter_new_fido2_pin": { "placeholders": { "min_length": {}, "max_length": {} } }, - "p_enter_new_fido2_pin_complexity_active": null, + "p_enter_new_fido2_pin_complexity_active": "Wprowadź nowy kod PIN. Kod PIN musi mieć długość {min_length}-{max_length} znaków, zawierać co najmniej {unique_characters} unikalnych znaków i nie może być powszechnie używanym kodem PIN, takim jak \"{common_pin}\". Może zawierać litery, cyfry i znaki specjalne.", "@p_enter_new_fido2_pin_complexity_active": { "placeholders": { "min_length": {}, @@ -321,23 +321,23 @@ "common_pin": {} } }, - "s_ep_attestation": null, - "s_ep_attestation_enabled": null, - "s_enable_ep_attestation": null, - "p_enable_ep_attestation_desc": null, - "p_enable_ep_attestation_disable_with_factory_reset": null, - "s_pin_required": "Wymagany PIN", - "p_pin_required_desc": "Czynność, którą zamierzasz wykonać, wymaga wprowadzenia kodu PIN PIV.", - "l_piv_pin_blocked": "Zablokowano, użyj PUK, aby zresetować", - "l_piv_pin_puk_blocked": "Zablokowano, konieczny reset do ustawień fabrycznych", - "p_enter_new_piv_pin_puk": null, + "s_ep_attestation": "Atest przedsiębiorstwa", + "s_ep_attestation_enabled": "Atestacja przedsiębiorstwa włączona", + "s_enable_ep_attestation": "Włącz zaświadczenia dla przedsiębiorstw", + "p_enable_ep_attestation_desc": "Umożliwi to zaświadczenie Enterprise Attestation, pozwalające autoryzowanym domenom na jednoznaczną identyfikację klucza YubiKey.", + "p_enable_ep_attestation_disable_with_factory_reset": "Po włączeniu atest Enterprise Attestation można wyłączyć tylko poprzez przywrócenie ustawień fabrycznych FIDO.", + "s_pin_required": "Kod PIN jest wymagany", + "p_pin_required_desc": "Działanie wymaga wpisania kodu PIN PIV.", + "l_piv_pin_blocked": "Zablokowano, użyj kodu PUK, aby zresetować", + "l_piv_pin_puk_blocked": "Zablokowano, zresetuj do ustawień fabrycznych", + "p_enter_new_piv_pin_puk": "Wprowadź nowy adres {name} do ustawienia. Musi mieć co najmniej {length} znaków.", "@p_enter_new_piv_pin_puk": { "placeholders": { "name": {}, "length": {} } }, - "p_enter_new_piv_pin_puk_complexity_active": null, + "p_enter_new_piv_pin_puk_complexity_active": "Wprowadź nową nazwę {name} do ustawienia. Musi mieć co najmniej {length} znaków, zawierać co najmniej 2 unikalne znaki i nie może być powszechnie używanym {name}, takim jak \"{common}\".", "@p_enter_new_piv_pin_puk_complexity_active": { "placeholders": { "name": {}, @@ -345,17 +345,17 @@ "common": {} } }, - "p_pin_puk_complexity_failure": null, + "p_pin_puk_complexity_failure": "{name} nie spełnia wymogów.", "@p_pin_puk_complexity_failure": { "placeholders": { "name": {} } }, - "l_warning_default_pin": null, - "l_warning_default_puk": null, - "l_default_pin_used": null, - "l_default_puk_used": null, - "l_pin_complexity": null, + "l_warning_default_pin": "Ostrzeżenie: Używany jest domyślny kod PIN", + "l_warning_default_puk": "Ostrzeżenie: Używany jest domyślny kod PUK", + "l_default_pin_used": "Używany jest domyślny kod PIN", + "l_default_puk_used": "Używany jest domyślny kod PUK", + "l_pin_complexity": "Wymuszona złożoność kodu PIN", "@_passwords": {}, "s_password": "Hasło", @@ -365,41 +365,41 @@ "s_show_password": "Pokaż hasło", "s_hide_password": "Ukryj hasło", "l_optional_password_protection": "Opcjonalna ochrona hasłem", - "l_password_protection": null, + "l_password_protection": "Ochrona kont hasłem", "s_new_password": "Nowe hasło", - "s_current_password": "Aktualne hasło", + "s_current_password": "Obecne hasło", "s_confirm_password": "Potwierdź hasło", - "l_password_mismatch": null, - "s_wrong_password": "Błędne hasło", + "l_password_mismatch": "Hasła nie pasują do siebie", + "s_wrong_password": "Hasło jest nieprawidłowe", "s_remove_password": "Usuń hasło", "s_password_removed": "Hasło zostało usunięte", "s_remember_password": "Zapamiętaj hasło", "s_clear_saved_password": "Usuń zapisane hasło", - "s_password_forgotten": "Hasło zostało zapomniane", + "s_password_forgotten": "Zapomniane hasło", "l_keystore_unavailable": "Magazyn kluczy systemu operacyjnego jest niedostępny", "l_remember_pw_failed": "Nie udało się zapamiętać hasła", "l_unlock_first": "Najpierw odblokuj hasłem", - "l_set_password_first": null, - "l_enter_oath_pw": "Wprowadź hasło OATH dla klucza YubiKey", - "p_enter_current_password_or_reset": "Wprowadź aktualne hasło. Jeśli go nie znasz, musisz zresetować klucz YubiKey.", - "p_enter_new_password": "Wprowadź nowe hasło. Może ono zawierać litery, cyfry i znaki specjalne.", + "l_set_password_first": "Ustaw hasło", + "l_enter_oath_pw": "Wpisz hasło OATH dla klucza YubiKey", + "p_enter_current_password_or_reset": "Wpisz obecne hasło. Jeśli go nie pamiętasz, zresetuj klucz YubiKey.", + "p_enter_new_password": "Wpisz nowe hasło. Hasło może zawierać litery, cyfry i znaki specjalne.", "@_management_key": {}, "s_management_key": "Klucz zarządzania", - "s_current_management_key": "Aktualny klucz zarządzania", + "s_current_management_key": "Obecny klucz zarządzania", "s_new_management_key": "Nowy klucz zarządzania", "l_change_management_key": "Zmień klucz zarządzania", - "p_change_management_key_desc": "Zmień swój klucz zarządzania. Opcjonalnie możesz zezwolić na używanie kodu PIN zamiast klucza zarządzania.", - "l_management_key_changed": "Zmieniono klucz zarządzania", + "p_change_management_key_desc": "Zmień klucz zarządzania. Możesz opcjonalnie używać kodu PIN.", + "l_management_key_changed": "Klucz zarządzania został zmieniony", "l_default_key_used": "Używany jest domyślny klucz zarządzania", "s_generate_random": "Generuj losowo", "s_use_default": "Użyj domyślnego", - "l_warning_default_key": "Uwaga: Używany jest klucz domyślny", + "l_warning_default_key": "Ostrzeżenie: Używany jest domyślny klucz", "s_protect_key": "Zabezpiecz kodem PIN", "l_pin_protected_key": "Zamiast tego można użyć kodu PIN", - "l_wrong_key": "Błędny klucz", + "l_wrong_key": "Klucz jest nieprawidłowy", "l_unlock_piv_management": "Odblokuj zarządzanie PIV", - "p_unlock_piv_management_desc": "Czynność, którą zamierzasz wykonać, wymaga klucza zarządzania PIV. Podaj ten klucz, aby odblokować funkcje zarządzania dla tej sesji.", + "p_unlock_piv_management_desc": "Działanie wymaga klucza zarządzania PIV. Wpisz ten klucz, aby odblokować funkcje zarządzania dla tej sesji.", "@_oath_accounts": {}, "l_account": "Konto: {label}", @@ -410,16 +410,16 @@ }, "s_accounts": "Konta", "s_no_accounts": "Brak kont", - "l_results_for": null, + "l_results_for": "Wyniki dla \"{query}\"", "@l_results_for": { "placeholders": { "query": {} } }, - "l_authenticator_get_started": "Rozpocznij korzystanie z kont OTP", - "l_no_accounts_desc": "Dodaj konta do swojego klucza YubiKey od dowolnego dostawcy usług obsługującego OATH TOTP/HOTP", + "l_authenticator_get_started": "Zacznij korzystać z kont OTP", + "l_no_accounts_desc": "Dodaj do klucza YubiKey konta z dowolnej usługi wspierające kody OATH TOTP/HOTP", "s_add_account": "Dodaj konto", - "s_add_accounts": "Dodaj konto(-a)", + "s_add_accounts": "Dodaj konta", "p_add_description": "W celu zeskanowania kodu QR, upewnij się, że pełny kod jest widoczny na ekranie a następnie naciśnij poniższy przycisk. Jeśli posiadasz dane uwierzytelniające do konta w tekstowej formie, skorzystaj z opcji ręcznego wprowadzania danych.", "l_drop_qr_description": "Upuść kod QR, aby dodać konto(a)", "s_add_manually": "Dodaj ręcznie", @@ -430,24 +430,24 @@ "message": {} } }, - "l_add_account_password_required": null, - "l_add_account_unlock_required": null, - "l_add_account_already_exists": null, - "l_add_account_func_missing": null, - "l_account_name_required": "Twoje konto musi mieć nazwę", - "l_name_already_exists": "Ta nazwa już istnieje dla tego wydawcy", - "l_account_already_exists": "To konto już istnieje w YubiKey", + "l_add_account_password_required": "Wymagane hasło", + "l_add_account_unlock_required": "Wymagane odblokowanie", + "l_add_account_already_exists": "Konto już istnieje", + "l_add_account_func_missing": "Brak lub wyłączona funkcjonalność", + "l_account_name_required": "Konto musi mieć nazwę", + "l_name_already_exists": "Ta nazwa wydawcy już istnieje", + "l_account_already_exists": "To konto już istnieje w kluczu YubiKey", "l_invalid_character_issuer": "Nieprawidłowy znak: „:” nie jest dozwolony w polu wydawcy", - "l_select_accounts": "Wybierz konta, które chcesz dodać do YubiKey", + "l_select_accounts": "Wybierz konta, które chcesz dodać do klucza YubiKey", "s_pin_account": "Przypnij konto", "s_unpin_account": "Odepnij konto", "s_no_pinned_accounts": "Brak przypiętych kont", - "s_pinned": null, - "l_pin_account_desc": "Przechowuj ważne konta razem", + "s_pinned": "Przypięty", + "l_pin_account_desc": "Trzymaj ważne konta razem", "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_desc": "Edytuj wydawcę lub nazwę konta", + "s_account_renamed": "Nazwa konta została zmieniona", + "l_rename_account_failed": "Zmiana nazwy konta nie powiodła się: {message}", "@l_rename_account_failed": { "placeholders": { "message": {} @@ -457,10 +457,10 @@ "s_delete_account": "Usuń konto", "l_delete_account_desc": "Usuń konto z klucza YubiKey", "s_account_deleted": "Konto zostało usunięte", - "p_warning_delete_account": "Uwaga! Ta czynność spowoduje usunięcie konta z klucza YubiKey.", + "p_warning_delete_account": "Ostrzeżenie! Spowoduje to usunięcie konta z klucza YubiKey.", "p_warning_disable_credential": "Nie będzie już możliwe generowanie OTP dla tego konta. Upewnij się, że najpierw wyłączono te dane uwierzytelniające w witrynie, aby uniknąć zablokowania konta.", "s_account_name": "Nazwa konta", - "s_search_accounts": "Wyszukaj konta", + "s_search_accounts": "Szukaj kont", "l_accounts_used": "Użyto {used} z {capacity} kont", "@l_accounts_used": { "placeholders": { @@ -474,7 +474,7 @@ "num": {} } }, - "s_num_sec": "{num} sek", + "s_num_sec": "{num} sek.", "@s_num_sec": { "placeholders": { "num": {} @@ -483,15 +483,15 @@ "s_issuer_optional": "Wydawca (opcjonalnie)", "s_counter_based": "Na podstawie licznika", "s_time_based": "Na podstawie czasu", - "l_copy_code_desc": "Łatwe wklejanie kodu do innych aplikacji", + "l_copy_code_desc": "Wklej kod do innej aplikacji", "l_calculate_code_desc": "Uzyskaj nowy kod z klucza YubiKey", "@_fido_credentials": {}, - "s_rp_id": null, - "s_user_id": null, - "s_credential_id": null, - "s_display_name": null, - "s_user_name": null, + "s_rp_id": "RP ID", + "s_user_id": "User ID", + "s_credential_id": "Credential ID", + "s_display_name": "Nazwa wyświetlana", + "s_user_name": "Nazwa użytkownika", "l_passkey": "Klucz dostępu: {label}", "@l_passkey": { "placeholders": { @@ -499,17 +499,17 @@ } }, "s_passkeys": "Klucze dostępu", - "s_no_passkeys": null, - "l_ready_to_use": "Gotowe do użycia", + "s_no_passkeys": "Brak kluczy dostępu", + "l_ready_to_use": "Gotowy do użycia", "l_register_sk_on_websites": "Zarejestruj jako klucz bezpieczeństwa na stronach internetowych", - "l_no_discoverable_accounts": "Nie wykryto kont", - "p_non_passkeys_note": null, + "l_no_discoverable_accounts": "Brak kluczy dostępu", + "p_non_passkeys_note": "Poświadczenia bez klucza mogą istnieć, ale nie mogą być wymienione.", "s_delete_passkey": "Usuń klucz dostępu", "l_delete_passkey_desc": "Usuń klucz dostępu z klucza YubiKey", - "s_passkey_deleted": "Usunięto klucz dostępu", - "p_warning_delete_passkey": "Spowoduje to usunięcie klucza dostępu z klucza YubiKey.", - "s_search_passkeys": null, - "p_passkeys_used": null, + "s_passkey_deleted": "Klucz dostępu został usunięty", + "p_warning_delete_passkey": "Spowoduje to usunięcie klucza dostępu z YubiKey.", + "s_search_passkeys": "Szukaj kluczy dostępu", + "p_passkeys_used": "{used} użytych kluczy dostępu {max} .", "@p_passkeys_used": { "placeholders": { "used": {}, @@ -517,7 +517,7 @@ } }, "@_fingerprints": {}, - "s_biometrics": null, + "s_biometrics": "Biometria", "l_fingerprint": "Odcisk palca: {label}", "@l_fingerprint": { "placeholders": { @@ -525,13 +525,13 @@ } }, "s_fingerprints": "Odciski palców", - "l_fingerprint_captured": "Odcisk palca zarejestrowany pomyślnie!", - "s_fingerprint_added": "Dodano odcisk palca", - "l_adding_fingerprint_failed": "Błąd dodawania odcisku palca: {message}", + "l_fingerprint_captured": "Odcisk palca został dodany!", + "s_fingerprint_added": "Odcisk palca został dodany", + "l_adding_fingerprint_failed": "Wystąpił błąd podczas dodawania odcisku palca: {message}", "@l_adding_fingerprint_failed": { "placeholders": {} }, - "l_setting_name_failed": "Błąd ustawienia nazwy: {message}", + "l_setting_name_failed": "Wystąpił błąd podczas ustawienia nazwy: {message}", "@l_setting_name_failed": { "placeholders": { "message": {} @@ -543,20 +543,20 @@ "s_delete_fingerprint": "Usuń odcisk palca", "l_delete_fingerprint_desc": "Usuń odcisk palca z klucza YubiKey", "s_fingerprint_deleted": "Odcisk palca został usunięty", - "p_warning_delete_fingerprint": "Spowoduje to usunięcie odcisku palca z twojego YubiKey.", + "p_warning_delete_fingerprint": "Spowoduje to usunięcie odcisku palca z klucza YubiKey.", "s_fingerprints_get_started": "Zacznij korzystać z odcisków palców", "p_set_fingerprints_desc": "Przed możliwością zarejestrowania odcisków palców, należy ustawić PIN.", - "l_no_fps_added": "Nie dodano odcisków palców", + "l_no_fps_added": "Brak dodanych odcisków palców", "s_rename_fp": "Zmień nazwę odcisku palca", - "l_rename_fp_desc": "Zmień etykietę", - "s_fingerprint_renamed": "Zmieniono nazwę odcisku palca", - "l_rename_fp_failed": "Błąd zmiany nazwy: {message}", + "l_rename_fp_desc": "Zmień nazwę", + "s_fingerprint_renamed": "Nazwa odcisku palca została zmieniona", + "l_rename_fp_failed": "Wystąpił błąd podczas zmiany nazwy: {message}", "@l_rename_fp_failed": { "placeholders": { "message": {} } }, - "l_add_one_or_more_fps": "Dodaj jeden lub więcej (do pięciu) odcisków palców", + "l_add_one_or_more_fps": "Dodaj do 5 odcisków palców", "l_fingerprints_used": "Zarejestrowano {used}/5 odcisków palców", "@l_fingerprints_used": { "placeholders": { @@ -564,8 +564,8 @@ } }, "p_press_fingerprint_begin": "Przytrzymaj palec na kluczu YubiKey, aby rozpocząć.", - "p_will_change_label_fp": "Spowoduje to zmianę etykiety odcisku palca.", - "l_name_fingerprint": "Nazwij ten odcisk palca", + "p_will_change_label_fp": "Spowoduje to zmianę nazwę odcisku palca.", + "l_name_fingerprint": "Nazwij odcisk palca", "@_fido_errors": {}, "l_user_action_timeout_error": "Nie powiodło się z powodu braku aktywności użytkownika", @@ -577,32 +577,32 @@ "s_csr": "CSR", "s_subject": "Temat", "l_export_csr_file": "Zapisz CSR do pliku", - "l_export_public_key": null, - "l_export_public_key_file": null, - "l_export_public_key_desc": null, - "l_public_key_exported": null, + "l_export_public_key": "Eksportuj klucz publiczny", + "l_export_public_key_file": "Zapisz klucz publiczny do pliku", + "l_export_public_key_desc": "Eksportuj klucz publiczny do pliku", + "l_public_key_exported": "Klucz publiczny został wyeksportowany", "l_export_certificate": "Eksportuj certyfikat", "l_export_certificate_file": "Eksportuj certyfikat do pliku", - "l_export_certificate_desc": "Pozwala wyeksportować certyfikat do pliku", - "l_certificate_exported": "Wyeksportowano certyfikat", + "l_export_certificate_desc": "Eksportuj certyfikat do pliku", + "l_certificate_exported": "Certyfikat został wyeksportowany", "l_select_import_file": "Wybierz plik do zaimportowania", "l_import_file": "Importuj plik", - "l_import_desc": "Zaimportuj klucz i/lub certyfikat", - "l_import_nothing": null, + "l_import_desc": "Importuj klucz i / lub certyfikat", + "l_import_nothing": "Nic do zaimportowania", "l_importing_file": "Importowanie pliku\u2026", "s_file_imported": "Plik został zaimportowany", - "l_unsupported_key_type": null, + "l_unsupported_key_type": "Typ klucza jest nieobsługiwany", "l_delete_certificate": "Usuń certyfikat", "l_delete_certificate_desc": "Usuń certyfikat z klucza YubiKey", - "l_delete_key": null, - "l_delete_key_desc": null, - "l_delete_certificate_or_key": null, - "l_delete_certificate_or_key_desc": null, - "l_move_key": null, - "l_move_key_desc": null, - "l_change_defaults": null, + "l_delete_key": "Usuń klucz", + "l_delete_key_desc": "Usuń klucz z YubiKey", + "l_delete_certificate_or_key": "Usuń certyfikat / klucz", + "l_delete_certificate_or_key_desc": "Usuń certyfikat lub klucz z YubiKey", + "l_move_key": "Przenieś klucz", + "l_move_key_desc": "Przenieś klucz pomiędzy slotami PIV", + "l_change_defaults": "Zmiana domyślnych kodów dostępu", "s_issuer": "Wydawca", - "s_serial": "Nr. seryjny", + "s_serial": "Numer seryjny", "s_certificate_fingerprint": "Odcisk palca", "s_valid_from": "Ważny od", "s_valid_to": "Ważny do", @@ -616,48 +616,48 @@ "slot": {} } }, - "s_private_key_generated": "Wygenerowano klucz prywatny", - "p_select_what_to_delete": null, - "p_warning_delete_certificate": "Uwaga! Ta czynność spowoduje usunięcie certyfikatu z klucza YubiKey.", - "p_warning_delete_key": null, - "p_warning_delete_certificate_and_key": null, + "s_private_key_generated": "Klucz prywatny został wygeneroewany", + "p_select_what_to_delete": "Wybierz, co usunąć ze slotu.", + "p_warning_delete_certificate": "Ostrzeżenie! Spowoduje to usunięcie certyfikatu z klucza YubiKey.", + "p_warning_delete_key": "Uwaga! Ta czynność spowoduje usunięcie klucza prywatnego z YubiKey.", + "p_warning_delete_certificate_and_key": "Uwaga! Ta czynność spowoduje usunięcie certyfikatu i klucza prywatnego z YubiKey.", "q_delete_certificate_confirm": "Usunąć certyfikat ze slotu PIV {slot}?", "@q_delete_certificate_confirm": { "placeholders": { "slot": {} } }, - "q_delete_key_confirm": null, + "q_delete_key_confirm": "Usunąć klucz prywatny ze slotu PIV {slot}?", "@q_delete_key_confirm": { "placeholders": { "slot": {} } }, - "q_delete_certificate_and_key_confirm": null, + "q_delete_certificate_and_key_confirm": "Usunąć certyfikat i klucz prywatny ze slotu PIV {slot}?", "@q_delete_certificate_and_key_confirm": { "placeholders": { "slot": {} } }, "l_certificate_deleted": "Certyfikat został usunięty", - "l_key_deleted": null, - "l_certificate_and_key_deleted": null, - "l_include_certificate": null, - "l_select_destination_slot": null, - "q_move_key_confirm": null, + "l_key_deleted": "Klucz został usunięty", + "l_certificate_and_key_deleted": "Certyfikat i klucz został usunięty", + "l_include_certificate": "Dołącz certyfikat", + "l_select_destination_slot": "Wybierz docelowy slot", + "q_move_key_confirm": "Przenieść klucz prywatny ze slotu PIV {from_slot}?", "@q_move_key_confirm": { "placeholders": { "from_slot": {} } }, - "q_move_key_to_slot_confirm": null, + "q_move_key_to_slot_confirm": "Przenieść klucz prywatny ze slotu PIV {from_slot} do {to_slot}?", "@q_move_key_to_slot_confirm": { "placeholders": { "from_slot": {}, "to_slot": {} } }, - "q_move_key_and_certificate_to_slot_confirm": null, + "q_move_key_and_certificate_to_slot_confirm": "Przenieść klucz prywatny i certyfikat ze slotu PIV {from_slot} do {to_slot}?", "@q_move_key_and_certificate_to_slot_confirm": { "placeholders": { "from_slot": {}, @@ -671,17 +671,17 @@ "slot": {} } }, - "l_key_moved": null, - "l_key_and_certificate_moved": null, + "l_key_moved": "Klucz został przeniesiony", + "l_key_and_certificate_moved": "Klucz i certyfikat został przeniesiony", "p_subject_desc": "Nazwa wyróżniająca (DN) sformatowana zgodnie ze specyfikacją RFC 4514.", - "l_rfc4514_invalid": "Nieprawidłowy format RFC 4514", + "l_rfc4514_invalid": "Format RFC 4514 jest nieprawidłowy", "rfc4514_examples": "Przykłady:\nCN=Przykładowa Nazwa\nCN=jkowalski,DC=przyklad,DC=pl", - "s_allow_fingerprint": null, + "s_allow_fingerprint": "Zezwalaj na odcisk palca", "p_cert_options_desc": "Algorytm klucza do użycia, format wyjściowy i data wygaśnięcia (tylko certyfikat).", - "p_cert_options_bio_desc": null, - "p_key_options_bio_desc": null, + "p_cert_options_bio_desc": "Używany algorytm klucza, format wyjściowy, data wygaśnięcia (tylko certyfikat) i czy zamiast kodu PIN można użyć danych biometrycznych.", + "p_key_options_bio_desc": "Umożliwienie korzystania z danych biometrycznych zamiast kodu PIN.", "s_overwrite_slot": "Nadpisz slot", - "p_overwrite_slot_desc": "Spowoduje to trwałe nadpisanie istniejącej zawartości w slocie {slot}.", + "p_overwrite_slot_desc": "Spowoduje to trwałe nadpisanie obecnej zawartości w slocie {slot}.", "@p_overwrite_slot_desc": { "placeholders": { "slot": {} @@ -689,7 +689,7 @@ }, "l_overwrite_cert": "Certyfikat zostanie nadpisany", "l_overwrite_key": "Klucz prywatny zostanie nadpisany", - "l_overwrite_key_maybe": "Każdy istniejący klucz prywatny w slocie zostanie nadpisany", + "l_overwrite_key_maybe": "Każdy obecny klucz prywatny w slocie zostanie nadpisany", "@_piv_slots": {}, "s_slot_display_name": "{name} ({hexid})", @@ -700,10 +700,10 @@ } }, "s_slot_9a": "Uwierzytelnienie", - "s_slot_9c": "Cyfrowy podpis", - "s_slot_9d": "Menedżer kluczy", - "s_slot_9e": "Autoryzacja karty", - "s_retired_slot": null, + "s_slot_9c": "Podpis cyfrowy", + "s_slot_9d": "Zarządzanie kluczem", + "s_slot_9e": "Uwierzytelnianie kartą", + "s_retired_slot": "Emerytowane kluczowe kierownictwo", "@_otp_slots": {}, "s_otp_slot_one": "Krótkie dotknięcie", @@ -713,21 +713,21 @@ "@_otp_slot_configurations": {}, "l_yubiotp_desc": "Zaprogramuj poświadczenie Yubico OTP", - "s_challenge_response": "Wyzwanie i odpowiedź", - "l_challenge_response_desc": "Zaprogramuj poświadczenie typu challenge-response", + "s_challenge_response": "Challenge-response", + "l_challenge_response_desc": "Zaprogramuj poświadczenie challenge-response", "s_static_password": "Hasło statyczne", "l_static_password_desc": "Skonfiguruj hasło statyczne", "s_hotp": "OATH-HOTP", - "l_hotp_desc": "Zaprogramuj poświadczenie oparte na HMAC-SHA1", - "s_public_id": "Publiczne ID", - "s_private_id": "Prywatne ID", + "l_hotp_desc": "Zaprogramuj poświadczenie HMAC-SHA1", + "s_public_id": "Public ID", + "s_private_id": "Private ID", "s_use_serial": "Użyj numeru seryjnego", "l_select_file": "Wybierz plik", "l_no_export_file": "Brak pliku eksportu", "s_no_export": "Brak eksportu", "s_export": "Eksportuj", "l_export_configuration_file": "Eksportuj konfigurację do pliku", - "l_exported_can_be_uploaded_at": null, + "l_exported_can_be_uploaded_at": "Wyeksportowane poświadczenia może zostać przesłane na stronie {url}", "@_export_can_be_uploaded_at": { "placeholders": { "url": {} @@ -737,25 +737,25 @@ "@_otp_slot_actions": {}, "s_delete_slot": "Usuń poświadczenie", "l_delete_slot_desc": "Usuń poświadczenie ze slotu", - "p_warning_delete_slot_configuration": "Ostrzeżenie! Ta akcja spowoduje trwałe usunięcie poświadczenia ze slotu {slot_id}.", + "p_warning_delete_slot_configuration": "Ostrzeżenie! Spowoduje to trwałe usunięcie poświadczenia ze slotu {slot_id}.", "@p_warning_delete_slot_configuration": { "placeholders": { "slot_id": {} } }, "l_slot_deleted": "Poświadczenie zostało usunięte", - "s_swap": "Zamień", - "s_swap_slots": "Zamiana slotów", - "l_swap_slots_desc": "Zamień krótkie/długie dotknięcie", - "p_swap_slots_desc": "Spowoduje to zamianę konfiguracji dwóch gniazd.", - "l_slots_swapped": "Konfiguracje gniazd zostały zamienione", - "l_slot_credential_configured": "Skonfigurowano poświadczenie {type}", + "s_swap": "Zmień", + "s_swap_slots": "Zmień sloty", + "l_swap_slots_desc": "Zmień krótkie / długie dotknięcie", + "p_swap_slots_desc": "Spowoduje to zmianę miejsc slotów.", + "l_slots_swapped": "Sloty zostały zmienione", + "l_slot_credential_configured": "Poświadczenie {type} zostało skonfigurowane", "@l_slot_credential_configured": { "placeholders": { "type": {} } }, - "l_slot_credential_configured_and_exported": "Skonfigurowano poświadczenie {type} i wyeksportowano do {file}", + "l_slot_credential_configured_and_exported": "Poświadczenie {type} zostało skonfigurowane i wyeksportowane do pliku {file}", "@l_slot_credential_configured_and_exported": { "placeholders": { "type": {}, @@ -763,17 +763,17 @@ } }, "s_append_enter": "Dołącz ⏎", - "l_append_enter_desc": "Dołącz naciśnięcie klawisza Enter po wysłaniu OTP", + "l_append_enter_desc": "Dołącz klawisz Enter po wysłaniu kodu OTP", "@_otp_errors": {}, - "p_otp_swap_error": null, - "l_wrong_access_code": null, + "p_otp_swap_error": "Nie udało się zamienić gniazd! Upewnij się, że klucz YubiKey nie ma ograniczonego dostępu.", + "l_wrong_access_code": "Kod dostępu jest nieprawidłowy", "@_otp_access_code": {}, - "s_access_code": null, - "s_show_access_code": null, - "s_hide_access_code": null, - "p_enter_access_code": null, + "s_access_code": "Kod dostępu", + "s_show_access_code": "Pokaż kod dostępu", + "s_hide_access_code": "Ukryj kod dostępu", + "p_enter_access_code": "Wpisz kod dostępu slotu {slot}.", "@p_enter_access_code": { "placeholders": { "slot": {} @@ -783,28 +783,28 @@ "@_permissions": {}, "s_enable_nfc": "Włącz NFC", - "s_request_access": null, + "s_request_access": "Żądanie dostępu", "s_permission_denied": "Odmowa dostępu", "l_elevating_permissions": "Podnoszenie uprawnień\u2026", "s_review_permissions": "Przegląd uprawnień", - "s_open_windows_settings": null, - "l_admin_privileges_required": "Wymagane uprawnienia administratora", + "s_open_windows_settings": "Otwórz ustawienia systemu Windows", + "l_admin_privileges_required": "Uprawnienia administratora są wymagane", "p_elevated_permissions_required": "Zarządzanie tym urządzeniem wymaga podwyższonych uprawnień.", "p_webauthn_elevated_permissions_required": "Zarządzanie WebAuthn wymaga podwyższonych uprawnień.", - "l_ms_store_permission_note": null, + "l_ms_store_permission_note": "Wersja aplikacji ze sklepu Microsoft Store może nie być w stanie podnieść uprawnień", "p_need_camera_permission": "Yubico Authenticator wymaga dostępu do aparatu w celu skanowania kodów QR.", "@_qr_codes": {}, - "s_qr_scan": "Skanuj kod QR", - "l_invalid_qr": "Nieprawidłowy kod QR", + "s_qr_scan": "Zeskanuj kod QR", + "l_invalid_qr": "Kod QR jest nieprawidłowy", "l_qr_not_found": "Nie znaleziono kodu QR", - "l_qr_file_too_large": "Zbyt duży plik (maks. {max})", + "l_qr_file_too_large": "Plik jest zbyt duży (maksymalnie {max})", "@l_qr_file_too_large": { "placeholders": { "max": {} } }, - "l_qr_invalid_image_file": "Nieprawidłowy plik obrazu", + "l_qr_invalid_image_file": "Plik obrazu jest nieprawidłowy", "l_qr_select_file": "Wybierz plik z kodem QR", "l_qr_not_read": "Odczytanie kodu QR nie powiodło się: {message}", "@l_qr_not_read": { @@ -815,28 +815,28 @@ "l_point_camera_scan": "Skieruj aparat na kod QR, by go zeskanować", "q_want_to_scan": "Czy chcesz zeskanować?", "q_no_qr": "Nie masz kodu QR?", - "s_enter_manually": "Wprowadź ręcznie", + "s_enter_manually": "Wpisz ręcznie", "s_read_from_file": "Odczytaj z pliku", "@_factory_reset": {}, "s_reset": "Zresetuj", "s_factory_reset": "Ustawienia fabryczne", - "l_factory_reset_desc": null, - "l_factory_reset_required": null, - "l_oath_application_reset": "Reset funkcji OATH", - "l_fido_app_reset": "Reset funkcji FIDO", - "l_reset_failed": "Błąd podczas resetowania: {message}", + "l_factory_reset_desc": "Przywróć ustawienia domyślne klucza YubiKey", + "l_factory_reset_required": "Wymagane przywrócenie ustawień fabrycznych", + "l_oath_application_reset": "Resetowanie aplikacji OATH", + "l_fido_app_reset": "Resetowanie aplikacji FIDO", + "l_reset_failed": "Wystąpił błąd podczas resetowania: {message}", "@l_reset_failed": { "placeholders": { "message": {} } }, - "l_piv_app_reset": "Funkcja PIV została zresetowana", - "p_factory_reset_an_app": "Zresetuj funkcję na YubiKey do ustawień fabrycznych.", + "l_piv_app_reset": "Resetowanie aplikacji PIV", + "p_factory_reset_an_app": "Zresetuj aplikację klucza YubiKey do ustawień fabrycznych.", "p_factory_reset_desc": "Dane są przechowywane w wielu funkcjach YubiKey. Niektóre z tych funkcji można zresetować niezależnie od siebie.\n\nWybierz funkcję powyżej, aby ją zresetować.", - "p_warning_factory_reset": "Uwaga! Spowoduje to nieodwracalne usunięcie wszystkich kont OATH TOTP/HOTP z klucza YubiKey.", + "p_warning_factory_reset": "Ostrzeżenie! Spowoduje to nieodwracalne usunięcie wszystkich kont OATH TOTP/HOTP z klucza YubiKey.", "p_warning_disable_credentials": "Twoje poświadczenia OATH, jak również wszelkie ustawione hasła, zostaną usunięte z tego klucza YubiKey. Upewnij się, że najpierw wyłączono je w odpowiednich witrynach internetowych, aby uniknąć zablokowania kont.", - "p_warning_deletes_accounts": "Uwaga! Spowoduje to nieodwracalne usunięcie wszystkich kont U2F i FIDO2 z klucza YubiKey.", + "p_warning_deletes_accounts": "Ostrzeżenie! Spowoduje to nieodwracalne usunięcie wszystkich kont U2F i FIDO2 z klucza YubiKey.", "p_warning_disable_accounts": "Twoje poświadczenia, a także wszelkie ustawione kody PIN, zostaną usunięte z tego klucza YubiKey. Upewnij się, że najpierw wyłączono je w odpowiednich witrynach internetowych, aby uniknąć zablokowania kont.", "p_warning_piv_reset": "Ostrzeżenie! Wszystkie dane przechowywane dla PIV zostaną nieodwracalnie usunięte z klucza YubiKey.", "p_warning_piv_reset_desc": "Obejmuje to klucze prywatne i certyfikaty. Kod PIN, PUK i klucz zarządzania zostaną zresetowane do domyślnych wartości fabrycznych.", @@ -844,12 +844,12 @@ "p_warning_global_reset_desc": "Zresetuj funkcje na swoim YubiKey. PIN zostanie zresetowany do wartości domyślnej i zarejestrowane odciski palców zostaną usunięte. Wszelkie klucze, certyfikaty czy inne dane uwierzytelniające zostaną trwale usunięte.", "@_copy_to_clipboard": {}, - "l_copy_to_clipboard": "Skopiuj do schowka", - "s_code_copied": "Kod skopiowany", - "l_code_copied_clipboard": "Kod skopiowany do schowka", + "l_copy_to_clipboard": "Kopiuj do schowka", + "s_code_copied": "Kod został skopiowany", + "l_code_copied_clipboard": "Kod został skopiowany do schowka", "s_copy_log": "Kopiuj logi", - "l_log_copied": "Logi skopiowane do schowka", - "l_diagnostics_copied": "Dane diagnostyczne skopiowane do schowka", + "l_log_copied": "Logi zostały skopiowane do schowka", + "l_diagnostics_copied": "Dane diagnostyczne zostały skopiowane do schowka", "p_target_copied_clipboard": "{label} skopiowano do schowka.", "@p_target_copied_clipboard": { "placeholders": { @@ -860,87 +860,87 @@ "@_custom_icons": {}, "s_custom_icons": "Niestandardowe ikony", "l_set_icons_for_accounts": "Ustaw ikony dla kont", - "p_custom_icons_description": "Pakiety ikon mogą sprawić, że Twoje konta będą łatwiejsze do odróżnienia dzięki znanym logo i kolorom.", - "s_replace_icon_pack": "Zastąp pakiet ikon", - "l_loading_icon_pack": "Wczytywanie pakietu ikon\u2026", - "s_load_icon_pack": "Wczytaj pakiet ikon", + "p_custom_icons_description": "Pakiety ikon mogą sprawić, że Twoje konta będą łatwiejsze do rozróżnienia dzięki znanym logo i kolorom.", + "s_replace_icon_pack": "Zmień pakiet ikon", + "l_loading_icon_pack": "Ładowanie pakietu ikon\u2026", + "s_load_icon_pack": "Wybierz pakiet ikon", "s_remove_icon_pack": "Usuń pakiet ikon", - "l_icon_pack_removed": "Usunięto pakiet ikon", - "l_remove_icon_pack_failed": "Błąd podczas usuwania pakietu ikon", + "l_icon_pack_removed": "Pakiet ikon został usunięty", + "l_remove_icon_pack_failed": "Wystąpił błąd podczas usuwania pakietu ikon", "s_choose_icon_pack": "Wybierz pakiet ikon", - "l_icon_pack_imported": "Zaimportowano pakiet ikon", - "l_import_icon_pack_failed": "Błąd importu pakietu ikon: {message}", + "l_icon_pack_imported": "Pakiet ikon został zaimportowany", + "l_import_icon_pack_failed": "Wystąpił błąd podczas importowania pakietu ikon: {message}", "@l_import_icon_pack_failed": { "placeholders": { "message": {} } }, - "l_invalid_icon_pack": "Nieprawidłowy pakiet ikon", - "l_icon_pack_copy_failed": "Nie udało się skopiować plików z pakietu ikon", + "l_invalid_icon_pack": "Pakiet ikon jest nieprawidłowy", + "l_icon_pack_copy_failed": "Nie udało się skopiować plików pakietu ikon", "@_android_settings": {}, "s_nfc_options": "Opcje NFC", - "l_on_yk_nfc_tap": "Podczas kontaktu YubiKey z NFC", + "l_on_yk_nfc_tap": "Podczas zbliżenia klucza YubiKey przez NFC", "l_do_nothing": "Nic nie rób", "l_launch_ya": "Uruchom Yubico Authenticator", - "l_copy_otp_clipboard": "Skopiuj OTP do schowka", - "l_launch_and_copy_otp": "Uruchom aplikację i skopiuj OTP", + "l_copy_otp_clipboard": "Kopiuj kod OTP do schowka", + "l_launch_and_copy_otp": "Uruchom aplikację i kopiuj kod OTP", "l_kbd_layout_for_static": "Układ klawiatury (dla hasła statycznego)", "s_choose_kbd_layout": "Wybierz układ klawiatury", - "l_bypass_touch_requirement": "Obejdź wymóg dotknięcia", - "l_bypass_touch_requirement_on": "Konta, które wymagają dotknięcia, są automatycznie wyświetlane przez NFC", - "l_bypass_touch_requirement_off": "Konta, które wymagają dotknięcia, potrzebują dodatkowego przyłożenia do NFC", - "s_silence_nfc_sounds": "Dźwięk NFC", - "l_silence_nfc_sounds_on": "Nie będzie odtwarzany", - "l_silence_nfc_sounds_off": "Będzie odtwarzany", + "l_bypass_touch_requirement": "Pomiń wymagane dotknięcie", + "l_bypass_touch_requirement_on": "Konta, które wymagają dotknięcia będą automatycznie wyświetlane przez NFC", + "l_bypass_touch_requirement_off": "Konta, które wymagają dotknięcia będą wymagały dodatkowego zbliżenia przez NFC", + "s_silence_nfc_sounds": "Wycisz dźwięki NFC", + "l_silence_nfc_sounds_on": "Dźwięk zbliżenia NFC nie będzie odtwarzany", + "l_silence_nfc_sounds_off": "Dźwięk zbliżenia NFC będzie odtwarzany", "s_usb_options": "Opcje USB", - "l_launch_app_on_usb": "Uruchom po podłączeniu YubiKey", - "l_launch_app_on_usb_on": "Inne aplikacje nie mogą korzystać z YubiKey przez USB", - "l_launch_app_on_usb_off": "Inne aplikacje mogą korzystać z YubiKey przez USB", + "l_launch_app_on_usb": "Uruchom po podłączeniu klucza YubiKey", + "l_launch_app_on_usb_on": "Inne aplikacje mogą korzystać z klucza YubiKey przez USB", + "l_launch_app_on_usb_off": "Inne aplikacje mogą korzystać z klucza YubiKey przez USB", "s_allow_screenshots": "Zezwalaj na zrzuty ekranu", - "l_nfc_dialog_tap_key": null, - "s_nfc_dialog_operation_success": "Powodzenie", + "l_nfc_dialog_tap_key": "Zbliż i przytrzymaj klucz", + "s_nfc_dialog_operation_success": "Udało się", "s_nfc_dialog_operation_failed": "Niepowodzenie", - "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_reset": "Działanie: zresetowanie aplikacji OATH", + "s_nfc_dialog_oath_unlock": "Działanie: odblokowanie aplikacji OATH", + "s_nfc_dialog_oath_set_password": "Działanie: ustawienie hasła OATH", + "s_nfc_dialog_oath_unset_password": "Działanie: usunięcie hasła OATH", + "s_nfc_dialog_oath_add_account": "Działanie: dodanie nowego konta", + "s_nfc_dialog_oath_rename_account": "Działanie: zmiana nazwy konta", + "s_nfc_dialog_oath_delete_account": "Działanie: usunięcie konta", + "s_nfc_dialog_oath_calculate_code": "Działanie: obliczenie kodu 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_add_multiple_accounts": "Działanie: dodanie wielu kont", - "s_nfc_dialog_fido_reset": null, - "s_nfc_dialog_fido_unlock": null, - "l_nfc_dialog_fido_set_pin": 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_reset": "Działanie: zresetowanie aplikacji FIDO", + "s_nfc_dialog_fido_unlock": "Działanie: odblokowanie aplikacji FIDO", + "l_nfc_dialog_fido_set_pin": "Działanie: ustawienie lub zmiana kodu PIN FIDO", + "s_nfc_dialog_fido_delete_credential": "Działanie: usunięcie klucza dostępu", + "s_nfc_dialog_fido_delete_fingerprint": "Działanie: usunięcie odcisku palca", + "s_nfc_dialog_fido_rename_fingerprint": "Działanie: zmiana nazwy odcisku palca", + "s_nfc_dialog_fido_failure": "Operacja FIDO nie powiodła się", "@_nfc": {}, - "s_nfc_ready_to_scan": null, - "s_nfc_hold_still": null, - "s_nfc_tap_your_yubikey": null, - "l_nfc_failed_to_scan": null, + "s_nfc_ready_to_scan": "Gotowy do skanowania", + "s_nfc_hold_still": "Nie ruszaj się\u2026", + "s_nfc_tap_your_yubikey": "Dotknij przycisku YubiKey", + "l_nfc_failed_to_scan": "Skanowanie nie powiodło się, spróbuj ponownie", "@_ndef": {}, - "p_ndef_set_otp": "OTP zostało skopiowane do schowka.", - "p_ndef_set_password": "Hasło statyczne zostało skopiowane do schowka.", - "p_ndef_parse_failure": "Błąd czytania OTP z YubiKey.", - "p_ndef_set_clip_failure": "Błąd kopiowania OTP do schowka.", + "p_ndef_set_otp": "Kod OTP został skopiowany do schowka.", + "p_ndef_set_password": "Hasło zostało skopiowane do schowka.", + "p_ndef_parse_failure": "Nie udało się odczytać kodu OTP z klucza YubiKey.", + "p_ndef_set_clip_failure": "Wystąpił błąd podczas kopiowania kodu OTP do schowka.", "@_key_customization": {}, - "s_set_label": null, - "s_set_color": null, - "s_change_label": null, - "s_color": null, - "p_set_will_add_custom_name": null, - "p_rename_will_change_custom_name": null, + "s_set_label": "Ustaw etykietę", + "s_set_color": "Ustaw kolor", + "s_change_label": "Zmień etykietę", + "s_color": "Kolor", + "p_set_will_add_custom_name": "Spowoduje to nazwanie klucza YubiKey.", + "p_rename_will_change_custom_name": "Spowoduje to zmianę etykiety klucza YubiKey.", "@_eof": {} } diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index d64b6cffc..4697ab510 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -38,7 +38,7 @@ "s_calculate": "Tính toán", "s_import": "Nhập khẩu", "s_overwrite": "Ghi đè", - "s_done": null, + "s_done": "Xong", "s_label": "Nhãn", "s_name": "Tên", "s_usb": "USB", @@ -47,11 +47,11 @@ "s_details": "Chi tiết", "s_show_window": "Hiển thị cửa sổ", "s_hide_window": "Ẩn cửa sổ", - "s_show_navigation": null, + "s_show_navigation": "Hiển thị điều hướng", "s_expand_navigation": "Mở rộng điều hướng", "s_collapse_navigation": "Thu gọn điều hướng", - "s_show_menu": null, - "s_hide_menu": null, + "s_show_menu": "Hiển thị thực đơn", + "s_hide_menu": "Ẩn thực đơn", "q_rename_target": "Đổi tên {label}?", "@q_rename_target": { "placeholders": { @@ -237,8 +237,8 @@ "s_unsupported_yk": "YubiKey không được hỗ trợ", "s_yk_not_recognized": "Thiết bị không được nhận diện", "p_operation_failed_try_again": "Thao tác không thành công, vui lòng thử lại.", - "l_configuration_unsupported": null, - "p_scp_unsupported": null, + "l_configuration_unsupported": "Cấu hình không được hỗ trợ", + "p_scp_unsupported": "Để giao tiếp qua NFC, YubiKey yêu cầu công nghệ không được điện thoại này hỗ trợ. Vui lòng cắm YubiKey vào cổng USB của điện thoại.", "@_general_errors": {}, "l_error_occurred": "Đã xảy ra lỗi", @@ -430,10 +430,10 @@ "message": {} } }, - "l_add_account_password_required": null, - "l_add_account_unlock_required": null, - "l_add_account_already_exists": null, - "l_add_account_func_missing": null, + "l_add_account_password_required": "Yêu cầu mật khẩu", + "l_add_account_unlock_required": "Yêu cầu mở khóa", + "l_add_account_already_exists": "Tài khoản đã tồn tại", + "l_add_account_func_missing": "Chức năng bị thiếu hoặc bị vô hiệu hóa", "l_account_name_required": "Tài khoản của bạn phải có tên", "l_name_already_exists": "Tên này đã tồn tại cho nhà phát hành", "l_account_already_exists": "Tài khoản này đã tồn tại trên YubiKey", @@ -447,7 +447,7 @@ "s_rename_account": "Đổi tên tài khoản", "l_rename_account_desc": "Chỉnh sửa nhà phát hành/tên của tài khoản", "s_account_renamed": "Tài khoản đã được đổi tên", - "l_rename_account_failed": null, + "l_rename_account_failed": "Đổi tên tài khoản không thành công: {message}", "@l_rename_account_failed": { "placeholders": { "message": {} @@ -600,7 +600,7 @@ "l_delete_certificate_or_key_desc": "Xóa chứng chỉ hoặc khóa khỏi YubiKey", "l_move_key": "Di chuyển khóa", "l_move_key_desc": "Di chuyển một khóa từ khe PIV này sang khe khác", - "l_change_defaults": null, + "l_change_defaults": "Thay đổi mã truy cập mặc định", "s_issuer": "Nhà phát hành", "s_serial": "Số serial", "s_certificate_fingerprint": "Dấu vân tay", @@ -923,10 +923,10 @@ "s_nfc_dialog_fido_failure": "Hành động FIDO thất bại", "@_nfc": {}, - "s_nfc_ready_to_scan": null, - "s_nfc_hold_still": null, - "s_nfc_tap_your_yubikey": null, - "l_nfc_failed_to_scan": null, + "s_nfc_ready_to_scan": "Sẵn sàng để quét", + "s_nfc_hold_still": "Giữ yên\u2026", + "s_nfc_tap_your_yubikey": "Nhấn vào YubiKey của bạn", + "l_nfc_failed_to_scan": "Không quét được, hãy thử lại", "@_ndef": {}, "p_ndef_set_otp": "Đã sao chép mã OTP từ YubiKey vào clipboard.", From 827c95f72b67e10fab4634c5dcc95e863681b087 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 10 Sep 2024 19:31:06 +0200 Subject: [PATCH 47/71] 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 d422706fa..bc81e92ed 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 b1d648c16..8f6908ae7 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 9b540cf85..e5b1036b6 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 4d088453e..b2e18721f 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 48/71] 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 bf9032c37..364166705 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 49/71] 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 e5b1036b6..c8073b38b 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 364166705..2b47e8927 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 b2e18721f..39176e49b 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 50/71] 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 39176e49b..c94e435a3 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 51/71] 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 1a71812cd..f45f56da1 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 52/71] 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 50660bd63..c4f29d539 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 162cd713a..68353509d 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 5d8d39c23..6504d000d 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 f5d5d4e1e..cd6995d5d 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. From 56702f981b181f223535e8ec0e9be3dc59219aa0 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 11 Sep 2024 12:38:54 +0200 Subject: [PATCH 53/71] Fix minor helper issues --- helper/helper/base.py | 3 ++- helper/helper/device.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/helper/helper/base.py b/helper/helper/base.py index 622273506..7daefd27d 100644 --- a/helper/helper/base.py +++ b/helper/helper/base.py @@ -132,6 +132,8 @@ def __call__(self, action, target, params, event, signal, traversed=None): response = self.get_child(action)( "get", [], params, event, signal, traversed ) + else: + raise NoSuchActionException(action) if isinstance(response, RpcResponse): return response @@ -143,7 +145,6 @@ def __call__(self, action, target, params, event, signal, traversed=None): raise # Prevent catching this as a ValueError below except ValueError as e: raise InvalidParametersException(e) - raise NoSuchActionException(action) def close(self): if self._child: diff --git a/helper/helper/device.py b/helper/helper/device.py index a2db4192e..c85101034 100644 --- a/helper/helper/device.py +++ b/helper/helper/device.py @@ -278,7 +278,7 @@ def __call__(self, *args, **kwargs): return response except (SmartcardException, OSError): - logger.exception("Device error") + logger.exception("Device error", exc_info=True) self._child = None name = self._child_name From accbb3a3ee7331965ce7c89c558744be65d434f9 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 11 Sep 2024 12:39:22 +0200 Subject: [PATCH 54/71] Only refresh OATH while page is active --- lib/desktop/oath/state.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/desktop/oath/state.dart b/lib/desktop/oath/state.dart index cdd133f7f..c04346135 100755 --- a/lib/desktop/oath/state.dart +++ b/lib/desktop/oath/state.dart @@ -196,8 +196,11 @@ final desktopOathCredentialListProvider = StateNotifierProvider.autoDispose .select((r) => r.whenOrNull(data: (state) => state.locked) ?? true)), ); ref.listen(windowStateProvider, (_, windowState) { - notifier._notifyWindowState(windowState); + notifier._rescheduleTimer(windowState.active); }, fireImmediately: true); + ref.listen(currentSectionProvider, (_, section) { + notifier._rescheduleTimer(section == Section.accounts); + }); return notifier; }, @@ -231,9 +234,9 @@ class DesktopCredentialListNotifier extends OathCredentialListNotifier { DesktopCredentialListNotifier(this._withContext, this._session, this._locked) : super(); - void _notifyWindowState(WindowState windowState) { + void _rescheduleTimer(bool active) { if (_locked) return; - if (windowState.active) { + if (active) { _scheduleRefresh(); } else { _timer?.cancel(); From 9834672f67169df7404e6e37f8c8e41d89d01670 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 11 Sep 2024 15:08:25 +0200 Subject: [PATCH 55/71] Helper: Don't return closed child --- helper/helper/base.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/helper/helper/base.py b/helper/helper/base.py index 7daefd27d..a8bfaaf45 100644 --- a/helper/helper/base.py +++ b/helper/helper/base.py @@ -114,6 +114,7 @@ def child(func=None, *, condition=None): class RpcNode: def __init__(self): + self._closed = False self._child = None self._child_name = None @@ -147,9 +148,14 @@ def __call__(self, action, target, params, event, signal, traversed=None): raise InvalidParametersException(e) def close(self): + self._closed = True if self._child: self._close_child() + @property + def closed(self): + return self._closed + def get_data(self): return dict() @@ -209,7 +215,7 @@ def get_child(self, name): if self._child and self._child_name != name: self._close_child() - if not self._child: + if not self._child or self._child.closed: self._child = self.create_child(name) self._child_name = name logger.debug("created child: %s", name) From bb00b2c65e3be580de569ab36a51638364cb38bc Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 11 Sep 2024 15:24:44 +0200 Subject: [PATCH 56/71] CTAP: Handle expired PIN token --- helper/helper/fido.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/helper/helper/fido.py b/helper/helper/fido.py index 181cac426..fff7bac5d 100644 --- a/helper/helper/fido.py +++ b/helper/helper/fido.py @@ -91,6 +91,14 @@ def __init__(self, connection): self.client_pin = ClientPin(self.ctap) self._token = None + def __call__(self, *args, **kwargs): + try: + return super().__call__(*args, **kwargs) + except CtapError as e: + if e.code == CtapError.ERR.PIN_AUTH_INVALID: + raise AuthRequiredException() + raise + def get_data(self): self._info = self.ctap.get_info() logger.debug(f"Info: {self._info}") From f38e5a5f4682e737491caf2aa675f85b2ecbc8e2 Mon Sep 17 00:00:00 2001 From: Elias Bonnici Date: Wed, 11 Sep 2024 15:53:16 +0200 Subject: [PATCH 57/71] Default to home when section is not enabled --- lib/app/views/main_page.dart | 38 ------------------------------------ lib/desktop/state.dart | 11 +++-------- 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 - 8 files changed, 3 insertions(+), 52 deletions(-) diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index c94e435a3..dbe5ac90d 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -27,16 +27,13 @@ import '../../fido/views/passkeys_screen.dart'; import '../../fido/views/webauthn_page.dart'; import '../../home/views/home_message_page.dart'; import '../../home/views/home_screen.dart'; -import '../../management/views/management_screen.dart'; import '../../oath/views/oath_screen.dart'; import '../../oath/views/utils.dart'; import '../../otp/views/otp_screen.dart'; import '../../piv/views/piv_screen.dart'; -import '../message.dart'; import '../models.dart'; import '../state.dart'; import 'device_error_screen.dart'; -import 'message_page.dart'; class MainPage extends ConsumerWidget { const MainPage({super.key}); @@ -131,41 +128,6 @@ class MainPage extends ConsumerWidget { return ref.watch(currentDeviceDataProvider).when( data: (data) { final section = ref.watch(currentSectionProvider); - final capabilities = section.capabilities; - if (section.getAvailability(data) == Availability.unsupported) { - return MessagePage( - title: section.getDisplayName(l10n), - capabilities: capabilities, - header: l10n.s_app_not_supported, - message: l10n.l_app_not_supported_on_yk(capabilities - .map((c) => c.getDisplayName(l10n)) - .join(',')), - ); - } else if (section.getAvailability(data) != - Availability.enabled) { - return MessagePage( - title: section.getDisplayName(l10n), - capabilities: capabilities, - header: l10n.s_app_disabled, - message: l10n.l_app_disabled_desc(capabilities - .map((c) => c.getDisplayName(l10n)) - .join(',')), - actionsBuilder: (context, expanded) => [ - ActionChip( - label: Text(data.info.version.major > 4 - ? l10n.s_toggle_applications - : l10n.s_toggle_interfaces), - onPressed: () async { - await showBlurDialog( - context: context, - builder: (context) => ManagementScreen(data), - ); - }, - avatar: const Icon(Symbols.construction), - ) - ], - ); - } return switch (section) { Section.home => HomeScreen(data), diff --git a/lib/desktop/state.dart b/lib/desktop/state.dart index a0de9c641..3a383b7aa 100755 --- a/lib/desktop/state.dart +++ b/lib/desktop/state.dart @@ -261,15 +261,10 @@ class DesktopCurrentSectionNotifier extends CurrentSectionNotifier { state.getAvailability(data) != Availability.enabled) { state = Section.passkeys; } - if (state.getAvailability(data) != Availability.unsupported) { - // Keep current app - return; + if (state.getAvailability(data) != Availability.enabled) { + // Default to home if app is not enabled + state = Section.home; } - - state = _supportedSections.firstWhere( - (app) => app.getAvailability(data) == Availability.enabled, - orElse: () => _supportedSections.first, - ); } static Section _fromName(String? name, List
supportedSections) => diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 58eef40ba..1e6bbce21 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -203,7 +203,6 @@ "app": {} } }, - "s_app_disabled": "Anwendung deaktiviert", "l_app_disabled_desc": "Aktivieren Sie die Anwendung '{app}' auf Ihrem YubiKey für Zugriff", "@l_app_disabled_desc": { "placeholders": { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2f4efa75d..e403ea671 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -203,7 +203,6 @@ "app": {} } }, - "s_app_disabled": "Application disabled", "l_app_disabled_desc": "Enable the '{app}' application on your YubiKey to access", "@l_app_disabled_desc": { "placeholders": { diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index b4cf1b092..d1f0300c3 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -203,7 +203,6 @@ "app": {} } }, - "s_app_disabled": "Application désactivée", "l_app_disabled_desc": "Activez l'application {app} sur votre YubiKey pour y accéder", "@l_app_disabled_desc": { "placeholders": { diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 2eb46bacf..a7d59214a 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -203,7 +203,6 @@ "app": {} } }, - "s_app_disabled": "アプリケーションが無効です", "l_app_disabled_desc": "アクセスするにはYubiKeyで{app}アプリケーションを有効にしてください", "@l_app_disabled_desc": { "placeholders": { diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index b829bdaea..3855cbf30 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -203,7 +203,6 @@ "app": {} } }, - "s_app_disabled": "Aplikacja została wyłączona", "l_app_disabled_desc": "Włącz aplikację '{app}' w kluczu YubiKey, aby uzyskać do niej dostęp", "@l_app_disabled_desc": { "placeholders": { diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index 3c16a0102..7c5c2b616 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -203,7 +203,6 @@ "app": {} } }, - "s_app_disabled": "Ứng dụng đã bị tắt", "l_app_disabled_desc": "Bật ứng dụng '{app}' trên YubiKey của bạn để truy cập", "@l_app_disabled_desc": { "placeholders": { From ba7dac792367a458ffaaa819f612e25f58c5fd68 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Thu, 12 Sep 2024 10:11:25 +0200 Subject: [PATCH 58/71] Remove broken caching, refresh OATH state on capability change --- helper/helper/base.py | 1 + helper/helper/device.py | 14 +++++++------- lib/desktop/oath/state.dart | 13 +++++++++++-- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/helper/helper/base.py b/helper/helper/base.py index a8bfaaf45..c894b10ff 100644 --- a/helper/helper/base.py +++ b/helper/helper/base.py @@ -148,6 +148,7 @@ def __call__(self, action, target, params, event, signal, traversed=None): raise InvalidParametersException(e) def close(self): + logger.debug(f"Closing node {self}") self._closed = True if self._child: self._close_child() diff --git a/helper/helper/device.py b/helper/helper/device.py index c85101034..e059c03dd 100644 --- a/helper/helper/device.py +++ b/helper/helper/device.py @@ -168,7 +168,7 @@ def list_children(self): return self._readers def create_child(self, name): - return ReaderDeviceNode(self._reader_mapping[name], None) + return ReaderDeviceNode(self._reader_mapping[name]) class _ScanDevices: @@ -238,7 +238,7 @@ def list_children(self): dev_id = str(info.serial) else: dev_id = _id_from_fingerprint(dev.fingerprint) - self._device_mapping[dev_id] = (dev, info) + self._device_mapping[dev_id] = dev name = get_name(info, dev.pid.yubikey_type if dev.pid else None) self._devices[dev_id] = dict(pid=dev.pid, name=name, serial=info.serial) @@ -255,16 +255,16 @@ def create_child(self, name): if name not in self._device_mapping and self._list_state == 0: self.list_children() try: - return UsbDeviceNode(*self._device_mapping[name]) + return UsbDeviceNode(self._device_mapping[name]) except KeyError: raise NoSuchNodeException(name) class AbstractDeviceNode(RpcNode): - def __init__(self, device, info): + def __init__(self, device): super().__init__() self._device = device - self._info = info + self._info = None self._data = None def __call__(self, *args, **kwargs): @@ -383,8 +383,8 @@ def update(self, observable, actions): class ReaderDeviceNode(AbstractDeviceNode): - def __init__(self, device, info): - super().__init__(device, info) + def __init__(self, device): + super().__init__(device) self._observer = _ReaderObserver(device) self._monitor = CardMonitor() self._monitor.addObserver(self._observer) diff --git a/lib/desktop/oath/state.dart b/lib/desktop/oath/state.dart index c04346135..f15dedf5b 100755 --- a/lib/desktop/oath/state.dart +++ b/lib/desktop/oath/state.dart @@ -28,6 +28,7 @@ import '../../app/logging.dart'; import '../../app/models.dart'; import '../../app/state.dart'; import '../../app/views/user_interaction.dart'; +import '../../management/models.dart'; import '../../oath/models.dart'; import '../../oath/state.dart'; import '../rpc.dart'; @@ -37,8 +38,16 @@ final _log = Logger('desktop.oath.state'); final _sessionProvider = Provider.autoDispose.family( - (ref, devicePath) => RpcNodeSession( - ref.watch(rpcProvider).requireValue, devicePath, ['ccid', 'oath']), + (ref, devicePath) { + // Reset state if the OATH capability is toggled. + ref.watch(currentDeviceDataProvider.select((value) => + (value.valueOrNull?.info.config + .enabledCapabilities[value.valueOrNull?.node.transport] ?? + 0) & + Capability.oath.value)); + return RpcNodeSession( + ref.watch(rpcProvider).requireValue, devicePath, ['ccid', 'oath']); + }, ); // This remembers the key for all devices for the duration of the process. From ea0a7d298c7c7af6ad1254304ce03aec8c1e3f5f Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 12 Sep 2024 13:09:33 +0200 Subject: [PATCH 59/71] catch exceptions during initial NFC connections --- .../yubico/authenticator/AppContextManager.kt | 2 +- .../com/yubico/authenticator/MainActivity.kt | 80 +++++++++++-------- .../fido/FidoConnectionHelper.kt | 10 +-- .../yubico/authenticator/fido/FidoManager.kt | 15 ++-- .../yubico/authenticator/fido/data/Session.kt | 28 +++++++ .../yubico/authenticator/oath/OathManager.kt | 16 ++-- .../authenticator/yubikit/DeviceInfoHelper.kt | 10 +-- lib/android/fido/state.dart | 3 +- lib/app/views/main_page.dart | 3 +- lib/fido/views/delete_credential_dialog.dart | 23 +++--- lib/oath/views/manage_password_dialog.dart | 36 +++++---- lib/oath/views/rename_account_dialog.dart | 6 +- 12 files changed, 140 insertions(+), 92 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 4467b6ad8..7d0af7c0c 100755 --- a/android/app/src/main/kotlin/com/yubico/authenticator/AppContextManager.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/AppContextManager.kt @@ -28,7 +28,7 @@ abstract class AppContextManager { open fun onPause() {} - open fun onError() {} + open fun onError(e: Exception) {} } 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 57301c5f6..ae0b4a60f 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -79,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 @@ -310,43 +311,58 @@ class MainActivity : FlutterFragmentActivity() { } private suspend fun processYubiKey(device: YubiKeyDevice) { - val deviceInfo = getDeviceInfo(device) + val deviceInfo = try { - if (deviceInfo == null) { - deviceManager.setDeviceInfo(null) - return - } - - if (device is NfcYubiKeyDevice) { - appMethodChannel.nfcStateChanged(NfcState.ONGOING) - } + if (device is NfcYubiKeyDevice) { + 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...") - deviceManager.scpKeyParams = try { - device.withConnection { connection -> - val scp = SecurityDomainSession(connection) - val keyRef = scp.keyInformation.keys.firstOrNull { it.kid == ScpKid.SCP11b } - keyRef?.let { - val certs = scp.getCertificateBundle(it) - if (certs.isNotEmpty()) Scp11KeyParams( - keyRef, - certs[certs.size - 1].publicKey - ) else null - }?.also { - logger.debug("Found SCP11b key: {}", keyRef) + val deviceInfo = getDeviceInfo(device) + + 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...") + deviceManager.scpKeyParams = try { + device.withConnection { connection -> + val scp = SecurityDomainSession(connection) + val keyRef = scp.keyInformation.keys.firstOrNull { it.kid == ScpKid.SCP11b } + keyRef?.let { + val certs = scp.getCertificateBundle(it) + if (certs.isNotEmpty()) Scp11KeyParams( + keyRef, + certs[certs.size - 1].publicKey + ) else null + }?.also { + logger.debug("Found SCP11b key: {}", keyRef) + } } + } catch (e: Exception) { + logger.error("Exception when reading SCP key information: ", e) + // we throw IO exception to unify handling failures as we don't want + // th clear device info + throw IOException("Failure getting SCP keys") } - } catch (e: Exception) { - logger.debug("Exception while getting scp keys: ", e) - contextManager?.onError() - if (device is NfcYubiKeyDevice) { - appMethodChannel.nfcStateChanged(NfcState.FAILURE) - } - null } + deviceInfo + } catch (e: Exception) { + logger.debug("Exception while getting device info and scp keys: ", e) + contextManager?.onError(e) + if (device is NfcYubiKeyDevice) { + logger.debug("Setting NFC state to failure") + appMethodChannel.nfcStateChanged(NfcState.FAILURE) + } + + // do not clear the device info when IOException's occur, + // this allows for retries of failed actions + if (e !is IOException) { + logger.debug("Resetting device info") + deviceManager.setDeviceInfo(null) + } else { + logger.debug("Keeping device info") + } + + return } // this YubiKey provides SCP11b key but the phone cannot perform AESCMAC 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 bc81e92ed..8ead1d5bf 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,14 +42,6 @@ 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())) @@ -80,7 +72,7 @@ class FidoConnectionHelper(private val deviceManager: DeviceManager) { block(YubiKitFidoSession(it)) }.also { if (updateDeviceInfo) { - deviceManager.setDeviceInfo(getDeviceInfo(device)) + deviceManager.setDeviceInfo(runCatching { getDeviceInfo(device) }.getOrNull()) } } 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 8f6908ae7..581a821c7 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 @@ -174,9 +174,9 @@ class FidoManager( } } - override fun onError() { - super.onError() - logger.debug("Cancel any pending action because of upstream error") + override fun onError(e: Exception) { + super.onError(e) + logger.error("Cancelling pending action. Cause: ", e) connectionHelper.cancelPending() } @@ -204,13 +204,12 @@ class FidoManager( } if (updateDeviceInfo.getAndSet(false)) { - deviceManager.setDeviceInfo(getDeviceInfo(device)) + deviceManager.setDeviceInfo(runCatching { getDeviceInfo(device) }.getOrNull()) } } catch (e: Exception) { - // something went wrong, try to get DeviceInfo from any available connection type - logger.error("Failure when processing YubiKey: ", e) - connectionHelper.failPending(e) + logger.error("Cancelling pending action. Cause: ", e) + connectionHelper.cancelPending() if (e !is IOException) { // we don't clear the session on IOExceptions so that the session is ready for @@ -240,7 +239,7 @@ class FidoManager( currentSession ) - val sameDevice = currentSession == previousSession + val sameDevice = currentSession.sameDevice(previousSession) if (device is NfcYubiKeyDevice && (sameDevice || resetHelper.inProgress)) { requestHandled = connectionHelper.invokePending(fidoSession) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/fido/data/Session.kt b/android/app/src/main/kotlin/com/yubico/authenticator/fido/data/Session.kt index 93cd770b1..835a3eca6 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/fido/data/Session.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/fido/data/Session.kt @@ -41,6 +41,19 @@ data class Options( infoData.getOptionsBoolean("ep"), ) + fun sameDevice(other: Options) : Boolean { + if (this === other) return true + + if (clientPin != other.clientPin) return false + if (credMgmt != other.credMgmt) return false + if (credentialMgmtPreview != other.credentialMgmtPreview) return false + if (bioEnroll != other.bioEnroll) return false + // alwaysUv may differ + // ep may differ + + return true + } + companion object { private fun InfoData.getOptionsBoolean( key: String @@ -67,6 +80,21 @@ data class SessionInfo( infoData.remainingDiscoverableCredentials ) + // this is a more permissive comparison, which does not take in an account properties, + // which might change by using the FIDO authenticator + fun sameDevice(other: SessionInfo?): Boolean { + if (other == null) return false + if (this === other) return true + + if (!options.sameDevice(other.options)) return false + if (!aaguid.contentEquals(other.aaguid)) return false + // minPinLength may differ + // forcePinChange may differ + // remainingDiscoverableCredentials may differ + + return true + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false 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 c8073b38b..9f78a6a5e 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,9 +110,9 @@ 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") + override fun onError(e: Exception) { + super.onError(e) + logger.error("Cancelling pending action in onError. Cause: ", e) pendingAction?.let { action -> action.invoke(Result.failure(CancellationException())) pendingAction = null @@ -343,16 +343,16 @@ class OathManager( ) if (updateDeviceInfo.getAndSet(false)) { - deviceManager.setDeviceInfo(getDeviceInfo(device)) + deviceManager.setDeviceInfo(runCatching { getDeviceInfo(device) }.getOrNull()) } } catch (e: Exception) { // OATH not enabled/supported, try to get DeviceInfo over other USB interfaces logger.error("Exception during SmartCard connection/OATH session creation: ", e) - // Remove any pending action + // Cancel any pending action pendingAction?.let { action -> - logger.error("Failing pending action with {}", e.message) - action.invoke(Result.failure(e)) + logger.error("Cancelling pending action. Cause: ", e) + action.invoke(Result.failure(CancellationException())) pendingAction = null } @@ -782,7 +782,7 @@ class OathManager( block(getOathSession(it)) }.also { if (updateDeviceInfo) { - deviceManager.setDeviceInfo(getDeviceInfo(device)) + deviceManager.setDeviceInfo(runCatching { getDeviceInfo(device) }.getOrNull()) } } diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/DeviceInfoHelper.kt b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/DeviceInfoHelper.kt index a0f04d779..938014a37 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/DeviceInfoHelper.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/yubikit/DeviceInfoHelper.kt @@ -47,17 +47,17 @@ class DeviceInfoHelper { private val restrictedNfcBytes = byteArrayOf(0x00, 0x1F, 0xD1.toByte(), 0x01, 0x1b, 0x55, 0x04) + uri - suspend fun getDeviceInfo(device: YubiKeyDevice): Info? { + suspend fun getDeviceInfo(device: YubiKeyDevice): Info { SessionVersionOverride.set(null) var deviceInfo = readDeviceInfo(device) - if (deviceInfo?.version?.major == 0.toByte()) { + if (deviceInfo.version.major == 0.toByte()) { SessionVersionOverride.set(Version(5, 7, 2)) deviceInfo = readDeviceInfo(device) } return deviceInfo } - private suspend fun readDeviceInfo(device: YubiKeyDevice): Info? { + private suspend fun readDeviceInfo(device: YubiKeyDevice): Info { val pid = (device as? UsbYubiKeyDevice)?.pid val deviceInfo = runCatching { @@ -106,8 +106,8 @@ class DeviceInfoHelper { } } catch (e: Exception) { // no smart card connectivity - logger.error("Failure getting device info", e) - return null + logger.error("Failure getting device info: ", e) + throw e } } diff --git a/lib/android/fido/state.dart b/lib/android/fido/state.dart index 3afcfc5f5..5daf430ee 100644 --- a/lib/android/fido/state.dart +++ b/lib/android/fido/state.dart @@ -365,9 +365,8 @@ class _FidoCredentialsNotifier extends FidoCredentialsNotifier { var decodedException = pe.decode(); if (decodedException is CancellationException) { _log.debug('User cancelled delete credential FIDO operation'); - } else { - throw decodedException; } + throw decodedException; } } } diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index c94e435a3..5df54e3e7 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -63,7 +63,8 @@ class MainPage extends ConsumerWidget { final prevSerial = prev?.hasValue == true ? prev?.value?.info.serial : null; if ((serial != null && serial == prevSerial) || - (next.hasValue && (prev != null && prev.isLoading))) { + (next.hasValue && (prev != null && prev.isLoading)) || + next.isLoading) { return; } diff --git a/lib/fido/views/delete_credential_dialog.dart b/lib/fido/views/delete_credential_dialog.dart index 0a07dc7b4..d2ea2fbc3 100755 --- a/lib/fido/views/delete_credential_dialog.dart +++ b/lib/fido/views/delete_credential_dialog.dart @@ -23,6 +23,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../app/message.dart'; import '../../app/models.dart'; import '../../app/state.dart'; +import '../../exception/cancellation_exception.dart'; import '../../widgets/responsive_dialog.dart'; import '../models.dart'; import '../state.dart'; @@ -57,15 +58,19 @@ class DeleteCredentialDialog extends ConsumerWidget { actions: [ TextButton( onPressed: () async { - await ref - .read(credentialProvider(devicePath).notifier) - .deleteCredential(credential); - await ref.read(withContextProvider)( - (context) async { - Navigator.of(context).pop(true); - showMessage(context, l10n.s_passkey_deleted); - }, - ); + try { + await ref + .read(credentialProvider(devicePath).notifier) + .deleteCredential(credential); + await ref.read(withContextProvider)( + (context) async { + Navigator.of(context).pop(true); + showMessage(context, l10n.s_passkey_deleted); + }, + ); + } on CancellationException catch (_) { + // ignored + } }, child: Text(l10n.s_delete), ), diff --git a/lib/oath/views/manage_password_dialog.dart b/lib/oath/views/manage_password_dialog.dart index 14c34773f..782db8fba 100755 --- a/lib/oath/views/manage_password_dialog.dart +++ b/lib/oath/views/manage_password_dialog.dart @@ -22,6 +22,7 @@ import 'package:material_symbols_icons/symbols.dart'; import '../../app/message.dart'; import '../../app/models.dart'; import '../../app/state.dart'; +import '../../exception/cancellation_exception.dart'; import '../../management/models.dart'; import '../../widgets/app_input_decoration.dart'; import '../../widgets/app_text_field.dart'; @@ -71,23 +72,28 @@ class _ManagePasswordDialogState extends ConsumerState { _submit() async { _removeFocus(); - final result = await ref - .read(oathStateProvider(widget.path).notifier) - .setPassword(_currentPasswordController.text, _newPassword); - if (result) { - if (mounted) { - await ref.read(withContextProvider)((context) async { - Navigator.of(context).pop(); - showMessage(context, AppLocalizations.of(context)!.s_password_set); + try { + final result = await ref + .read(oathStateProvider(widget.path).notifier) + .setPassword(_currentPasswordController.text, _newPassword); + if (result) { + if (mounted) { + await ref.read(withContextProvider)((context) async { + Navigator.of(context).pop(); + showMessage(context, AppLocalizations.of(context)!.s_password_set); + }); + } + } else { + _currentPasswordController.selection = TextSelection( + baseOffset: 0, + extentOffset: _currentPasswordController.text.length); + _currentPasswordFocus.requestFocus(); + setState(() { + _currentIsWrong = true; }); } - } else { - _currentPasswordController.selection = TextSelection( - baseOffset: 0, extentOffset: _currentPasswordController.text.length); - _currentPasswordFocus.requestFocus(); - setState(() { - _currentIsWrong = true; - }); + } on CancellationException catch (_) { + // ignored } } diff --git a/lib/oath/views/rename_account_dialog.dart b/lib/oath/views/rename_account_dialog.dart index cd6995d5d..b2916be0d 100755 --- a/lib/oath/views/rename_account_dialog.dart +++ b/lib/oath/views/rename_account_dialog.dart @@ -90,7 +90,7 @@ class RenameAccountDialog extends ConsumerStatefulWidget { context, AppLocalizations.of(context)!.s_account_renamed)); return renamed; } on CancellationException catch (_) { - // ignored + return CancellationException(); } catch (e) { _log.error('Failed to rename account', e); final String errorMessage; @@ -140,7 +140,9 @@ class _RenameAccountDialogState extends ConsumerState { final nav = Navigator.of(context); final renamed = await widget.rename(_issuer.isNotEmpty ? _issuer : null, _name); - nav.pop(renamed); + if (renamed is! CancellationException) { + nav.pop(renamed); + } } @override From ce95119a73fdf65b963d30a05f5def4913251a04 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Thu, 12 Sep 2024 14:49:12 +0200 Subject: [PATCH 60/71] review fixes --- .../src/main/kotlin/com/yubico/authenticator/MainActivity.kt | 5 +---- lib/fido/views/delete_credential_dialog.dart | 2 +- 2 files changed, 2 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 ae0b4a60f..a543dfc2d 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -349,17 +349,14 @@ class MainActivity : FlutterFragmentActivity() { logger.debug("Exception while getting device info and scp keys: ", e) contextManager?.onError(e) if (device is NfcYubiKeyDevice) { - logger.debug("Setting NFC state to failure") appMethodChannel.nfcStateChanged(NfcState.FAILURE) } - // do not clear the device info when IOException's occur, + // do not clear deviceInfo on IOExceptions, // this allows for retries of failed actions if (e !is IOException) { logger.debug("Resetting device info") deviceManager.setDeviceInfo(null) - } else { - logger.debug("Keeping device info") } return diff --git a/lib/fido/views/delete_credential_dialog.dart b/lib/fido/views/delete_credential_dialog.dart index d2ea2fbc3..1a7f49dfa 100755 --- a/lib/fido/views/delete_credential_dialog.dart +++ b/lib/fido/views/delete_credential_dialog.dart @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 Yubico. + * Copyright (C) 2022-2024 Yubico. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 3f8e69a51056abe927da4244631261fdc31a7567 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 13 Sep 2024 08:37:57 +0200 Subject: [PATCH 61/71] refactor exception handling in rename dialog --- lib/oath/views/rename_account_dialog.dart | 86 +++++++++++------------ 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/lib/oath/views/rename_account_dialog.dart b/lib/oath/views/rename_account_dialog.dart index b2916be0d..6bbfbdb33 100755 --- a/lib/oath/views/rename_account_dialog.dart +++ b/lib/oath/views/rename_account_dialog.dart @@ -67,49 +67,15 @@ class RenameAccountDialog extends ConsumerStatefulWidget { OathCredential credential, List<(String? issuer, String name)> existing) { return RenameAccountDialog( - devicePath: devicePath, - issuer: credential.issuer, - name: credential.name, - oathType: credential.oathType, - period: credential.period, - existing: existing, - rename: (issuer, name) async { - final withContext = ref.read(withContextProvider); - try { - // Rename credentials - final renamed = await ref - .read(credentialListProvider(devicePath).notifier) - .renameAccount(credential, issuer, name); - - // Update favorite - ref - .read(favoritesProvider.notifier) - .renameCredential(credential.id, renamed.id); - - await withContext((context) async => showMessage( - context, AppLocalizations.of(context)!.s_account_renamed)); - return renamed; - } on CancellationException catch (_) { - return CancellationException(); - } catch (e) { - _log.error('Failed to rename account', e); - final String errorMessage; - // TODO: Make this cleaner than importing desktop specific RpcError. - if (e is RpcError) { - errorMessage = e.message; - } else { - errorMessage = e.toString(); - } - await withContext((context) async => showMessage( - context, - AppLocalizations.of(context)! - .l_rename_account_failed(errorMessage), - duration: const Duration(seconds: 4), - )); - return null; - } - }, - ); + devicePath: devicePath, + issuer: credential.issuer, + name: credential.name, + oathType: credential.oathType, + period: credential.period, + existing: existing, + rename: (issuer, name) async => await ref + .read(credentialListProvider(devicePath).notifier) + .renameAccount(credential, issuer, name)); } } @@ -138,10 +104,38 @@ class _RenameAccountDialogState extends ConsumerState { _issuerFocus.unfocus(); _nameFocus.unfocus(); final nav = Navigator.of(context); - final renamed = - await widget.rename(_issuer.isNotEmpty ? _issuer : null, _name); - if (renamed is! CancellationException) { + final withContext = ref.read(withContextProvider); + + try { + // Rename credentials + final renamed = + await widget.rename(_issuer.isNotEmpty ? _issuer : null, _name); + + // Update favorite + ref + .read(favoritesProvider.notifier) + .renameCredential(renamed.id, renamed.id); + + await withContext((context) async => showMessage( + context, AppLocalizations.of(context)!.s_account_renamed)); + nav.pop(renamed); + } on CancellationException catch (_) { + // ignored + } catch (e) { + _log.error('Failed to rename account', e); + final String errorMessage; + // TODO: Make this cleaner than importing desktop specific RpcError. + if (e is RpcError) { + errorMessage = e.message; + } else { + errorMessage = e.toString(); + } + await withContext((context) async => showMessage( + context, + AppLocalizations.of(context)!.l_rename_account_failed(errorMessage), + duration: const Duration(seconds: 4), + )); } } From 815150d11372abbd0d9e6ffa8d1f08bbc8e69681 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 13 Sep 2024 09:16:13 +0200 Subject: [PATCH 62/71] don't show progress widgets when using NFC --- lib/app/views/reset_dialog.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/app/views/reset_dialog.dart b/lib/app/views/reset_dialog.dart index 52c3d1ad4..b651fa3fb 100644 --- a/lib/app/views/reset_dialog.dart +++ b/lib/app/views/reset_dialog.dart @@ -123,6 +123,12 @@ class _ResetDialogState extends ConsumerState { _application == Capability.fido2 && !ref.watch(rpcStateProvider.select((state) => state.isAdmin)); + // show the progress widgets on desktop, or on Android when using USB + final showFidoResetProgress = !Platform.isAndroid || + (Platform.isAndroid && + (ref.read(currentDeviceProvider)?.transport == Transport.usb || + _currentStep == _totalSteps)); + return ResponsiveDialog( title: Text(l10n.s_factory_reset), key: factoryResetCancel, @@ -323,10 +329,12 @@ class _ResetDialogState extends ConsumerState { ), ], if (_resetting) - if (_application == Capability.fido2 && _currentStep >= 0) ...[ + if (_application == Capability.fido2 && + _currentStep >= 0 && + showFidoResetProgress) ...[ Text('${l10n.s_status}: ${_getMessage()}'), LinearProgressIndicator(value: progress), - ] else + ] else if (showFidoResetProgress) const LinearProgressIndicator() ] .map((e) => Padding( From d8e9cf34c9b9e679836aed49f5c9b02c9ed2a904 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Fri, 13 Sep 2024 10:45:41 +0200 Subject: [PATCH 63/71] Refactor DeviceInfo caching --- helper/helper/device.py | 53 +++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/helper/helper/device.py b/helper/helper/device.py index e059c03dd..b431487be 100644 --- a/helper/helper/device.py +++ b/helper/helper/device.py @@ -271,9 +271,13 @@ def __call__(self, *args, **kwargs): try: response = super().__call__(*args, **kwargs) if "device_info" in response.flags: - # Clear DeviceInfo cache - self._info = None - self._data = None + old_info = self._info + # Refresh data, and close any open child + self._close_child() + self._data = self._refresh_data() + if old_info == self._info: + # No change to DeviceInfo, further propagation not needed. + response.flags.remove("device_info") return response @@ -318,9 +322,17 @@ def _supports_connection(self, conn_type): def _create_connection(self, conn_type): connection = self._device.open_connection(conn_type) + self._data = self._read_data(connection) return ConnectionNode(self._device, connection, self._info) def _refresh_data(self): + # Re-use existing connection if possible + if self._child and not self._child.closed: + # Make sure to close any open session + self._child._close_child() + return self._read_data(self._child._connection) + + # New connection for conn_type in (SmartCardConnection, OtpConnection, FidoConnection): if self._supports_connection(conn_type): try: @@ -398,6 +410,9 @@ def get_data(self): self._data = None return super().get_data() + def _read_data(self, conn): + return dict(super()._read_data(conn), present=True) + def _refresh_data(self): card = self._observer.card if card is None: @@ -405,7 +420,7 @@ def _refresh_data(self): try: with self._device.open_connection(SmartCardConnection) as conn: try: - data = dict(self._read_data(conn), present=True) + data = self._read_data(conn) except ValueError: # Unknown device, maybe NFC restricted try: @@ -434,8 +449,8 @@ def get(self, params, event, signal): def ccid(self): try: connection = self._device.open_connection(SmartCardConnection) - info = read_info(connection) - return ScpConnectionNode(self._device, connection, info) + self._data = self._read_data(connection) + return ScpConnectionNode(self._device, connection, self._info) except (ValueError, SmartcardException, EstablishContextException) as e: logger.warning("Error opening connection", exc_info=True) raise ConnectionException(self._device.fingerprint, "ccid", e) @@ -444,9 +459,9 @@ def ccid(self): def fido(self): try: with self._device.open_connection(SmartCardConnection) as conn: - info = read_info(conn) + self._data = self._read_data(conn) connection = self._device.open_connection(FidoConnection) - return ConnectionNode(self._device, connection, info) + return ConnectionNode(self._device, connection, self._info) except (ValueError, SmartcardException, EstablishContextException) as e: logger.warning("Error opening connection", exc_info=True) raise ConnectionException(self._device.fingerprint, "fido", e) @@ -458,24 +473,11 @@ def __init__(self, device, connection, info): self._device = device self._transport = device.transport self._connection = connection - self._info = info or read_info(self._connection, device.pid) + self._info = info def __call__(self, *args, **kwargs): try: - response = super().__call__(*args, **kwargs) - if "device_info" in response.flags: - # Refresh DeviceInfo - info = read_info(self._connection, self._device.pid) - if self._info != info: - self._info = info - # Make sure any child node is re-opened after this, - # as enabled applications may have changed - self.close() - else: - # No change to DeviceInfo, further propagation not needed. - response.flags.remove("device_info") - - return response + return super().__call__(*args, **kwargs) except (SmartcardException, OSError) as e: logger.exception("Connection error") raise ChildResetException(f"{e}") @@ -504,11 +506,6 @@ def close(self): logger.warning("Error closing connection", exc_info=True) def get_data(self): - if ( - isinstance(self._connection, SmartCardConnection) - or self._transport == TRANSPORT.USB - ): - self._info = read_info(self._connection, self._device.pid) return dict(version=self._info.version, serial=self._info.serial) def _init_child_node(self, child_cls, capability=CAPABILITY(0)): From 5ba095777ea05a479e928771f857fbcfdc41ecea Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Fri, 13 Sep 2024 11:42:08 +0200 Subject: [PATCH 64/71] Helper: More DeviceInfo improvements --- helper/helper/device.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/helper/helper/device.py b/helper/helper/device.py index b431487be..099b01d20 100644 --- a/helper/helper/device.py +++ b/helper/helper/device.py @@ -265,15 +265,14 @@ def __init__(self, device): super().__init__() self._device = device self._info = None - self._data = None + self._data = self._refresh_data() def __call__(self, *args, **kwargs): try: response = super().__call__(*args, **kwargs) if "device_info" in response.flags: old_info = self._info - # Refresh data, and close any open child - self._close_child() + # Refresh data self._data = self._refresh_data() if old_info == self._info: # No change to DeviceInfo, further propagation not needed. @@ -297,8 +296,6 @@ def create_child(self, name): raise NoSuchNodeException(name) def get_data(self): - if not self._data: - self._data = self._refresh_data() return self._data def _refresh_data(self): @@ -322,7 +319,6 @@ def _supports_connection(self, conn_type): def _create_connection(self, conn_type): connection = self._device.open_connection(conn_type) - self._data = self._read_data(connection) return ConnectionNode(self._device, connection, self._info) def _refresh_data(self): @@ -332,7 +328,7 @@ def _refresh_data(self): self._child._close_child() return self._read_data(self._child._connection) - # New connection + # No child, open new connection for conn_type in (SmartCardConnection, OtpConnection, FidoConnection): if self._supports_connection(conn_type): try: @@ -396,10 +392,10 @@ def update(self, observable, actions): class ReaderDeviceNode(AbstractDeviceNode): def __init__(self, device): - super().__init__(device) self._observer = _ReaderObserver(device) self._monitor = CardMonitor() self._monitor.addObserver(self._observer) + super().__init__(device) def close(self): self._monitor.deleteObserver(self._observer) @@ -407,7 +403,7 @@ def close(self): def get_data(self): if self._observer.needs_refresh: - self._data = None + self._data = self._refresh_data() return super().get_data() def _read_data(self, conn): @@ -418,6 +414,7 @@ def _refresh_data(self): if card is None: return dict(present=False, status="no-card") try: + self._close_child() with self._device.open_connection(SmartCardConnection) as conn: try: data = self._read_data(conn) @@ -449,7 +446,6 @@ def get(self, params, event, signal): def ccid(self): try: connection = self._device.open_connection(SmartCardConnection) - self._data = self._read_data(connection) return ScpConnectionNode(self._device, connection, self._info) except (ValueError, SmartcardException, EstablishContextException) as e: logger.warning("Error opening connection", exc_info=True) @@ -458,8 +454,6 @@ def ccid(self): @child def fido(self): try: - with self._device.open_connection(SmartCardConnection) as conn: - self._data = self._read_data(conn) connection = self._device.open_connection(FidoConnection) return ConnectionNode(self._device, connection, self._info) except (ValueError, SmartcardException, EstablishContextException) as e: From 3099cfc75e2e6f9e6feaa58ae2edbc902069510d Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Fri, 13 Sep 2024 12:57:55 +0200 Subject: [PATCH 65/71] Helper: Improve connection switching when toggling interfaces --- helper/helper/device.py | 29 +++++++++++++++++++++++++---- helper/helper/fido.py | 2 +- helper/helper/management.py | 26 ++++++++++++++------------ 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/helper/helper/device.py b/helper/helper/device.py index 099b01d20..846fbc077 100644 --- a/helper/helper/device.py +++ b/helper/helper/device.py @@ -202,7 +202,12 @@ def __init__(self): def __call__(self, *args, **kwargs): with self._get_state: try: - return super().__call__(*args, **kwargs) + response = super().__call__(*args, **kwargs) + if "device_closed" in response.flags: + self._list_state = 0 + self._device_mapping = {} + response.flags.remove("device_closed") + return response except ConnectionException as e: if self._failing_connection == e.body: self._retries += 1 @@ -270,6 +275,13 @@ def __init__(self, device): def __call__(self, *args, **kwargs): try: response = super().__call__(*args, **kwargs) + + # The command resulted in the device closing + if "device_closed" in response.flags: + self.close() + return response + + # The command resulted in device_info modification if "device_info" in response.flags: old_info = self._info # Refresh data @@ -296,7 +308,9 @@ def create_child(self, name): raise NoSuchNodeException(name) def get_data(self): - return self._data + if self._data: + return self._data + raise ChildResetException("Unable to read device data") def _refresh_data(self): ... @@ -326,7 +340,12 @@ def _refresh_data(self): if self._child and not self._child.closed: # Make sure to close any open session self._child._close_child() - return self._read_data(self._child._connection) + try: + return self._read_data(self._child._connection) + except Exception: + logger.warning( + f"Unable to use {self._child._connection}", exc_info=True + ) # No child, open new connection for conn_type in (SmartCardConnection, OtpConnection, FidoConnection): @@ -336,7 +355,9 @@ def _refresh_data(self): return self._read_data(conn) except Exception: logger.warning(f"Unable to connect via {conn_type}", exc_info=True) - raise ValueError("No supported connections") + # Failed to refresh, close + self.close() + return None @child(condition=lambda self: self._supports_connection(SmartCardConnection)) def ccid(self): diff --git a/helper/helper/fido.py b/helper/helper/fido.py index fff7bac5d..284c6e4bb 100644 --- a/helper/helper/fido.py +++ b/helper/helper/fido.py @@ -203,7 +203,7 @@ def reset(self, params, event, signal): raise self._info = self.ctap.get_info() self._token = None - return RpcResponse(dict(), ["device_info"]) + return RpcResponse(dict(), ["device_info", "device_closed"]) @action(condition=lambda self: self._info.options["clientPin"]) def unlock(self, params, event, signal): diff --git a/helper/helper/management.py b/helper/helper/management.py index 3086a1bde..5474f8e21 100644 --- a/helper/helper/management.py +++ b/helper/helper/management.py @@ -51,20 +51,20 @@ def list_actions(self): def _await_reboot(self, serial, usb_enabled): ifaces = CAPABILITY(usb_enabled or 0).usb_interfaces + types: list[Type[Connection]] = [ + SmartCardConnection, + OtpConnection, + # mypy doesn't support ABC.register() + FidoConnection, # type: ignore + ] + connection_types = [t for t in types if t.usb_interface in ifaces] # Prefer to use the "same" connection type as before - if self._connection_type.usb_interface in ifaces: - connection_types = [self._connection_type] - else: - types: list[Type[Connection]] = [ - SmartCardConnection, - OtpConnection, - # mypy doesn't support ABC.register() - FidoConnection, # type: ignore - ] - connection_types = [t for t in types if t.usb_interface in ifaces] + if self._connection_type in connection_types: + connection_types.remove(self._connection_type) + connection_types.insert(0, self._connection_type) self.session.close() - logger.debug("Waiting for device to re-appear...") + logger.debug(f"Waiting for device to re-appear over {connection_types}...") for _ in range(10): sleep(0.2) # Always sleep initially for dev, info in list_all_devices(connection_types): @@ -87,10 +87,12 @@ def configure(self, params, event, signal): ) serial = self.session.read_device_info().serial self.session.write_device_config(config, reboot, cur_lock_code, new_lock_code) + flags = ["device_info"] if reboot: enabled = config.enabled_capabilities.get(TRANSPORT.USB) + flags.append("device_closed") self._await_reboot(serial, enabled) - return RpcResponse(dict(), ["device_info"]) + return RpcResponse(dict(), flags) @action def set_mode(self, params, event, signal): From 5637d906f9c60352c4a98ffa3b2e34a2ae7f5dff Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 13 Sep 2024 14:16:53 +0200 Subject: [PATCH 66/71] fix code style --- lib/app/views/reset_dialog.dart | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/app/views/reset_dialog.dart b/lib/app/views/reset_dialog.dart index b651fa3fb..06c68cb67 100644 --- a/lib/app/views/reset_dialog.dart +++ b/lib/app/views/reset_dialog.dart @@ -124,10 +124,11 @@ class _ResetDialogState extends ConsumerState { !ref.watch(rpcStateProvider.select((state) => state.isAdmin)); // show the progress widgets on desktop, or on Android when using USB - final showFidoResetProgress = !Platform.isAndroid || - (Platform.isAndroid && - (ref.read(currentDeviceProvider)?.transport == Transport.usb || - _currentStep == _totalSteps)); + final showResetProgress = _resetting && + (!Platform.isAndroid || + (Platform.isAndroid && + (ref.read(currentDeviceProvider)?.transport == Transport.usb || + _currentStep == _totalSteps))); return ResponsiveDialog( title: Text(l10n.s_factory_reset), @@ -328,13 +329,11 @@ class _ResetDialogState extends ConsumerState { }, ), ], - if (_resetting) - if (_application == Capability.fido2 && - _currentStep >= 0 && - showFidoResetProgress) ...[ + if (showResetProgress) + if (_application == Capability.fido2 && _currentStep >= 0) ...[ Text('${l10n.s_status}: ${_getMessage()}'), LinearProgressIndicator(value: progress), - ] else if (showFidoResetProgress) + ] else const LinearProgressIndicator() ] .map((e) => Padding( From 8d9e9e21590d6d778b654e5b4b33ad929448ef45 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 13 Sep 2024 14:26:53 +0200 Subject: [PATCH 67/71] Simplify condition --- lib/app/views/reset_dialog.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/app/views/reset_dialog.dart b/lib/app/views/reset_dialog.dart index 06c68cb67..a318f838c 100644 --- a/lib/app/views/reset_dialog.dart +++ b/lib/app/views/reset_dialog.dart @@ -126,9 +126,8 @@ class _ResetDialogState extends ConsumerState { // show the progress widgets on desktop, or on Android when using USB final showResetProgress = _resetting && (!Platform.isAndroid || - (Platform.isAndroid && - (ref.read(currentDeviceProvider)?.transport == Transport.usb || - _currentStep == _totalSteps))); + ref.read(currentDeviceProvider)?.transport == Transport.usb || + _currentStep == _totalSteps); return ResponsiveDialog( title: Text(l10n.s_factory_reset), From e12c9a4625f932644e5b287a3be4bfd16f098876 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Fri, 13 Sep 2024 15:12:29 +0200 Subject: [PATCH 68/71] Only use NFC if available --- .../main/kotlin/com/yubico/authenticator/MainActivity.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 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 a543dfc2d..47c45af99 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -129,10 +129,11 @@ class MainActivity : FlutterFragmentActivity() { allowScreenshots(false) - yubikit = YubiKitManager( - UsbYubiKeyManager(this), + val nfcManager = if (NfcAdapter.getDefaultAdapter(this) != null) { NfcYubiKeyManager(this, NfcStateDispatcher(nfcStateListener)) - ) + } else null + + yubikit = YubiKitManager(UsbYubiKeyManager(this), nfcManager) } override fun onNewIntent(intent: Intent) { From b68798d6b42cfe8f3a2e37aa4579547adf6f1086 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Mon, 16 Sep 2024 15:29:03 +0200 Subject: [PATCH 69/71] Improve AddToAny when NFC not available --- lib/android/oath/state.dart | 3 --- lib/android/overlay/nfc/nfc_overlay.dart | 9 +++++---- lib/android/overlay/nfc/views/nfc_overlay_icons.dart | 11 +++++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/android/oath/state.dart b/lib/android/oath/state.dart index 2b47e8927..25bf1ce59 100755 --- a/lib/android/oath/state.dart +++ b/lib/android/oath/state.dart @@ -36,7 +36,6 @@ 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'; @@ -192,7 +191,6 @@ 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 @@ -208,7 +206,6 @@ 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', diff --git a/lib/android/overlay/nfc/nfc_overlay.dart b/lib/android/overlay/nfc/nfc_overlay.dart index 028a4d7b4..d0014ef6f 100755 --- a/lib/android/overlay/nfc/nfc_overlay.dart +++ b/lib/android/overlay/nfc/nfc_overlay.dart @@ -37,6 +37,7 @@ final nfcOverlay = class _NfcOverlayNotifier extends Notifier { Timer? processingViewTimeout; late final l10n = ref.read(l10nProvider); + late final eventNotifier = ref.read(nfcEventNotifier.notifier); @override int build() { @@ -76,10 +77,9 @@ class _NfcOverlayNotifier extends Notifier { }); _channel.setMethodCallHandler((call) async { - final notifier = ref.read(nfcEventNotifier.notifier); switch (call.method) { case 'show': - notifier.send(showTapYourYubiKey()); + eventNotifier.send(showTapYourYubiKey()); break; case 'close': @@ -97,12 +97,13 @@ class _NfcOverlayNotifier extends Notifier { } NfcEvent showTapYourYubiKey() { + final nfcAvailable = ref.watch(androidNfcAdapterState); ref.read(nfcOverlayWidgetProperties.notifier).update(hasCloseButton: true); return NfcSetViewEvent( child: NfcContentWidget( title: l10n.s_nfc_ready_to_scan, - subtitle: l10n.s_nfc_tap_your_yubikey, - icon: const NfcIconProgressBar(false), + subtitle: nfcAvailable ? l10n.s_nfc_tap_your_yubikey : l10n.l_insert_yk, + icon: nfcAvailable ? const NfcIconProgressBar(false) : const UsbIcon(), )); } diff --git a/lib/android/overlay/nfc/views/nfc_overlay_icons.dart b/lib/android/overlay/nfc/views/nfc_overlay_icons.dart index 92ed4a76f..80881ed1c 100644 --- a/lib/android/overlay/nfc/views/nfc_overlay_icons.dart +++ b/lib/android/overlay/nfc/views/nfc_overlay_icons.dart @@ -56,6 +56,17 @@ class NfcIconProgressBar extends StatelessWidget { ); } +class UsbIcon extends StatelessWidget { + const UsbIcon({super.key}); + + @override + Widget build(BuildContext context) => Icon( + Symbols.usb, + size: 64, + color: Theme.of(context).colorScheme.primary, + ); +} + class NfcIconSuccess extends StatelessWidget { const NfcIconSuccess({super.key}); From 1a9db69418af38d2d953d5d4b2f0e7e41cf49ed7 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 25 Sep 2024 08:34:31 +0200 Subject: [PATCH 70/71] Update NEWS --- NEWS | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/NEWS b/NEWS index 34c1c5c45..a651dfcc6 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,17 @@ +* Version 7.1.0 (released 2024-09-25) + ** Improved support for YubiKey Bio - Multi-protocol Edition. + ** Improved support for YubiKey 5.7 FIPS: + *** Use secure messaging (SCP11b) when needed over NFC. + *** Add FIPS status information to Home screen and application pages. + *** Disable locked features in UI while not in FIPS approved mode. + ** Android: Improve UI for NFC communication. + ** Android: Improve error messages shown in UI. + ** FIDO: Add button to enable Enterprise Attestation on supported devices. + ** UI: Add instructions for enabling NFC for NFC-restricted devices. + ** UI: Display PIN complexity info on Home screen for applicable devices. + ** UI: Add grid views for OATH and Passkey credential lists. + ** UI: Add toggling of visibility to the right sidebar menu. + * Version 7.0.1 (released 2024-05-30) Android only release ** Fix: Opening the app by NFC tap needs another tap to reveal accounts. ** Fix: NFC devices attached to mobile phone prevent usage of USB YubiKeys. From acfd00bd75e48df4c25a780f1916a657019da0c6 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Wed, 25 Sep 2024 09:13:41 +0200 Subject: [PATCH 71/71] Bump version --- helper/version_info.txt | 8 ++++---- lib/version.dart | 4 ++-- pubspec.yaml | 2 +- resources/win/release-win.ps1 | 2 +- resources/win/yubioath-desktop.wxs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/helper/version_info.txt b/helper/version_info.txt index 9968024d1..b925ee1a2 100755 --- a/helper/version_info.txt +++ b/helper/version_info.txt @@ -6,8 +6,8 @@ VSVersionInfo( ffi=FixedFileInfo( # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4) # Set not needed items to zero 0. - filevers=(7, 1, 0, 0), - prodvers=(7, 1, 0, 0), + filevers=(7, 1, 1, 0), + prodvers=(7, 1, 1, 0), # Contains a bitmask that specifies the valid bits 'flags'r mask=0x3f, # Contains a bitmask that specifies the Boolean attributes of the file. @@ -31,11 +31,11 @@ VSVersionInfo( '040904b0', [StringStruct('CompanyName', 'Yubico'), StringStruct('FileDescription', 'Yubico Authenticator Helper'), - StringStruct('FileVersion', '7.1.0'), + StringStruct('FileVersion', '7.1.1-dev.0'), StringStruct('LegalCopyright', 'Copyright (c) Yubico'), StringStruct('OriginalFilename', 'authenticator-helper.exe'), StringStruct('ProductName', 'Yubico Authenticator'), - StringStruct('ProductVersion', '7.1.0')]) + StringStruct('ProductVersion', '7.1.1-dev.0')]) ]), VarFileInfo([VarStruct('Translation', [1033, 1200])]) ] diff --git a/lib/version.dart b/lib/version.dart index c641b61d6..b28490e82 100755 --- a/lib/version.dart +++ b/lib/version.dart @@ -1,5 +1,5 @@ // GENERATED CODE - DO NOT MODIFY BY HAND // This file is generated by running ./set-version.py -const String version = '7.1.0'; -const int build = 70100; +const String version = '7.1.1-dev.0'; +const int build = 70101; diff --git a/pubspec.yaml b/pubspec.yaml index f55badb03..0ff6a4a87 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # This field is updated by running ./set-version.py # DO NOT MANUALLY EDIT THIS! -version: 7.1.0+70100 +version: 7.1.1-dev.0+70101 environment: sdk: '>=3.4.3 <4.0.0' diff --git a/resources/win/release-win.ps1 b/resources/win/release-win.ps1 index f15e00fb1..0bdca893e 100644 --- a/resources/win/release-win.ps1 +++ b/resources/win/release-win.ps1 @@ -1,4 +1,4 @@ -$version="7.1.0" +$version="7.1.1-dev.0" echo "Clean-up of old files" rm *.msi diff --git a/resources/win/yubioath-desktop.wxs b/resources/win/yubioath-desktop.wxs index f11183b1c..d9f4918ab 100644 --- a/resources/win/yubioath-desktop.wxs +++ b/resources/win/yubioath-desktop.wxs @@ -1,7 +1,7 @@ - +