Skip to content

Commit

Permalink
[google_sign_in_web] Update button_tester to use web_only library. (#…
Browse files Browse the repository at this point in the history
…6868)

(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:

<img width="821" alt="Screenshot 2024-06-04 at 7 39 13�PM" src="https://github.com/flutter/packages/assets/1255594/284ba951-1d46-4ffb-9136-102344337286">

## Versioning

This change doesn't need publishing/versioning; it's purely reference code living in the repo.
  • Loading branch information
ditman authored Jun 5, 2024
1 parent 023210f commit 586faa6
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> main() async {
await _plugin.initWithParams(const SignInInitParameters(
await _platform.initWithParams(const SignInInitParameters(
clientId: 'your-client_id.apps.googleusercontent.com',
));
runApp(
Expand All @@ -41,15 +41,15 @@ class _ButtonConfiguratorState extends State<ButtonConfiguratorDemo> {
@override
void initState() {
super.initState();
_plugin.userDataEvents?.listen((GoogleSignInUserData? userData) {
_platform.userDataEvents?.listen((GoogleSignInUserData? userData) {
setState(() {
_userData = userData;
});
});
}

void _handleSignOut() {
_plugin.signOut();
_platform.signOut();
setState(() {
// signOut does not broadcast through the userDataEvents, so we fake it.
_userData = null;
Expand All @@ -70,7 +70,7 @@ class _ButtonConfiguratorState extends State<ButtonConfiguratorDemo> {
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (_userData == null)
_plugin.renderButton(configuration: _buttonConfiguration),
renderButton(configuration: _buttonConfiguration),
if (_userData != null) ...<Widget>[
Text('Hello, ${_userData!.displayName}!'),
ElevatedButton(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> availableLocales = <String>[
/// 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<String> _availableLocales = <String>[
'en_US',
'es_ES',
'pt_BR',
Expand All @@ -18,123 +22,146 @@ const List<String> availableLocales = <String>[
'de_DE',
];

// The builder functions for the Cards that let users customize the button.
final List<CardBuilder> _cards = <CardBuilder>[
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
_renderLocaleCard(
value: currentConfig?.locale ?? 'en_US',
locales: _availableLocales,
onChanged: _onChanged<String>(currentConfig, onChange),
),
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
_renderMinimumWidthCard(
value: currentConfig?.minimumWidth,
max: 500,
actualMax: 400,
onChanged: _onChanged<double>(currentConfig, onChange),
),
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
_renderRadioListTileCard<GSIButtonType>(
title: 'ButtonType',
values: GSIButtonType.values,
selected: currentConfig?.type ?? GSIButtonType.standard,
onChanged: _onChanged<GSIButtonType>(currentConfig, onChange),
),
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
_renderRadioListTileCard<GSIButtonShape>(
title: 'ButtonShape',
values: GSIButtonShape.values,
selected: currentConfig?.shape ?? GSIButtonShape.rectangular,
onChanged: _onChanged<GSIButtonShape>(currentConfig, onChange),
),
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
_renderRadioListTileCard<GSIButtonSize>(
title: 'ButtonSize',
values: GSIButtonSize.values,
selected: currentConfig?.size ?? GSIButtonSize.large,
onChanged: _onChanged<GSIButtonSize>(currentConfig, onChange),
),
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
_renderRadioListTileCard<GSIButtonTheme>(
title: 'ButtonTheme',
values: GSIButtonTheme.values,
selected: currentConfig?.theme ?? GSIButtonTheme.outline,
onChanged: _onChanged<GSIButtonTheme>(currentConfig, onChange),
),
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
_renderRadioListTileCard<GSIButtonText>(
title: 'ButtonText',
values: GSIButtonText.values,
selected: currentConfig?.text ?? GSIButtonText.signinWith,
onChanged: _onChanged<GSIButtonText>(currentConfig, onChange),
),
(GSIButtonConfiguration? currentConfig, OnWebConfigChangeFn? onChange) =>
_renderRadioListTileCard<GSIButtonLogoAlignment>(
title: 'ButtonLogoAlignment',
values: GSIButtonLogoAlignment.values,
selected: currentConfig?.logoAlignment ?? GSIButtonLogoAlignment.left,
onChanged: _onChanged<GSIButtonLogoAlignment>(currentConfig, onChange),
),
];

/// Renders a Scrollable Column widget that allows the user to see (and change) a ButtonConfiguration.
Widget renderWebButtonConfiguration(
GSIButtonConfiguration? currentConfig, {
OnWebConfigChangeFn? onChange,
}) {
final ScrollController scrollController = ScrollController();
return Scrollbar(
controller: scrollController,
thumbVisibility: true,
interactive: true,
child: SingleChildScrollView(
controller: scrollController,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_renderLocaleCard(
value: currentConfig?.locale,
locales: availableLocales,
onChanged: _onChanged<String>(currentConfig, onChange),
),
_renderMinimumWidthCard(
value: currentConfig?.minimumWidth,
max: 500,
actualMax: 400,
onChanged: _onChanged<double>(currentConfig, onChange),
),
_renderRadioListTileCard<GSIButtonType>(
title: 'ButtonType',
values: GSIButtonType.values,
selected: currentConfig?.type,
onChanged: _onChanged<GSIButtonType>(currentConfig, onChange),
),
_renderRadioListTileCard<GSIButtonShape>(
title: 'ButtonShape',
values: GSIButtonShape.values,
selected: currentConfig?.shape,
onChanged: _onChanged<GSIButtonShape>(currentConfig, onChange),
),
_renderRadioListTileCard<GSIButtonSize>(
title: 'ButtonSize',
values: GSIButtonSize.values,
selected: currentConfig?.size,
onChanged: _onChanged<GSIButtonSize>(currentConfig, onChange),
),
_renderRadioListTileCard<GSIButtonTheme>(
title: 'ButtonTheme',
values: GSIButtonTheme.values,
selected: currentConfig?.theme,
onChanged: _onChanged<GSIButtonTheme>(currentConfig, onChange),
),
_renderRadioListTileCard<GSIButtonText>(
title: 'ButtonText',
values: GSIButtonText.values,
selected: currentConfig?.text,
onChanged: _onChanged<GSIButtonText>(currentConfig, onChange),
),
_renderRadioListTileCard<GSIButtonLogoAlignment>(
title: 'ButtonLogoAlignment',
values: GSIButtonLogoAlignment.values,
selected: currentConfig?.logoAlignment,
onChanged:
_onChanged<GSIButtonLogoAlignment>(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<String> locales,
void Function(String?)? onChanged}) {
return _renderConfigCard(title: 'locale', children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: DropdownButton<String>(
items: locales
.map((String locale) => DropdownMenuItem<String>(
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<String> locales,
void Function(String?)? onChanged,
}) {
return _renderConfigCard(
title: 'locale',
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: DropdownButton<String>(
items: locales
.map((String locale) => DropdownMenuItem<String>(
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: <Widget>[
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: <Widget>[
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<T extends Enum>(
{required String title,
required List<T> values,
T? selected,
void Function(T?)? onChanged}) {
Widget _renderRadioListTileCard<T extends Enum>({
required String title,
required List<T> values,
T? selected,
void Function(T?)? onChanged,
}) {
return _renderConfigCard(
title: title,
children: values
Expand All @@ -150,29 +177,32 @@ Widget _renderRadioListTileCard<T extends Enum>(
}

/// Renders a Card where we render some `children` that change config.
Widget _renderConfigCard(
{required String title, required List<Widget> children}) {
return Container(
constraints: const BoxConstraints(maxWidth: 200),
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text(
title,
style: const TextStyle(fontWeight: FontWeight.bold),
),
dense: true,
Widget _renderConfigCard({
required String title,
required List<Widget> children,
}) {
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
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<T>(
GSIButtonConfiguration? old,
T? value,
) {
return GSIButtonConfiguration(
locale: value is String ? value : old?.locale,
minimumWidth:
Expand All @@ -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<T>(
GSIButtonConfiguration? current, OnWebConfigChangeFn? fn) {
GSIButtonConfiguration? current,
OnWebConfigChangeFn? fn,
) {
if (fn == null) {
return null;
}
return (T? value) {
fn(_copyConfigWith(current, value));
fn(_copyConfigWith<T>(current, value));
};
}

0 comments on commit 586faa6

Please sign in to comment.