From aefd8cacf586e2dce74310567f25519aafe68a38 Mon Sep 17 00:00:00 2001 From: Frank Merkel <138444693+frankmer@users.noreply.github.com> Date: Wed, 21 Feb 2024 11:32:46 +0100 Subject: [PATCH] . --- lib/l10n/app_cs.arb | 14 +- lib/l10n/app_de.arb | 14 +- lib/l10n/app_en.arb | 15 +- lib/l10n/app_es.arb | 14 +- lib/l10n/app_fr.arb | 14 +- lib/l10n/app_localizations.dart | 12 + lib/l10n/app_localizations_cs.dart | 8 + lib/l10n/app_localizations_de.dart | 8 + lib/l10n/app_localizations_en.dart | 8 + lib/l10n/app_localizations_es.dart | 8 + lib/l10n/app_localizations_fr.dart | 8 + lib/l10n/app_localizations_nl.dart | 8 + lib/l10n/app_localizations_pl.dart | 8 + lib/l10n/app_nl.arb | 14 +- lib/l10n/app_pl.arb | 14 +- lib/model/states/token_state.dart | 4 - lib/state_notifiers/token_notifier.dart | 11 +- lib/utils/globals.dart | 1 + .../main_view_tokens_list.dart | 2 +- .../day_password_token_widget_tile.dart | 76 ++-- lib/views/settings_view/settings_view.dart | 140 ++++---- .../settings_groups.dart | 8 +- lib/widgets/custom_trailing.dart | 10 +- .../dialog_widgets/default_dialog.dart | 23 +- .../dialog_widgets/push_request_dialog.dart | 325 ++++++++---------- pubspec.yaml | 2 +- 26 files changed, 457 insertions(+), 312 deletions(-) diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index fcb5ebe42..849fe434c 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -561,5 +561,17 @@ "patchNotesDialogTitle": "Co je nového?", "version": "Verze", "noMailAppTitle": "Není nainstalována žádná e-mailová aplikace", - "noMailAppDescription": "There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message." + "noMailAppDescription": "There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.", + "authenticationRequest": "Žádost o ověření", + "requestInfo": "Odesláno {issuer} pro váš účet: \"{account}\"", + "@requestInfo": { + "placeholders": { + "issuer": { + "example": "privacyIDEA" + }, + "account": { + "example": "GitHub" + } + } + } } \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 5c299c1a6..e70ba1c33 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -540,5 +540,17 @@ "patchNotesDialogTitle": "Was ist neu?", "version": "Version", "noMailAppTitle": "Keine Mail-App gefunden", - "noMailAppDescription": "Auf diesem Gerät ist keine E-Mail-App installiert oder initialisiert, bitte versuchen Sie es erneut, wenn Sie eine E-Mail-Nachricht senden können." + "noMailAppDescription": "Auf diesem Gerät ist keine E-Mail-App installiert oder initialisiert, bitte versuchen Sie es erneut, wenn Sie eine E-Mail-Nachricht senden können.", + "authenticationRequest": "Authentifizierung", + "requestInfo": "Gesendet von {issuer} für Ihr Konto: \"{account}\"", + "@requestInfo": { + "placeholders": { + "issuer": { + "example": "privacyIDEA" + }, + "Konto": { + "example": "GitHub" + } + } + } } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 7a8426903..b8be4e2dd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -551,5 +551,18 @@ "patchNotesDialogTitle": "What's new?", "version": "Version", "noMailAppTitle": "No mail app found", - "noMailAppDescription": "There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message." + "noMailAppDescription": "There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.", + "authenticationRequest": "Authentication request", + "requestInfo": "Sent by {issuer} for your account: \"{account}\"", + "@requestInfo": { + "description": "Description of the authentication request.", + "placeholders": { + "issuer": { + "example": "privacyIDEA" + }, + "account": { + "example": "GitHub" + } + } + } } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 84290376b..282f949ea 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -557,5 +557,17 @@ "patchNotesDialogTitle": "¿Qué hay de nuevo?", "version": "Versión", "noMailAppTitle": "No hay aplicación de correo electrónico", - "noMailAppDescription": "No hay ninguna app de correo electrónico instalada o inicializada en este dispositivo, inténtalo de nuevo cuando puedas enviar un mensaje de correo electrónico." + "noMailAppDescription": "No hay ninguna app de correo electrónico instalada o inicializada en este dispositivo, inténtalo de nuevo cuando puedas enviar un mensaje de correo electrónico.", + "authenticationRequest": "Autenticación", + "requestInfo": "Enviado por {issuer} para su cuenta: \"{account}\"", + "@requestInfo": { + "placeholders": { + "issuer": { + "example": "privacyIDEA" + }, + "account": { + "example": "GitHub" + } + } + } } \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 285878cce..d15cd3053 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -562,5 +562,17 @@ "patchNotesDialogTitle": "Quoi de neuf ?", "version": "Version", "noMailAppTitle": "Aucune application de messagerie trouvée", - "noMailAppDescription": "Aucune application de messagerie n'est installée ou initialisée sur cet appareil. Veuillez réessayer lorsque vous serez en mesure d'envoyer un message électronique." + "noMailAppDescription": "Aucune application de messagerie n'est installée ou initialisée sur cet appareil. Veuillez réessayer lorsque vous serez en mesure d'envoyer un message électronique.", + "authenticationRequest": "Authentification", + "requestInfo": "Envoyé par {issuer} pour votre compte : \"{account}\"", + "@requestInfo": { + "placeholders": { + "issuer": { + "exemple": "privacyIDEA" + }, + "account": { + "exemple": "GitHub" + } + } + } } \ No newline at end of file diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 575d0a537..b9db77f7e 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1380,6 +1380,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.'** String get noMailAppDescription; + + /// No description provided for @authenticationRequest. + /// + /// In en, this message translates to: + /// **'Authentication request'** + String get authenticationRequest; + + /// Description of the authentication request. + /// + /// In en, this message translates to: + /// **'Sent by {issuer} for your account: \"{account}\"'** + String requestInfo(Object issuer, Object account); } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/app_localizations_cs.dart b/lib/l10n/app_localizations_cs.dart index ac71df4b0..301284cff 100644 --- a/lib/l10n/app_localizations_cs.dart +++ b/lib/l10n/app_localizations_cs.dart @@ -701,4 +701,12 @@ class AppLocalizationsCs extends AppLocalizations { @override String get noMailAppDescription => 'There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.'; + + @override + String get authenticationRequest => 'Žádost o ověření'; + + @override + String requestInfo(Object issuer, Object account) { + return 'Odesláno $issuer pro váš účet: \"$account\"'; + } } diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index da8ee8663..41e21965b 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -701,4 +701,12 @@ class AppLocalizationsDe extends AppLocalizations { @override String get noMailAppDescription => 'Auf diesem Gerät ist keine E-Mail-App installiert oder initialisiert, bitte versuchen Sie es erneut, wenn Sie eine E-Mail-Nachricht senden können.'; + + @override + String get authenticationRequest => 'Authentifizierung'; + + @override + String requestInfo(Object issuer, Object account) { + return 'Gesendet von $issuer für Ihr Konto: \"$account\"'; + } } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 842a64540..3d4c40998 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -701,4 +701,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get noMailAppDescription => 'There is no e-mail app installed or initialised on this device, please try again when you are able to send an email message.'; + + @override + String get authenticationRequest => 'Authentication request'; + + @override + String requestInfo(Object issuer, Object account) { + return 'Sent by $issuer for your account: \"$account\"'; + } } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 975ec51f2..fe1b6295b 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -701,4 +701,12 @@ class AppLocalizationsEs extends AppLocalizations { @override String get noMailAppDescription => 'No hay ninguna app de correo electrónico instalada o inicializada en este dispositivo, inténtalo de nuevo cuando puedas enviar un mensaje de correo electrónico.'; + + @override + String get authenticationRequest => 'Autenticación'; + + @override + String requestInfo(Object issuer, Object account) { + return 'Enviado por $issuer para su cuenta: \"$account\"'; + } } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 5b544adb5..b7ff37c6d 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -701,4 +701,12 @@ class AppLocalizationsFr extends AppLocalizations { @override String get noMailAppDescription => 'Aucune application de messagerie n\'est installée ou initialisée sur cet appareil. Veuillez réessayer lorsque vous serez en mesure d\'envoyer un message électronique.'; + + @override + String get authenticationRequest => 'Authentification'; + + @override + String requestInfo(Object issuer, Object account) { + return 'Envoyé par $issuer pour votre compte : \"$account\"'; + } } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 8859231c2..9aad45cba 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -701,4 +701,12 @@ class AppLocalizationsNl extends AppLocalizations { @override String get noMailAppDescription => 'Er is geen e-mail app geïnstalleerd of geïnitialiseerd op dit apparaat, probeer het opnieuw wanneer u in staat bent om een e-mailbericht te verzenden.'; + + @override + String get authenticationRequest => 'Verificatieverzoek'; + + @override + String requestInfo(Object issuer, Object account) { + return 'Verzonden door $issuer voor uw account: \"$account\"'; + } } diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 2c760d6d6..4b64bc218 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -701,4 +701,12 @@ class AppLocalizationsPl extends AppLocalizations { @override String get noMailAppDescription => 'Na tym urządzeniu nie zainstalowano ani nie zainicjowano aplikacji poczty e-mail, spróbuj ponownie, gdy będziesz w stanie wysłać wiadomość e-mail'; + + @override + String get authenticationRequest => 'Żądanie uwierzytelnienia'; + + @override + String requestInfo(Object issuer, Object account) { + return 'Wysłane przez $issuer dla twojego konta: \"$account\"'; + } } diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 824c4318e..b2497e848 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -558,5 +558,17 @@ "patchNotesDialogTitle": "Wat is er nieuw?", "version": "Versie", "noMailAppTitle": "Geen mail app gevonden", - "noMailAppDescription": "Er is geen e-mail app geïnstalleerd of geïnitialiseerd op dit apparaat, probeer het opnieuw wanneer u in staat bent om een e-mailbericht te verzenden." + "noMailAppDescription": "Er is geen e-mail app geïnstalleerd of geïnitialiseerd op dit apparaat, probeer het opnieuw wanneer u in staat bent om een e-mailbericht te verzenden.", + "authenticationRequest": "Verificatieverzoek", + "requestInfo": "Verzonden door {issuer} voor uw account: \"{account}\"", + "@requestInfo": { + "placeholders": { + "issuer": { + "example": "privacyIDEA" + }, + "account": { + "example": "GitHub" + } + } + } } \ No newline at end of file diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 76523422b..59fa86e45 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -555,5 +555,17 @@ "patchNotesDialogTitle": "Co nowego?", "version": "Wersja", "noMailAppTitle": "Nie znaleziono aplikacji pocztowej", - "noMailAppDescription": "Na tym urządzeniu nie zainstalowano ani nie zainicjowano aplikacji poczty e-mail, spróbuj ponownie, gdy będziesz w stanie wysłać wiadomość e-mail" + "noMailAppDescription": "Na tym urządzeniu nie zainstalowano ani nie zainicjowano aplikacji poczty e-mail, spróbuj ponownie, gdy będziesz w stanie wysłać wiadomość e-mail", + "authenticationRequest": "Żądanie uwierzytelnienia", + "requestInfo": "Wysłane przez {issuer} dla twojego konta: \"{account}\"", + "@requestInfo": { + "placeholders": { + "issuer": { + "example": "privacyIDEA" + }, + "account": { + "example": "GitHub" + } + } + } } \ No newline at end of file diff --git a/lib/model/states/token_state.dart b/lib/model/states/token_state.dart index 7518b0ea7..2295f7a74 100644 --- a/lib/model/states/token_state.dart +++ b/lib/model/states/token_state.dart @@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; import '../../utils/logger.dart'; import '../enums/push_token_rollout_state.dart'; import '../token_folder.dart'; -import '../tokens/hotp_token.dart'; import '../tokens/otp_token.dart'; import '../tokens/push_token.dart'; import '../tokens/token.dart'; @@ -17,9 +16,6 @@ class TokenState { List get otpTokens => tokens.whereType().toList(); bool get hasOTPTokens => otpTokens.isNotEmpty; - List get hotpTokens => tokens.whereType().toList(); - bool get hasHOTPTokens => hotpTokens.isNotEmpty; - List get pushTokens => tokens.whereType().toList(); bool get hasPushTokens => pushTokens.isNotEmpty; bool get hasRolledOutPushTokens => pushTokens.any((element) => element.isRolledOut); diff --git a/lib/state_notifiers/token_notifier.dart b/lib/state_notifiers/token_notifier.dart index 65e03242f..2dcc44bb0 100644 --- a/lib/state_notifiers/token_notifier.dart +++ b/lib/state_notifiers/token_notifier.dart @@ -131,6 +131,7 @@ class TokenNotifier extends StateNotifier { return state; }); final failedTokens = (await loadingRepo).lastlyUpdatedTokens; + await _handlePushTokensIfExist(); return failedTokens.isEmpty; } @@ -563,7 +564,15 @@ class TokenNotifier extends StateNotifier { Future _handlePushTokensIfExist() async { await loadingRepo; - if (state.hasRolledOutPushTokens) checkNotificationPermission(); + if (state.hasPushTokens == false) { + if (globalRef?.read(settingsProvider).hidePushTokens == true) { + globalRef!.read(settingsProvider.notifier).setHidePushTokens(false); + } + return; + } + if (state.hasRolledOutPushTokens) { + checkNotificationPermission(); + } for (final element in state.pushTokensToRollOut) { Logger.info('Handling push token "${element.id}"', name: 'token_notifier.dart#_handlePushTokensIfExist'); await rolloutPushToken(element); diff --git a/lib/utils/globals.dart b/lib/utils/globals.dart index 7c7a7c86e..f6f671ce0 100644 --- a/lib/utils/globals.dart +++ b/lib/utils/globals.dart @@ -42,6 +42,7 @@ Map>> getLocalizedPatchNotes(AppLocaliz final globalSnackbarKey = GlobalKey(); final globalNavigatorKey = GlobalKey(); final Future> contextedGlobalNavigatorKey = Future(() async => await _getContextedGlobalNavigatorKey()); +final Future globalContext = Future(() async => await _getContextedGlobalNavigatorKey()).then((value) => value.currentContext!); Future> _getContextedGlobalNavigatorKey() async { if (globalNavigatorKey.currentContext != null) { return globalNavigatorKey; diff --git a/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart b/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart index 9f256f9a4..1d975b0cd 100644 --- a/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart +++ b/lib/views/main_view/main_view_widgets/main_view_tokens_list.dart @@ -35,7 +35,7 @@ class _MainViewTokensListState extends ConsumerState { final tokenState = ref.watch(tokenProvider); final allowToRefresh = tokenState.hasPushTokens; final draggingSortable = ref.watch(draggingSortableProvider); - bool filterPushTokens = ref.watch(settingsProvider).hidePushTokens && tokenState.hasHOTPTokens; + bool filterPushTokens = ref.watch(settingsProvider).hidePushTokens && tokenState.hasOTPTokens; final tokenStateWithNoFolder = tokenState.tokensWithoutFolder(exclude: filterPushTokens ? [PushToken] : []); diff --git a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart index 6973fd141..c201ed460 100644 --- a/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart +++ b/lib/views/main_view/main_view_widgets/token_widgets/day_password_token_widgets/day_password_token_widget_tile.dart @@ -2,8 +2,10 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; +import 'package:privacyidea_authenticator/widgets/custom_trailing.dart'; import '../../../../../l10n/app_localizations.dart'; import '../../../../../model/enums/day_passoword_token_view_mode.dart'; @@ -91,52 +93,31 @@ class _DayPasswordTokenWidgetTileState extends ConsumerState p0.copyWith(viewMode: DayPasswordTokenViewMode.VALIDUNTIL)); - return; - } - if (widget.token.viewMode == DayPasswordTokenViewMode.VALIDUNTIL) { - globalRef?.read(tokenProvider.notifier).updateToken(widget.token, (p0) => p0.copyWith(viewMode: DayPasswordTokenViewMode.VALIDFOR)); - return; - } - }, - child: switch (widget.token.viewMode) { - DayPasswordTokenViewMode.VALIDFOR => Column( + trailing: CustomTrailing( + padding: const EdgeInsets.all(0), + fit: BoxFit.none, + child: GestureDetector( + behavior: HitTestBehavior.deferToChild, + onTap: () { + if (widget.token.viewMode == DayPasswordTokenViewMode.VALIDFOR) { + globalRef?.read(tokenProvider.notifier).updateToken(widget.token, (p0) => p0.copyWith(viewMode: DayPasswordTokenViewMode.VALIDUNTIL)); + return; + } + if (widget.token.viewMode == DayPasswordTokenViewMode.VALIDUNTIL) { + globalRef?.read(tokenProvider.notifier).updateToken(widget.token, (p0) => p0.copyWith(viewMode: DayPasswordTokenViewMode.VALIDFOR)); + return; + } + }, + child: SizedBox( + height: Theme.of(context).textTheme.bodyLarge!.fontSize! * (Theme.of(context).textTheme.bodyLarge?.height ?? 1.2) * 3.1, + child: Column( children: [ - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - '${AppLocalizations.of(context)!.validFor}:', - style: Theme.of(context).listTileTheme.subtitleTextStyle, - textAlign: TextAlign.center, - overflow: TextOverflow.fade, - softWrap: false, - ), - ), Expanded( - child: FittedBox( - fit: BoxFit.scaleDown, - child: Text( - durationString, - style: Theme.of(context).textTheme.bodyLarge, - textAlign: TextAlign.center, - overflow: TextOverflow.fade, - softWrap: false, - ), - ), - ), - ], - ), - DayPasswordTokenViewMode.VALIDUNTIL => Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - FittedBox( - fit: BoxFit.scaleDown, child: Text( - '${AppLocalizations.of(context)!.validUntil}:', + switch (widget.token.viewMode) { + DayPasswordTokenViewMode.VALIDFOR => '${AppLocalizations.of(context)!.validFor}:', + DayPasswordTokenViewMode.VALIDUNTIL => '${AppLocalizations.of(context)!.validUntil}:', + }, style: Theme.of(context).listTileTheme.subtitleTextStyle, textAlign: TextAlign.center, overflow: TextOverflow.fade, @@ -144,10 +125,14 @@ class _DayPasswordTokenWidgetTileState extends ConsumerState durationString, + DayPasswordTokenViewMode.VALIDUNTIL => '$yMdString\n$ejmString', + }, style: Theme.of(context).textTheme.bodyLarge, textAlign: TextAlign.center, overflow: TextOverflow.fade, @@ -158,7 +143,8 @@ class _DayPasswordTokenWidgetTileState extends ConsumerState().where((e) => e.isRolledOut).toList(); final unsupported = enrolledPushTokenList.where((e) => e.url == null).toList(); - final showPushSettingsGroup = enrolledPushTokenList.isNotEmpty; + final enablePushSettingsGroup = enrolledPushTokenList.isNotEmpty; return PushRequestListener( child: Scaffold( @@ -195,94 +195,94 @@ class SettingsView extends ConsumerView { ), ], ), - Visibility( - visible: showPushSettingsGroup, - child: SettingsGroup( - title: AppLocalizations.of(context)!.pushToken, - children: [ - ListTile( - title: Text( - AppLocalizations.of(context)!.synchronizePushTokens, - style: Theme.of(context).textTheme.titleMedium, - ), - subtitle: Text( - AppLocalizations.of(context)!.synchronizesTokensWithServer, + SettingsGroup( + isActive: enablePushSettingsGroup, + title: AppLocalizations.of(context)!.pushToken, + children: [ + ListTile( + title: Text( + AppLocalizations.of(context)!.synchronizePushTokens, + style: Theme.of(context).textTheme.titleMedium, + ), + subtitle: Text( + AppLocalizations.of(context)!.synchronizesTokensWithServer, + overflow: TextOverflow.fade, + ), + trailing: ElevatedButton( + onPressed: enablePushSettingsGroup + ? () { + showDialog( + useRootNavigator: false, + context: context, + barrierDismissible: false, + builder: (context) => const UpdateFirebaseTokenDialog(), + ); + } + : null, + child: Text( + AppLocalizations.of(context)!.sync, overflow: TextOverflow.fade, + softWrap: false, ), - trailing: ElevatedButton( - child: Text( - AppLocalizations.of(context)!.sync, - overflow: TextOverflow.fade, - softWrap: false, - ), - onPressed: () { - showDialog( - useRootNavigator: false, - context: context, - barrierDismissible: false, - builder: (context) => const UpdateFirebaseTokenDialog(), - ); - }, + ), + ), + ListTile( + title: RichText( + text: TextSpan( + children: [ + TextSpan( + text: AppLocalizations.of(context)!.enablePolling, + style: Theme.of(context).textTheme.titleMedium, + ), + // Add clickable icon to inform user of unsupported push tokens (for polling) + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(left: 10), + child: unsupported.isNotEmpty && enrolledPushTokenList.isNotEmpty + ? GestureDetector( + onTap: () {}, // () => _showPollingInfo(unsupported), + child: const Icon( + Icons.info_outline, + color: Colors.red, + ), + ) + : null, + ), + ), + ], ), ), + subtitle: Text( + AppLocalizations.of(context)!.requestPushChallengesPeriodically, + overflow: TextOverflow.fade, + ), + trailing: Switch( + value: ref.watch(settingsProvider).enablePolling, + onChanged: enablePushSettingsGroup ? (value) => ref.read(settingsProvider.notifier).setPolling(value) : null, + ), + ), + if (ref.watch(tokenProvider).hasOTPTokens) ListTile( title: RichText( text: TextSpan( children: [ TextSpan( - text: AppLocalizations.of(context)!.enablePolling, + text: AppLocalizations.of(context)!.hidePushTokens, style: Theme.of(context).textTheme.titleMedium, ), - // Add clickable icon to inform user of unsupported push tokens (for polling) - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(left: 10), - child: unsupported.isNotEmpty && enrolledPushTokenList.isNotEmpty - ? GestureDetector( - onTap: () {}, // () => _showPollingInfo(unsupported), - child: const Icon( - Icons.info_outline, - color: Colors.red, - ), - ) - : null, - ), - ), ], ), ), subtitle: Text( - AppLocalizations.of(context)!.requestPushChallengesPeriodically, + AppLocalizations.of(context)!.hidePushTokensDescription, overflow: TextOverflow.fade, ), trailing: Switch( - value: ref.watch(settingsProvider).enablePolling, - onChanged: (value) => ref.read(settingsProvider.notifier).setPolling(value), + value: ref.watch(settingsProvider).hidePushTokens, + onChanged: enablePushSettingsGroup ? (value) => ref.read(settingsProvider.notifier).setHidePushTokens(value) : null, ), - ), - if (ref.watch(tokenProvider).hasOTPTokens) - ListTile( - title: RichText( - text: TextSpan( - children: [ - TextSpan( - text: AppLocalizations.of(context)!.hidePushTokens, - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ), - ), - subtitle: Text( - AppLocalizations.of(context)!.hidePushTokensDescription, - overflow: TextOverflow.fade, - ), - trailing: Switch( - value: ref.watch(settingsProvider).hidePushTokens, - onChanged: (value) => ref.read(settingsProvider.notifier).setHidePushTokens(value), - ), - ) - ], - ), + ) + ], ), const Divider(), SettingsGroup( diff --git a/lib/views/settings_view/settings_view_widgets/settings_groups.dart b/lib/views/settings_view/settings_view_widgets/settings_groups.dart index 6aff215b8..a44ea81dd 100644 --- a/lib/views/settings_view/settings_view_widgets/settings_groups.dart +++ b/lib/views/settings_view/settings_view_widgets/settings_groups.dart @@ -24,10 +24,12 @@ import 'package:flutter/material.dart'; class SettingsGroup extends StatelessWidget { final String _title; final List _children; + final bool _isActive; - const SettingsGroup({super.key, required String title, required List children}) + const SettingsGroup({super.key, required String title, required List children, bool isActive = true}) : _title = title, - _children = children; + _children = children, + _isActive = isActive; @override Widget build(BuildContext context) { @@ -39,7 +41,7 @@ class SettingsGroup extends StatelessWidget { dense: true, leading: Text( _title, - style: theme.textTheme.titleMedium!.copyWith(color: theme.colorScheme.secondary, fontWeight: FontWeight.bold), + style: theme.textTheme.titleLarge?.copyWith(color: _isActive ? null : Colors.grey), overflow: TextOverflow.fade, softWrap: false, ), diff --git a/lib/widgets/custom_trailing.dart b/lib/widgets/custom_trailing.dart index 24fd7c18d..e1185164a 100644 --- a/lib/widgets/custom_trailing.dart +++ b/lib/widgets/custom_trailing.dart @@ -6,11 +6,13 @@ class CustomTrailing extends StatelessWidget { final Widget child; final double maxPercentWidth; final double maxPixelsWidth; + final EdgeInsetsGeometry? padding; + final BoxFit fit; /// Creates a widget that limits the width of [child] to [maxPercentWidth] of /// the parent width or [maxPixelsWidth] if the parent width is too small. /// Defaults: [maxPercentWidth] = 27.5, [maxPixelsWidth] = 85 - const CustomTrailing({required this.child, super.key, double? maxPercentWidth, double? maxPixelsWidth}) + const CustomTrailing({required this.child, super.key, double? maxPercentWidth, double? maxPixelsWidth, this.padding, this.fit = BoxFit.contain}) : maxPercentWidth = maxPercentWidth ?? 27.5, maxPixelsWidth = maxPixelsWidth ?? 85; @@ -22,10 +24,10 @@ class CustomTrailing extends StatelessWidget { width: boxSize, height: boxSize, child: Padding( - padding: EdgeInsets.all(boxSize / 12), + padding: padding ?? EdgeInsets.all(boxSize / 16), child: FittedBox( - fit: BoxFit.contain, - child: Center(child: child), + fit: fit, + child: child, ), ), ); diff --git a/lib/widgets/dialog_widgets/default_dialog.dart b/lib/widgets/dialog_widgets/default_dialog.dart index 37620a4de..b339a3bf7 100644 --- a/lib/widgets/dialog_widgets/default_dialog.dart +++ b/lib/widgets/dialog_widgets/default_dialog.dart @@ -8,6 +8,8 @@ class DefaultDialog extends StatelessWidget { final List? actions; final MainAxisAlignment? actionsAlignment; final Widget? content; + final bool hasCloseButton; + final double closeButtonSize = 22; const DefaultDialog({ this.scrollable, @@ -15,6 +17,7 @@ class DefaultDialog extends StatelessWidget { this.actions, this.actionsAlignment, this.content, + this.hasCloseButton = false, super.key, }); @@ -36,9 +39,25 @@ class DefaultDialog extends StatelessWidget { buttonPadding: const EdgeInsets.fromLTRB(8, 0, 8, 8), insetPadding: const EdgeInsets.fromLTRB(16, 32, 16, 12), titlePadding: const EdgeInsets.all(12), - contentPadding: const EdgeInsets.fromLTRB(12, 0, 12, 12), + contentPadding: const EdgeInsets.all(16), elevation: 2, - title: title, + title: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: title ?? const SizedBox()), + if (hasCloseButton) + SizedBox( + width: closeButtonSize, + height: closeButtonSize, + child: IconButton( + padding: EdgeInsets.zero, + icon: Icon(Icons.close, size: closeButtonSize), + splashRadius: closeButtonSize, + onPressed: () => Navigator.of(context).pop(), + ), + ), + ], + ), actions: actions, content: content, ), diff --git a/lib/widgets/dialog_widgets/push_request_dialog.dart b/lib/widgets/dialog_widgets/push_request_dialog.dart index 9b2702dea..baca03212 100644 --- a/lib/widgets/dialog_widgets/push_request_dialog.dart +++ b/lib/widgets/dialog_widgets/push_request_dialog.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:privacyidea_authenticator/extensions/color_extension.dart'; import '../../l10n/app_localizations.dart'; import '../../model/tokens/push_token.dart'; @@ -20,44 +21,68 @@ class PushRequestDialog extends StatefulWidget { } class _PushRequestDialogState extends State { + static const titleScale = 1.35; + static const questionScale = 1.1; + double get lineHeight => Theme.of(context).textTheme.titleLarge?.fontSize ?? 16; + bool isHandled = false; + bool dialogIsOpen = false; + + @override + void dispose() { + if (dialogIsOpen) { + WidgetsBinding.instance.addPostFrameCallback((_) async { + int popCount = 0; + Navigator.of(await globalContext).popUntil((route) { + popCount++; + return route.isFirst || popCount > 1; + }); + }); + } + super.dispose(); + } @override Widget build(BuildContext context) { - final lineHeight = Theme.of(context).textTheme.titleLarge!.fontSize!; + final lineHeight = this.lineHeight; + final question = widget.tokenWithPushRequest.pushRequests.peek()?.question; return isHandled ? const SizedBox() : Container( color: Colors.transparent, child: DefaultDialog( title: Text( - widget.tokenWithPushRequest.label, - textAlign: TextAlign.center, + AppLocalizations.of(context)!.authenticationRequest, style: Theme.of(context).textTheme.titleLarge!, + textAlign: TextAlign.center, + textScaler: const TextScaler.linear(titleScale), ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - SizedBox( - height: lineHeight, - child: Text( - widget.tokenWithPushRequest.pushRequests.peek()?.title ?? '', - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, + Text( + AppLocalizations.of(context)!.requestInfo( + widget.tokenWithPushRequest.issuer, + widget.tokenWithPushRequest.label, ), + style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: Theme.of(context).textTheme.titleMedium?.fontSize), + textScaler: const TextScaler.linear(questionScale), + textAlign: TextAlign.center, ), - SizedBox( - height: lineHeight, - child: Text( - widget.tokenWithPushRequest.pushRequests.peek()?.question ?? '', - style: Theme.of(context).textTheme.bodyMedium, + SizedBox(height: lineHeight), + if (question != null) ...[ + Text( + question, + style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: Theme.of(context).textTheme.titleMedium?.fontSize), + textScaler: const TextScaler.linear(questionScale), textAlign: TextAlign.center, ), - ), - SizedBox(height: lineHeight * 0.5), + SizedBox(height: lineHeight), + ], SizedBox( - height: lineHeight * 3, + // Accept button + height: lineHeight * titleScale * 2 + 16, child: PressButton( onPressed: () async { if (widget.tokenWithPushRequest.isLocked && @@ -66,39 +91,29 @@ class _PushRequestDialogState extends State { if (mounted) setState(() => isHandled = true); }, child: Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Expanded(flex: 2, child: SizedBox()), - Expanded( - flex: 4, - child: FittedBox( - fit: BoxFit.scaleDown, - child: Text( - AppLocalizations.of(context)!.accept, - textAlign: TextAlign.left, - style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontSize: lineHeight * 1.2), - ), - ), + Text( + AppLocalizations.of(context)!.accept, + style: Theme.of(context).textTheme.titleLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimary), + textScaler: const TextScaler.linear(titleScale), + textAlign: TextAlign.center, + maxLines: 1, ), - const Expanded(child: SizedBox()), - Expanded( - flex: 2, - child: FittedBox( - fit: BoxFit.scaleDown, - child: Icon( - Icons.check_outlined, - size: lineHeight * 1.5, - ), - ), + Icon( + Icons.check_outlined, + size: lineHeight * titleScale, ), - const Expanded(flex: 2, child: SizedBox()), ], ), ), ), SizedBox(height: lineHeight * 0.5), SizedBox( - height: lineHeight * 2, + // Decline button + height: lineHeight * titleScale + 16, child: PressButton( style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.errorContainer)), onPressed: () async { @@ -106,38 +121,22 @@ class _PushRequestDialogState extends State { await lockAuth(localizedReason: AppLocalizations.of(context)!.authToDeclinePushRequest) == false) { return; } - _showConfirmationDialog(widget.tokenWithPushRequest); + dialogIsOpen = true; + await _showConfirmationDialog(widget.tokenWithPushRequest); + dialogIsOpen = false; }, child: Row( mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Expanded(flex: 2, child: SizedBox()), - Expanded( - flex: 4, - child: FittedBox( - fit: BoxFit.scaleDown, - child: Text( - AppLocalizations.of(context)!.decline, - textAlign: TextAlign.left, - style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontSize: lineHeight * 1.2), - ), - ), - ), - const Expanded( - child: SizedBox(), - ), - Expanded( - flex: 2, - child: FittedBox( - fit: BoxFit.scaleDown, - child: Icon( - Icons.close_outlined, - size: lineHeight * 1.5, - ), - ), + Text( + AppLocalizations.of(context)!.decline, + style: Theme.of(context).textTheme.titleLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimary), + textScaler: const TextScaler.linear(titleScale), + textAlign: TextAlign.center, ), - const Expanded(flex: 2, child: SizedBox()), + Icon(Icons.close_outlined, size: lineHeight * titleScale), ], )), ), @@ -147,124 +146,102 @@ class _PushRequestDialogState extends State { ); } - void _showConfirmationDialog(PushToken token) => showDialog( + Future _showConfirmationDialog(PushToken token) => showDialog( useRootNavigator: false, context: globalNavigatorKey.currentContext!, builder: (BuildContext context) { - return BackdropFilter( - filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), - child: Material( - color: Colors.transparent, - child: Center( - child: Container( - margin: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - borderRadius: BorderRadius.circular(10), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, + final lineHeight = this.lineHeight; + return DefaultDialog( + title: Text( + AppLocalizations.of(context)!.authenticationRequest, + style: Theme.of(context).textTheme.titleLarge!, + textAlign: TextAlign.center, + textScaler: const TextScaler.linear(titleScale), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + AppLocalizations.of(context)!.requestTriggerdByUserQuestion, + style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontSize: Theme.of(context).textTheme.titleMedium?.fontSize), + textScaler: const TextScaler.linear(questionScale), + textAlign: TextAlign.center, + ), + SizedBox(height: lineHeight), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Expanded(child: SizedBox()), + Expanded( + flex: 6, + child: PressButton( + onPressed: () { + globalRef?.read(pushRequestProvider.notifier).declinePop(token); + Navigator.of(context).pop(); + if (mounted) setState(() => isHandled = true); + }, + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.fromLTRB(16.0, 24.0, 0, 16.0), - child: Column( - children: [ - Text( - AppLocalizations.of(context)!.requestTriggerdByUserQuestion, - style: Theme.of(context).textTheme.titleLarge, + Text( + AppLocalizations.of(context)!.yes, + style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimary), + textScaler: const TextScaler.linear(titleScale), + textAlign: TextAlign.center, + ), + FittedBox( + fit: BoxFit.scaleDown, + child: Text( + AppLocalizations.of(context)!.butDiscardIt, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onPrimary.mixWith(Colors.grey.shade800), ), - ], - ), + textAlign: TextAlign.center, + softWrap: false, ), ), - SizedBox( - height: 60, - child: Align( - alignment: Alignment.topCenter, - child: IconButton( - onPressed: () => Navigator.of(context).pop(), - icon: const Icon(Icons.close, size: 20), - ), - ), + ], + ), + ), + ), + const Expanded(flex: 2, child: SizedBox()), + Expanded( + flex: 6, + child: PressButton( + style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.errorContainer)), + onPressed: () { + //TODO: Notify issuer + globalRef?.read(pushRequestProvider.notifier).declinePop(token); + Navigator.of(context).pop(); + if (mounted) setState(() => isHandled = true); + }, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AppLocalizations.of(context)!.no, + style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: Theme.of(context).colorScheme.onPrimary), + textScaler: const TextScaler.linear(titleScale), + textAlign: TextAlign.center, + ), + Text( + AppLocalizations.of(context)!.declineIt, + style: + Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).colorScheme.onPrimary.mixWith(Colors.grey.shade800)), + textAlign: TextAlign.center, + softWrap: false, ), ], ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 8.0), - child: Row( - children: [ - Expanded( - flex: 3, - child: PressButton( - onPressed: () { - globalRef?.read(pushRequestProvider.notifier).declinePop(token); - Navigator.of(context).pop(); - if (mounted) setState(() => isHandled = true); - }, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AppLocalizations.of(context)!.yes, - style: Theme.of(context).textTheme.bodyLarge, - textAlign: TextAlign.center, - ), - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - AppLocalizations.of(context)!.butDiscardIt, - style: Theme.of(context).textTheme.bodySmall, - textAlign: TextAlign.center, - softWrap: false, - ), - ), - ], - ), - ), - ), - const Expanded(child: SizedBox()), - Expanded( - flex: 3, - child: PressButton( - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Theme.of(context).colorScheme.errorContainer)), - onPressed: () { - //TODO: Notify issuer - globalRef?.read(pushRequestProvider.notifier).declinePop(token); - Navigator.of(context).pop(); - if (mounted) setState(() => isHandled = true); - }, - child: Column( - children: [ - Text( - AppLocalizations.of(context)!.no, - style: Theme.of(context).textTheme.bodyLarge, - textAlign: TextAlign.center, - ), - FittedBox( - child: Text( - AppLocalizations.of(context)!.declineIt, - style: Theme.of(context).textTheme.bodySmall, - textAlign: TextAlign.center, - softWrap: false, - ), - ), - ], - ), - ), - ), - ], - ), - ) - ], + ), ), - ), + const Expanded(child: SizedBox()), + ], ), - )); + ], + ), + ); }); } diff --git a/pubspec.yaml b/pubspec.yaml index 9f874ea52..ebbdaaf37 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ publish_to: none # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 4.3.0+403004 # TODO Set the right version number +version: 4.3.0+403005 # TODO Set the right version number # version: major.minor.build + 2x major|2x minor|3x build # version: version number + build number (optional) # android: build-name + versionCode