From fa37316b524c49f4d01903b03a7fce9700c55298 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Wed, 14 Sep 2022 12:03:36 +0200 Subject: [PATCH] update beta dialog code to follow common pattern --- .../android/beta_welcome_dialog_test.dart | 23 +- integration_test/android/util.dart | 14 +- lib/android/init.dart | 4 +- lib/android/views/beta_dialog.dart | 219 +++++++++++------- lib/app/views/main_page.dart | 1 + 5 files changed, 152 insertions(+), 109 deletions(-) diff --git a/integration_test/android/beta_welcome_dialog_test.dart b/integration_test/android/beta_welcome_dialog_test.dart index 25dbf48b..c130b1dd 100644 --- a/integration_test/android/beta_welcome_dialog_test.dart +++ b/integration_test/android/beta_welcome_dialog_test.dart @@ -3,7 +3,7 @@ import 'package:integration_test/integration_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:yubico_authenticator/android/keys.dart' as keys; -import '../test_util.dart'; +import '../android/util.dart' as android_test_util; import 'constants.dart'; void main() { @@ -11,29 +11,28 @@ void main() { binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; group('Beta welcome dialog', () { - - // this is here to make sure yubikey is connected before we test the dialog - testWidgets('startup', (WidgetTester tester) async { - await tester.startUp({ - 'dlg.beta.enabled': false, - }); - }); - testWidgets('shows welcome screen', (WidgetTester tester) async { - await tester.startUp({ + await android_test_util.startUp(tester, { 'dlg.beta.enabled': true, + 'needs_yubikey': false, }); expect(find.byKey(keys.betaDialogView), findsOneWidget); }); testWidgets('does not show welcome dialog', (WidgetTester tester) async { - await tester.startUp(); + await android_test_util.startUp(tester, { + 'dlg.beta.enabled': false, + 'needs_yubikey': false, + }); expect(find.byKey(keys.betaDialogView), findsNothing); }); testWidgets('updates preferences', (WidgetTester tester) async { - await tester.startUp({'dlg.beta.enabled': true}); + await android_test_util.startUp(tester, { + 'dlg.beta.enabled': true, + 'needs_yubikey': false, + }); var prefs = await SharedPreferences.getInstance(); await tester.tap(find.byKey(keys.okButton)); await expectLater(prefs.getBool(betaDialogPrefName), equals(false)); diff --git a/integration_test/android/util.dart b/integration_test/android/util.dart index 28dc74b8..1556b848 100644 --- a/integration_test/android/util.dart +++ b/integration_test/android/util.dart @@ -22,11 +22,15 @@ Future startUp(WidgetTester tester, await tester.pumpWidget(await initialize()); - // wait for a YubiKey connection - await tester.waitForFinder(find.descendant( - of: tester.findDeviceButton(), - matching: find.byWidgetPredicate((widget) => - widget is DeviceAvatar && widget.key != app_keys.noDeviceAvatar))); + // only wait for yubikey connection when needed + // needs_yubikey defaults to true + if (startUpParams['needs_yubikey'] != false) { + // wait for a YubiKey connection + await tester.waitForFinder(find.descendant( + of: tester.findDeviceButton(), + matching: find.byWidgetPredicate((widget) => + widget is DeviceAvatar && widget.key != app_keys.noDeviceAvatar))); + } await tester.pump(const Duration(milliseconds: 500)); } diff --git a/lib/android/init.dart b/lib/android/init.dart index 8c263c5d..216e35dd 100644 --- a/lib/android/init.dart +++ b/lib/android/init.dart @@ -65,8 +65,8 @@ Future initialize() async { /// initializes global handler for dialogs ref.read(androidDialogProvider); - var betaDialog = BetaDialog(context, ref); - betaDialog.request(); + /// if the beta dialog was not shown yet, this will show it + requestBetaDialog(ref); return const MainPage(); }, diff --git a/lib/android/views/beta_dialog.dart b/lib/android/views/beta_dialog.dart index 14311ca9..ebe3fda9 100644 --- a/lib/android/views/beta_dialog.dart +++ b/lib/android/views/beta_dialog.dart @@ -2,103 +2,142 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../app/message.dart'; +import '../../app/state.dart'; import '../../core/state.dart'; import '../keys.dart' as keys; -class BetaDialog { - final BuildContext context; - final WidgetRef ref; +void requestBetaDialog(WidgetRef ref) async { + const String prefBetaDialogShouldBeShown = 'prefBetaDialogShouldBeShown'; + var sharedPrefs = ref.read(prefProvider); + await sharedPrefs.reload(); + var dialogShouldBeShown = + sharedPrefs.getBool(prefBetaDialogShouldBeShown) ?? true; + if (dialogShouldBeShown) { + final withContext = ref.read(withContextProvider); - const BetaDialog(this.context, this.ref); - - void request() { - WidgetsBinding.instance.addPostFrameCallback((_) async { - var sharedPrefs = ref.read(prefProvider); - await sharedPrefs.reload(); - var dialogShouldBeShown = - sharedPrefs.getBool(prefBetaDialogShouldBeShown) ?? true; - if (dialogShouldBeShown) { - Future.delayed(const Duration(milliseconds: 100), () async { - await showBetaDialog(); - }); - } - }); - } - - Future showBetaDialog() async { - await showBlurDialog( - context: context, - builder: (context) { - final isDarkTheme = Theme.of(context).brightness == Brightness.dark; - return WillPopScope( - onWillPop: () async => false, - child: AlertDialog( - key: keys.betaDialogView, - content: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Image.asset( - isDarkTheme - ? 'assets/graphics/beta-dark.png' - : 'assets/graphics/beta-light.png', - alignment: Alignment.topCenter, - height: 124, - filterQuality: FilterQuality.medium, - ), - const SizedBox(height: 24), - Text( - 'Welcome to Yubico Authenticator Beta!', - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: isDarkTheme ? Colors.white : Colors.black), - textAlign: TextAlign.center, - ), - const SizedBox(height: 24), - const Text( - '• Preview the latest beta: Try out the newest features. (Sometimes these may be a little rough around the edges.)'), - const SizedBox(height: 8), - const Text( - '• Give early feedback: Let us know what you think and help make Authenticator for Android a better experience. Go to “Send us feedback” under Help and about.'), - ], - ), - actions: [ - // FIXME: enable and add correct uri - // TextButton( - // style: TextButton.styleFrom( - // textStyle: Theme.of(context) - // .textTheme - // .labelLarge - // ?.copyWith(fontWeight: FontWeight.bold), - // ), - // child: const Text('Learn more'), - // onPressed: () { - // launchUrl(Uri.parse('https://learn more uri'), - // mode: LaunchMode.externalApplication); - // onBetaDialogClosed(context, ref); - // }, - // ), - TextButton( - key: keys.okButton, - style: TextButton.styleFrom( - textStyle: Theme.of(context) - .textTheme - .labelLarge - ?.copyWith(fontWeight: FontWeight.bold), - ), - child: const Text('Got it'), - onPressed: () => onBetaDialogClosed(context, ref), - ), - ], - ), + await withContext( + (context) async { + await showBlurDialog( + context: context, + builder: (context) => const _BetaDialog(), + routeSettings: const RouteSettings(name: 'android_beta_dialog'), ); }, ); - } - final String prefBetaDialogShouldBeShown = 'prefBetaDialogShouldBeShown'; - - void onBetaDialogClosed(BuildContext context, WidgetRef ref) async { - Navigator.of(context).pop(true); - await ref.read(prefProvider).setBool(prefBetaDialogShouldBeShown, false); + await sharedPrefs.setBool(prefBetaDialogShouldBeShown, false); + } +} + +class _BetaDialog extends StatefulWidget { + const _BetaDialog(); + + @override + State createState() => _BetaDialogState(); +} + +class _BetaDialogState extends State<_BetaDialog> { + late FocusScopeNode _focus; + + @override + void initState() { + super.initState(); + _focus = FocusScopeNode(); + } + + @override + void dispose() { + _focus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // This keeps the focus in the dialog, even if the underlying page + // changes as it does when a new device is selected. + return FocusScope( + node: _focus, + autofocus: true, + onFocusChange: (focused) { + if (!focused) { + _focus.requestFocus(); + } + }, + child: const _BetaDialogContent(), + ); + } +} + +class _BetaDialogContent extends ConsumerWidget { + const _BetaDialogContent(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isDarkTheme = Theme.of(context).brightness == Brightness.dark; + + return WillPopScope( + onWillPop: () async => false, + child: AlertDialog( + key: keys.betaDialogView, + content: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + isDarkTheme + ? 'assets/graphics/beta-dark.png' + : 'assets/graphics/beta-light.png', + alignment: Alignment.topCenter, + height: 124, + filterQuality: FilterQuality.medium, + ), + const SizedBox(height: 24), + Text( + 'Welcome to Yubico Authenticator Beta!', + style: Theme.of(context) + .textTheme + .headlineMedium + ?.copyWith(color: isDarkTheme ? Colors.white : Colors.black), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + const Text( + '• Preview the latest beta: Try out the newest features. (Sometimes these may be a little rough around the edges.)'), + const SizedBox(height: 8), + const Text( + '• Give early feedback: Let us know what you think and help make Authenticator for Android a better experience. Go to “Send us feedback” under Help and about.'), + ], + ), + actions: [ + // FIXME: enable and add correct uri + // TextButton( + // style: TextButton.styleFrom( + // textStyle: Theme.of(context) + // .textTheme + // .labelLarge + // ?.copyWith(fontWeight: FontWeight.bold), + // ), + // child: const Text('Learn more'), + // onPressed: () { + // launchUrl(Uri.parse('https://learn more uri'), + // mode: LaunchMode.externalApplication); + // onBetaDialogClosed(context, ref); + // }, + // ), + TextButton( + key: keys.okButton, + style: TextButton.styleFrom( + textStyle: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith(fontWeight: FontWeight.bold), + ), + child: const Text('Got it'), + onPressed: () => Navigator.of(context) + .pop(true) //{}, //onBetaDialogClosed(context, ref), + ), + ], + ), + ); } } diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index 1aa07e1c..e167665c 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -26,6 +26,7 @@ class MainPage extends ConsumerWidget { Navigator.of(context).popUntil((route) { return route.isFirst || [ + 'android_beta_dialog', 'device_picker', 'settings', 'about',