From 586faa631499b19fc5c1b9239f40dbc9be73bd4f Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Wed, 5 Jun 2024 13:29:20 -0700 Subject: [PATCH] [google_sign_in_web] Update button_tester to use web_only library. (#6868) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (Cleanup) While reviewing a separate issue (flutter/flutter#149236), I noticed that the `button_tester` example app hadn't been updated to the latest style of using the `web_only` library to "renderButton". This PR updates the implementation of the `button_tester` example app to use the `web_only.dart` library from `package:google_sign_in_web`, instead of attempting to access the web-only methods through funky casts. While I was there, I also changed a couple of things in the selectable options column: * (Usability) Ensured the "default" value of each option is rendered by default in the option selection as well. * (Style) Refactored the column of cards to be a `ListView.builder`, rather than a `SingleChildScrollView + Column`. ## Testing I haven't deployed this anywhere, but this is what it looks like: Screenshot 2024-06-04 at 7 39 13 PM ## Versioning This change doesn't need publishing/versioning; it's purely reference code living in the repo. --- .../example/lib/button_tester.dart | 18 +- .../lib/src/button_configuration_column.dart | 280 ++++++++++-------- 2 files changed, 165 insertions(+), 133 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_web/example/lib/button_tester.dart b/packages/google_sign_in/google_sign_in_web/example/lib/button_tester.dart index 4def097e52c3..02b4346e9b87 100644 --- a/packages/google_sign_in/google_sign_in_web/example/lib/button_tester.dart +++ b/packages/google_sign_in/google_sign_in_web/example/lib/button_tester.dart @@ -4,17 +4,17 @@ import 'package:flutter/material.dart'; import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; -import 'package:google_sign_in_web/google_sign_in_web.dart'; +import 'package:google_sign_in_web/web_only.dart'; import 'src/button_configuration_column.dart'; -// The instance of the plugin is automatically created by Flutter before calling -// our main code, let's grab it directly from the Platform interface of the plugin. -final GoogleSignInPlugin _plugin = - GoogleSignInPlatform.instance as GoogleSignInPlugin; +// Let's use the Platform Interface directly, no need to use anything web-specific +// from it. (In a normal app, we'd use the plugin interface!) +// All the web-specific imports come from the `web_only.dart` library. +final GoogleSignInPlatform _platform = GoogleSignInPlatform.instance; Future main() async { - await _plugin.initWithParams(const SignInInitParameters( + await _platform.initWithParams(const SignInInitParameters( clientId: 'your-client_id.apps.googleusercontent.com', )); runApp( @@ -41,7 +41,7 @@ class _ButtonConfiguratorState extends State { @override void initState() { super.initState(); - _plugin.userDataEvents?.listen((GoogleSignInUserData? userData) { + _platform.userDataEvents?.listen((GoogleSignInUserData? userData) { setState(() { _userData = userData; }); @@ -49,7 +49,7 @@ class _ButtonConfiguratorState extends State { } void _handleSignOut() { - _plugin.signOut(); + _platform.signOut(); setState(() { // signOut does not broadcast through the userDataEvents, so we fake it. _userData = null; @@ -70,7 +70,7 @@ class _ButtonConfiguratorState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ if (_userData == null) - _plugin.renderButton(configuration: _buttonConfiguration), + renderButton(configuration: _buttonConfiguration), if (_userData != null) ...[ Text('Hello, ${_userData!.displayName}!'), ElevatedButton( diff --git a/packages/google_sign_in/google_sign_in_web/example/lib/src/button_configuration_column.dart b/packages/google_sign_in/google_sign_in_web/example/lib/src/button_configuration_column.dart index 75ea09559790..19009caa436f 100644 --- a/packages/google_sign_in/google_sign_in_web/example/lib/src/button_configuration_column.dart +++ b/packages/google_sign_in/google_sign_in_web/example/lib/src/button_configuration_column.dart @@ -3,13 +3,17 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:google_sign_in_web/google_sign_in_web.dart'; +import 'package:google_sign_in_web/web_only.dart'; /// Type of the onChange function for `ButtonConfiguration`. typedef OnWebConfigChangeFn = void Function(GSIButtonConfiguration newConfig); -/// (Incomplete) List of the locales that can be used to configure the button. -const List availableLocales = [ +/// The type of the widget builder function for each Card in the ListView builder +typedef CardBuilder = Widget Function( + GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange); + +// (Incomplete) List of the locales that can be used to configure the button. +const List _availableLocales = [ 'en_US', 'es_ES', 'pt_BR', @@ -18,6 +22,65 @@ const List availableLocales = [ 'de_DE', ]; +// The builder functions for the Cards that let users customize the button. +final List _cards = [ + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderLocaleCard( + value: currentConfig?.locale ?? 'en_US', + locales: _availableLocales, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderMinimumWidthCard( + value: currentConfig?.minimumWidth, + max: 500, + actualMax: 400, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderRadioListTileCard( + title: 'ButtonType', + values: GSIButtonType.values, + selected: currentConfig?.type ?? GSIButtonType.standard, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderRadioListTileCard( + title: 'ButtonShape', + values: GSIButtonShape.values, + selected: currentConfig?.shape ?? GSIButtonShape.rectangular, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderRadioListTileCard( + title: 'ButtonSize', + values: GSIButtonSize.values, + selected: currentConfig?.size ?? GSIButtonSize.large, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderRadioListTileCard( + title: 'ButtonTheme', + values: GSIButtonTheme.values, + selected: currentConfig?.theme ?? GSIButtonTheme.outline, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderRadioListTileCard( + title: 'ButtonText', + values: GSIButtonText.values, + selected: currentConfig?.text ?? GSIButtonText.signinWith, + onChanged: _onChanged(currentConfig, onChange), + ), + (GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) => + _renderRadioListTileCard( + title: 'ButtonLogoAlignment', + values: GSIButtonLogoAlignment.values, + selected: currentConfig?.logoAlignment ?? GSIButtonLogoAlignment.left, + onChanged: _onChanged(currentConfig, onChange), + ), +]; + /// Renders a Scrollable Column widget that allows the user to see (and change) a ButtonConfiguration. Widget renderWebButtonConfiguration( GSIButtonConfiguration? currentConfig, { @@ -25,116 +88,80 @@ Widget renderWebButtonConfiguration( }) { final ScrollController scrollController = ScrollController(); return Scrollbar( - controller: scrollController, - thumbVisibility: true, - interactive: true, - child: SingleChildScrollView( - controller: scrollController, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _renderLocaleCard( - value: currentConfig?.locale, - locales: availableLocales, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderMinimumWidthCard( - value: currentConfig?.minimumWidth, - max: 500, - actualMax: 400, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderRadioListTileCard( - title: 'ButtonType', - values: GSIButtonType.values, - selected: currentConfig?.type, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderRadioListTileCard( - title: 'ButtonShape', - values: GSIButtonShape.values, - selected: currentConfig?.shape, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderRadioListTileCard( - title: 'ButtonSize', - values: GSIButtonSize.values, - selected: currentConfig?.size, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderRadioListTileCard( - title: 'ButtonTheme', - values: GSIButtonTheme.values, - selected: currentConfig?.theme, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderRadioListTileCard( - title: 'ButtonText', - values: GSIButtonText.values, - selected: currentConfig?.text, - onChanged: _onChanged(currentConfig, onChange), - ), - _renderRadioListTileCard( - title: 'ButtonLogoAlignment', - values: GSIButtonLogoAlignment.values, - selected: currentConfig?.logoAlignment, - onChanged: - _onChanged(currentConfig, onChange), - ), - ], - ))); + controller: scrollController, + thumbVisibility: true, + interactive: true, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 250), + child: ListView.builder( + controller: scrollController, + itemCount: _cards.length, + itemBuilder: (BuildContext _, int index) => + _cards[index](currentConfig, onChange), + ), + ), + ); } /// Renders a Config card with a dropdown of locales. -Widget _renderLocaleCard( - {String? value, - required List locales, - void Function(String?)? onChanged}) { - return _renderConfigCard(title: 'locale', children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: DropdownButton( - items: locales - .map((String locale) => DropdownMenuItem( - value: locale, - child: Text(locale), - )) - .toList(), - value: value, - onChanged: onChanged, - isExpanded: true, - // padding: const EdgeInsets.symmetric(horizontal: 16), // Prefer padding here! +Widget _renderLocaleCard({ + String? value, + required List locales, + void Function(String?)? onChanged, +}) { + return _renderConfigCard( + title: 'locale', + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: DropdownButton( + items: locales + .map((String locale) => DropdownMenuItem( + value: locale, + child: Text(locale), + )) + .toList(), + value: value, + onChanged: onChanged, + isExpanded: true, + // padding: const EdgeInsets.symmetric(horizontal: 16), // Prefer padding here! + ), ), - ), - ]); + ], + ); } /// Renders a card with a slider -Widget _renderMinimumWidthCard( - {double? value, - double min = 0, - double actualMax = 10, - double max = 11, - void Function(double)? onChanged}) { - return _renderConfigCard(title: 'minimumWidth', children: [ - Slider( - label: value?.toString() ?? 'null', - value: value ?? 0, - min: min, - max: max, - secondaryTrackValue: actualMax, - onChanged: onChanged, - divisions: 10, - ) - ]); +Widget _renderMinimumWidthCard({ + double? value, + double min = 0, + double actualMax = 10, + double max = 11, + void Function(double)? onChanged, +}) { + return _renderConfigCard( + title: 'minimumWidth', + children: [ + Slider( + label: value?.toString() ?? 'null', + value: value ?? 0, + min: min, + max: max, + secondaryTrackValue: actualMax, + onChanged: onChanged, + divisions: 10, + ) + ], + ); } /// Renders a Config Card with the values of an Enum as radio buttons. -Widget _renderRadioListTileCard( - {required String title, - required List values, - T? selected, - void Function(T?)? onChanged}) { +Widget _renderRadioListTileCard({ + required String title, + required List values, + T? selected, + void Function(T?)? onChanged, +}) { return _renderConfigCard( title: title, children: values @@ -150,29 +177,32 @@ Widget _renderRadioListTileCard( } /// Renders a Card where we render some `children` that change config. -Widget _renderConfigCard( - {required String title, required List children}) { - return Container( - constraints: const BoxConstraints(maxWidth: 200), - child: Card( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: Text( - title, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - dense: true, +Widget _renderConfigCard({ + required String title, + required List children, +}) { + return Card( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + title: Text( + title, + style: const TextStyle(fontWeight: FontWeight.bold), ), - ...children, - ], - ))); + dense: true, + ), + ...children, + ], + ), + ); } /// Sets a `value` into an `old` configuration object. -GSIButtonConfiguration _copyConfigWith( - GSIButtonConfiguration? old, Object? value) { +GSIButtonConfiguration _copyConfigWith( + GSIButtonConfiguration? old, + T? value, +) { return GSIButtonConfiguration( locale: value is String ? value : old?.locale, minimumWidth: @@ -188,11 +218,13 @@ GSIButtonConfiguration _copyConfigWith( /// Returns a function that modifies the `current` configuration with a `value`, then calls `fn` with it. void Function(T?)? _onChanged( - GSIButtonConfiguration? current, OnWebConfigChangeFn? fn) { + GSIButtonConfiguration? current, + OnWebConfigChangeFn? fn, +) { if (fn == null) { return null; } return (T? value) { - fn(_copyConfigWith(current, value)); + fn(_copyConfigWith(current, value)); }; }