This commit is contained in:
Dennis Fokin 2022-09-07 08:28:40 +02:00
commit 3290a5c65e
No known key found for this signature in database
GPG Key ID: 870B88256690D8BC
5 changed files with 85 additions and 47 deletions

View File

@ -29,7 +29,7 @@
"oath_digits": "digits", "oath_digits": "digits",
"oath_success_delete_account": "Account deleted", "oath_success_delete_account": "Account deleted",
"oath_delete": "Delete", "oath_delete": "Delete",
"oath_warning_this_will_delete_from_key": "Warning! This action will delete the account from your YubiKey.", "oath_warning_this_will_delete_account_from_key": "Warning! This action will delete the account from your YubiKey.",
"oath_warning_disable_this_cred": "You will no longer be able to generate OTPs for this account. Make sure to first disable this credential from the website to avoid being locked out of your account.", "oath_warning_disable_this_cred": "You will no longer be able to generate OTPs for this account. Make sure to first disable this credential from the website to avoid being locked out of your account.",
"oath_account": "Account", "oath_account": "Account",
"oath_password_set": "Password set", "oath_password_set": "Password set",
@ -44,6 +44,35 @@
"oath_enter_new_password": "Enter your new password. A password may contain letters, numbers and special characters.", "oath_enter_new_password": "Enter your new password. A password may contain letters, numbers and special characters.",
"oath_new_password": "New password", "oath_new_password": "New password",
"oath_confirm_password": "Confirm password", "oath_confirm_password": "Confirm password",
"oath_authenticator": "Authenticator",
"oath_reset_oath": "Reset OATH",
"oath_no_accounts": "No accounts",
"oath_search_accounts": "Search accounts",
"oath_set_password": "Set password",
"oath_failed_remember_pw": "Failed to remember password",
"oath_enter_oath_pw": "Enter the OATH password for your YubiKey",
"oath_password": "Password",
"oath_keystore_unavailable": "OS Keystore unavailable",
"oath_remember_password": "Remember password",
"oath_unlock": "Unlock",
"oath_warning_will_change_account_displayed": "This will change how the account is displayed in the list.",
"oath_account_must_have_name": "Your account must have a name",
"oath_name_exists": "This name already exists for the Issuer",
"oath_account_renamed": "Account renamed",
"oath_rename": "Rename {label}?",
"@oath_rename" : {
"placeholders": {
"label": {}
}
},
"oath_factory_reset": "Factory reset",
"oath_oath_application_reset": "OATH application reset",
"oath_reset": "Reset",
"oath_warning_will_delete_accounts": "Warning! This will irrevocably delete all OATH TOTP/HOTP accounts from your YubiKey.",
"oath_warning_disable_these_creds": "Your OATH credentials, as well as any password set, will be removed from this YubiKey. Make sure to first disable these from their respective web sites to avoid being locked out of your accounts.",

View File

@ -50,7 +50,7 @@ class DeleteAccountDialog extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(AppLocalizations.of(context)! Text(AppLocalizations.of(context)!
.oath_warning_this_will_delete_from_key), .oath_warning_this_will_delete_account_from_key),
Text( Text(
AppLocalizations.of(context)!.oath_warning_disable_this_cred, AppLocalizations.of(context)!.oath_warning_disable_this_cred,
style: Theme.of(context).textTheme.bodyText1, style: Theme.of(context).textTheme.bodyText1,

View File

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../app/message.dart'; import '../../app/message.dart';
@ -28,12 +29,12 @@ class OathScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return ref.watch(oathStateProvider(devicePath)).when( return ref.watch(oathStateProvider(devicePath)).when(
loading: () => AppPage( loading: () => AppPage(
title: const Text('Authenticator'), title: Text(AppLocalizations.of(context)!.oath_authenticator),
centered: true, centered: true,
child: const AppLoadingScreen(), child: const AppLoadingScreen(),
), ),
error: (error, _) => AppFailurePage( error: (error, _) => AppFailurePage(
title: const Text('Authenticator'), title: Text(AppLocalizations.of(context)!.oath_authenticator),
cause: error, cause: error,
), ),
data: (oathState) => oathState.locked data: (oathState) => oathState.locked
@ -51,10 +52,10 @@ class _LockedView extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) => AppPage( Widget build(BuildContext context, WidgetRef ref) => AppPage(
title: const Text('Authenticator'), title: Text(AppLocalizations.of(context)!.oath_authenticator),
keyActions: [ keyActions: [
buildMenuItem( buildMenuItem(
title: const Text('Manage password'), title: Text(AppLocalizations.of(context)!.oath_manage_password),
leading: const Icon(Icons.password), leading: const Icon(Icons.password),
action: () { action: () {
showBlurDialog( showBlurDialog(
@ -65,7 +66,7 @@ class _LockedView extends ConsumerWidget {
}, },
), ),
buildMenuItem( buildMenuItem(
title: const Text('Reset OATH'), title: Text(AppLocalizations.of(context)!.oath_reset_oath),
leading: const Icon(Icons.delete), leading: const Icon(Icons.delete),
action: () { action: () {
showBlurDialog( showBlurDialog(
@ -119,9 +120,9 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
final credentials = ref.watch(credentialsProvider); final credentials = ref.watch(credentialsProvider);
if (credentials?.isEmpty == true) { if (credentials?.isEmpty == true) {
return MessagePage( return MessagePage(
title: const Text('Authenticator'), title: Text(AppLocalizations.of(context)!.oath_authenticator),
graphic: noAccounts, graphic: noAccounts,
header: 'No accounts', header: AppLocalizations.of(context)!.oath_no_accounts,
keyActions: _buildActions( keyActions: _buildActions(
context, context,
credentials: null, credentials: null,
@ -153,11 +154,11 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
controller: searchController, controller: searchController,
focusNode: searchFocus, focusNode: searchFocus,
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
decoration: const InputDecoration( decoration: InputDecoration(
hintText: 'Search accounts', hintText: AppLocalizations.of(context)!.oath_search_accounts,
isDense: true, isDense: true,
prefixIcon: Icon(Icons.search_outlined), prefixIcon: const Icon(Icons.search_outlined),
prefixIconConstraints: BoxConstraints( prefixIconConstraints: const BoxConstraints(
minHeight: 30, minHeight: 30,
minWidth: 30, minWidth: 30,
), ),
@ -190,7 +191,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
final capacity = widget.oathState.version.isAtLeast(4) ? 32 : null; final capacity = widget.oathState.version.isAtLeast(4) ? 32 : null;
return [ return [
buildMenuItem( buildMenuItem(
title: const Text('Add account'), title: Text(AppLocalizations.of(context)!.oath_add_account),
leading: const Icon(Icons.person_add_alt_1), leading: const Icon(Icons.person_add_alt_1),
trailing: capacity != null ? '$used/$capacity' : null, trailing: capacity != null ? '$used/$capacity' : null,
action: capacity == null || capacity > used action: capacity == null || capacity > used
@ -208,8 +209,9 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
: null, : null,
), ),
buildMenuItem( buildMenuItem(
title: Text( title: Text(widget.oathState.hasKey
widget.oathState.hasKey ? 'Manage password' : 'Set password'), ? AppLocalizations.of(context)!.oath_manage_password
: AppLocalizations.of(context)!.oath_set_password),
leading: const Icon(Icons.password), leading: const Icon(Icons.password),
action: () { action: () {
showBlurDialog( showBlurDialog(
@ -219,7 +221,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> {
); );
}), }),
buildMenuItem( buildMenuItem(
title: const Text('Reset OATH'), title: Text(AppLocalizations.of(context)!.oath_reset_oath),
leading: const Icon(Icons.delete), leading: const Icon(Icons.delete),
action: () { action: () {
showBlurDialog( showBlurDialog(
@ -260,7 +262,8 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> {
_passwordController.clear(); _passwordController.clear();
}); });
} else if (_remember && !result.second) { } else if (_remember && !result.second) {
showMessage(context, 'Failed to remember password'); showMessage(
context, AppLocalizations.of(context)!.oath_failed_remember_pw);
} }
} }
@ -274,8 +277,8 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
'Enter the OATH password for your YubiKey', AppLocalizations.of(context)!.oath_enter_oath_pw,
), ),
const SizedBox(height: 16.0), const SizedBox(height: 16.0),
TextField( TextField(
@ -284,8 +287,10 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> {
obscureText: _isObscure, obscureText: _isObscure,
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: 'Password', labelText: AppLocalizations.of(context)!.oath_password,
errorText: _passwordIsWrong ? 'Wrong password' : null, errorText: _passwordIsWrong
? AppLocalizations.of(context)!.oath_wrong_password
: null,
helperText: '', // Prevents resizing when errorText shown helperText: '', // Prevents resizing when errorText shown
prefixIcon: const Icon(Icons.password_outlined), prefixIcon: const Icon(Icons.password_outlined),
suffixIcon: IconButton( suffixIcon: IconButton(
@ -310,14 +315,16 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> {
), ),
const SizedBox(height: 8.0), const SizedBox(height: 8.0),
keystoreFailed keystoreFailed
? const ListTile( ? ListTile(
leading: Icon(Icons.warning_amber_rounded), leading: const Icon(Icons.warning_amber_rounded),
title: Text('OS Keystore unavailable'), title: Text(
AppLocalizations.of(context)!.oath_keystore_unavailable),
dense: true, dense: true,
minLeadingWidth: 0, minLeadingWidth: 0,
) )
: CheckboxListTile( : CheckboxListTile(
title: const Text('Remember password'), title:
Text(AppLocalizations.of(context)!.oath_remember_password),
dense: true, dense: true,
controlAffinity: ListTileControlAffinity.leading, controlAffinity: ListTileControlAffinity.leading,
value: _remember, value: _remember,
@ -332,7 +339,7 @@ class _UnlockFormState extends ConsumerState<_UnlockForm> {
child: Align( child: Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: ElevatedButton.icon( child: ElevatedButton.icon(
label: const Text('Unlock'), label: Text(AppLocalizations.of(context)!.oath_unlock),
icon: const Icon(Icons.lock_open), icon: const Icon(Icons.lock_open),
onPressed: _passwordController.text.isNotEmpty ? _submit : null, onPressed: _passwordController.text.isNotEmpty ? _submit : null,
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@ -54,7 +55,7 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
if (!mounted) return; if (!mounted) return;
Navigator.of(context).pop(renamed); Navigator.of(context).pop(renamed);
showMessage(context, 'Account renamed'); showMessage(context, AppLocalizations.of(context)!.oath_account_renamed);
} on CancellationException catch (_) { } on CancellationException catch (_) {
// ignored // ignored
} catch (e) { } catch (e) {
@ -68,7 +69,7 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
} }
showMessage( showMessage(
context, context,
'Failed adding account: $errorMessage', '${AppLocalizations.of(context)!.oath_fail_add_account}: $errorMessage',
duration: const Duration(seconds: 4), duration: const Duration(seconds: 4),
); );
} }
@ -111,30 +112,30 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
final isValid = isUnique && isValidFormat; final isValid = isUnique && isValidFormat;
return ResponsiveDialog( return ResponsiveDialog(
title: const Text('Rename account'), title: Text(AppLocalizations.of(context)!.oath_rename_account),
actions: [ actions: [
TextButton( TextButton(
onPressed: didChange && isValid ? _submit : null, onPressed: didChange && isValid ? _submit : null,
child: const Text('Save'), child: Text(AppLocalizations.of(context)!.oath_save),
), ),
], ],
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('Rename $label?'), Text(AppLocalizations.of(context)!.oath_rename(label)),
const Text( Text(AppLocalizations.of(context)!
'This will change how the account is displayed in the list.'), .oath_warning_will_change_account_displayed),
TextFormField( TextFormField(
initialValue: _issuer, initialValue: _issuer,
enabled: issuerRemaining > 0, enabled: issuerRemaining > 0,
maxLength: issuerRemaining > 0 ? issuerRemaining : null, maxLength: issuerRemaining > 0 ? issuerRemaining : null,
buildCounter: buildByteCounterFor(_issuer), buildCounter: buildByteCounterFor(_issuer),
inputFormatters: [limitBytesLength(issuerRemaining)], inputFormatters: [limitBytesLength(issuerRemaining)],
decoration: const InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: 'Issuer (optional)', labelText: AppLocalizations.of(context)!.oath_issuer_optional,
helperText: '', // Prevents dialog resizing when disabled helperText: '', // Prevents dialog resizing when disabled
prefixIcon: Icon(Icons.business_outlined), prefixIcon: const Icon(Icons.business_outlined),
), ),
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
onChanged: (value) { onChanged: (value) {
@ -150,12 +151,12 @@ class _RenameAccountDialogState extends ConsumerState<RenameAccountDialog> {
buildCounter: buildByteCounterFor(_account), buildCounter: buildByteCounterFor(_account),
decoration: InputDecoration( decoration: InputDecoration(
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: 'Account name', labelText: AppLocalizations.of(context)!.oath_account_name,
helperText: '', // Prevents dialog resizing when disabled helperText: '', // Prevents dialog resizing when disabled
errorText: !isValidFormat errorText: !isValidFormat
? 'Your account must have a name' ? AppLocalizations.of(context)!.oath_account_must_have_name
: !isUnique : !isUnique
? 'This name already exists for the Issuer' ? AppLocalizations.of(context)!.oath_name_exists
: null, : null,
prefixIcon: const Icon(Icons.people_alt_outlined), prefixIcon: const Icon(Icons.people_alt_outlined),
), ),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../app/message.dart'; import '../../app/message.dart';
@ -14,25 +15,25 @@ class ResetDialog extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return ResponsiveDialog( return ResponsiveDialog(
title: const Text('Factory reset'), title: Text(AppLocalizations.of(context)!.oath_factory_reset),
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
await ref.read(oathStateProvider(devicePath).notifier).reset(); await ref.read(oathStateProvider(devicePath).notifier).reset();
await ref.read(withContextProvider)((context) async { await ref.read(withContextProvider)((context) async {
Navigator.of(context).pop(); Navigator.of(context).pop();
showMessage(context, 'OATH application reset'); showMessage(context,
AppLocalizations.of(context)!.oath_oath_application_reset);
}); });
}, },
child: const Text('Reset'), child: Text(AppLocalizations.of(context)!.oath_reset),
), ),
], ],
child: Column( child: Column(
children: [ children: [
const Text( Text(AppLocalizations.of(context)!.oath_warning_will_delete_accounts),
'Warning! This will irrevocably delete all OATH TOTP/HOTP accounts from your YubiKey.'),
Text( Text(
'Your OATH credentials, as well as any password set, will be removed from this YubiKey. Make sure to first disable these from their respective web sites to avoid being locked out of your accounts.', AppLocalizations.of(context)!.oath_warning_disable_these_creds,
style: Theme.of(context).textTheme.bodyText1, style: Theme.of(context).textTheme.bodyText1,
), ),
] ]