From dce40d7826297055630dce2f01b0dc13f1ef02cf Mon Sep 17 00:00:00 2001 From: Luke Date: Mon, 26 Feb 2024 15:09:47 +0000 Subject: [PATCH] Dial Pad buttons --- example/lib/home.dart | 2 + .../lib/pages/components/dialpad_example.dart | 27 +++ example/web/index.html | 17 +- .../pages/components/dial_pad_widgetbook.dart | 45 +++++ example/widgetbook/widgetbook.dart | 2 + .../components/dial_pad/dial_pad_buttons.dart | 168 ++++++++++++++++++ lib/src/theme/typography.dart | 2 +- lib/zeta_flutter.dart | 1 + 8 files changed, 256 insertions(+), 8 deletions(-) create mode 100644 example/lib/pages/components/dialpad_example.dart create mode 100644 example/widgetbook/pages/components/dial_pad_widgetbook.dart create mode 100644 lib/src/components/dial_pad/dial_pad_buttons.dart diff --git a/example/lib/home.dart b/example/lib/home.dart index aaafbe7b..36111b65 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -8,6 +8,7 @@ import 'package:zeta_example/pages/components/bottom_sheet_example.dart'; import 'package:zeta_example/pages/components/button_example.dart'; import 'package:zeta_example/pages/components/checkbox_example.dart'; import 'package:zeta_example/pages/components/chip_example.dart'; +import 'package:zeta_example/pages/components/dialpad_example.dart'; import 'package:zeta_example/pages/theme/color_example.dart'; import 'package:zeta_example/pages/components/password_input_example.dart'; import 'package:zeta_example/pages/assets/icons_example.dart'; @@ -31,6 +32,7 @@ final List components = [ Component(CheckBoxExample.name, (context) => const CheckBoxExample()), Component(ChipExample.name, (context) => const ChipExample()), Component(PasswordInputExample.name, (context) => const PasswordInputExample()), + Component(DialPadExample.name, (context) => const DialPadExample()), ]; final List theme = [ diff --git a/example/lib/pages/components/dialpad_example.dart b/example/lib/pages/components/dialpad_example.dart new file mode 100644 index 00000000..5e381c6f --- /dev/null +++ b/example/lib/pages/components/dialpad_example.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class DialPadExample extends StatelessWidget { + static const String name = 'DialPad'; + + const DialPadExample({super.key}); + + @override + Widget build(BuildContext context) { + return ExampleScaffold( + name: name, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ZetaDialPad(), + ], + ), + ], + ), + ); + } +} diff --git a/example/web/index.html b/example/web/index.html index 115cc54e..f9b1d73c 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -1,5 +1,6 @@ + @@ -27,28 +28,29 @@ - + zeta_flutter_example + - + + \ No newline at end of file diff --git a/example/widgetbook/pages/components/dial_pad_widgetbook.dart b/example/widgetbook/pages/components/dial_pad_widgetbook.dart new file mode 100644 index 00000000..c0460580 --- /dev/null +++ b/example/widgetbook/pages/components/dial_pad_widgetbook.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +WidgetbookComponent dialPadWidgetbook() { + return WidgetbookComponent( + isInitiallyExpanded: false, + name: 'Dial Pad', + useCases: [ + WidgetbookUseCase( + name: 'Dial Pad', + builder: (context) { + return WidgetbookTestWidget( + widget: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ZetaDialPad( + buttonValues: context.knobs.boolean(label: 'Change to emoji') + ? { + 'πŸ˜€': '', + 'πŸ₯²': '', + 'πŸ₯³': '', + '🀠': '', + '😨': '', + 'πŸ‘€': '', + '🐀': '', + '🐞': '', + '🦊': '', + 'πŸ†': '', + '⛺️': '', + '🧽': '' + } + : null, + buttonsPerRow: context.knobs.int.slider(label: 'Buttons per row', initialValue: 3, min: 1, max: 9), + ), + ], + ).paddingAll(ZetaSpacing.l), + ); + }, + ), + ], + ); +} diff --git a/example/widgetbook/widgetbook.dart b/example/widgetbook/widgetbook.dart index ff8168f4..2179ec35 100644 --- a/example/widgetbook/widgetbook.dart +++ b/example/widgetbook/widgetbook.dart @@ -9,6 +9,7 @@ import 'pages/components/badges_widgetbook.dart'; import 'pages/components/bottom_sheet_widgetbook.dart'; import 'pages/components/button_widgetbook.dart'; import 'pages/components/checkbox_widgetbook.dart'; +import 'pages/components/dial_pad_widgetbook.dart'; import 'pages/theme/color_widgetbook.dart'; import 'pages/components/banner_widgetbook.dart'; import 'pages/components/chip_widgetbook.dart'; @@ -39,6 +40,7 @@ class HotReload extends StatelessWidget { chipWidgetBook(), passwordInputWidgetBook(), bottomSheetWidgetBook(), + dialPadWidgetbook(), ]..sort((a, b) => a.name.compareTo(b.name)), ), WidgetbookCategory( diff --git a/lib/src/components/dial_pad/dial_pad_buttons.dart b/lib/src/components/dial_pad/dial_pad_buttons.dart new file mode 100644 index 00000000..8ae4f614 --- /dev/null +++ b/lib/src/components/dial_pad/dial_pad_buttons.dart @@ -0,0 +1,168 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../zeta_flutter.dart'; + +const Map _defaultButtonValues = { + '1': '', + '2': 'ABC', + '3': 'DEF', + '4': 'GHI', + '5': 'JKL', + '6': 'MNO', + '7': 'PQRS', + '8': 'TUV', + '9': 'WXYZ', + '*': '', + '0': '+', + '#': '', +}; + +const int _defaultButtonsPerRow = 3; + +/// Dial pad gives the user the ability to dial a number and start a call. It also has a quick dial security action and a delete entry action. +class ZetaDialPad extends StatelessWidget { + /// Constructs a [ZetaDialPad]. + const ZetaDialPad({ + super.key, + this.onInput, + this.buttonsPerRow = _defaultButtonsPerRow, + this.buttonValues = _defaultButtonValues, + }); + + /// Callback when number is tapped. Returns the large value from the button, i,e, 1,2,3 etc. + final ValueChanged? onInput; + + /// Number of buttons to show on each row. Defaults to 3. + final int? buttonsPerRow; + + /// Map of values to show on the buttons. + /// + /// Key is the large character, i.e. 1, 2, 3. + /// + /// Value is the smaller character(s): i.e. 'ABC' + final Map? buttonValues; + + int get _buttonsPerRow => buttonsPerRow ?? _defaultButtonsPerRow; + Map get _buttonValues => buttonValues ?? _defaultButtonValues; + + @override + Widget build(BuildContext context) { + return SelectionContainer.disabled( + child: SizedBox( + width: (_buttonsPerRow * ZetaSpacing.x16) + ((_buttonsPerRow - 1) * ZetaSpacing.x9), + child: GridView.count( + crossAxisCount: _buttonsPerRow, + shrinkWrap: true, + semanticChildCount: _buttonValues.length, + mainAxisSpacing: ZetaSpacing.x9, + crossAxisSpacing: ZetaSpacing.x8, + children: _buttonValues.entries + .map( + (e) => _DialPadButton( + number: e.key, + letters: e.value, + onTap: onInput, + topPadding: e.key == '*' + ? 11 + : e.value.isEmpty && e.key != '1' + ? 14 + : 3, + ), + ) + .toList(), + ), + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(ObjectFlagProperty?>.has('onInput', onInput)) + ..add(IntProperty('buttonsPerRow', buttonsPerRow)) + ..add(DiagnosticsProperty>('buttonValues', buttonValues)); + } +} + +class _DialPadButton extends StatefulWidget { + const _DialPadButton({ + required this.number, + this.letters = '', + required this.onTap, + this.topPadding = 3, + }); + + final String number; + final String letters; + final double topPadding; + final ValueChanged? onTap; + + @override + State<_DialPadButton> createState() => _DialPadButtonState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(ObjectFlagProperty>.has('onTap', onTap)) + ..add(StringProperty('letters', letters)) + ..add(StringProperty('number', number)) + ..add(DoubleProperty('topPadding', topPadding)); + } +} + +class _DialPadButtonState extends State<_DialPadButton> { + bool _focused = false; + + @override + Widget build(BuildContext context) { + final colors = Zeta.of(context).colors; + + return Semantics( + button: true, + value: widget.number, + excludeSemantics: true, + child: AnimatedContainer( + duration: Durations.short2, + width: ZetaSpacing.x16, + height: ZetaSpacing.x16, + decoration: ShapeDecoration( + shape: CircleBorder( + side: _focused ? BorderSide(color: colors.blue, width: ZetaSpacing.x0_5) : BorderSide.none, + ), + color: colors.warm.shade10, + shadows: [BoxShadow(color: colors.black.withOpacity(0.15), blurRadius: 4, offset: const Offset(0, 2))], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => widget.onTap?.call(widget.number), + borderRadius: ZetaRadius.full, + overlayColor: MaterialStateProperty.resolveWith((states) { + if (states.contains(MaterialState.pressed)) { + return colors.surfaceSelectedHovered; + } + if (states.contains(MaterialState.hovered)) { + return colors.surfaceHovered; + } + return null; + }), + onFocusChange: (value) { + if (_focused != value) setState(() => _focused = value); + }, + child: Column( + children: [ + SizedBox(height: widget.topPadding), + Text(widget.number, style: ZetaTextStyles.heading1), + if (widget.topPadding < 10) Text(widget.letters, style: ZetaTextStyles.labelIndicator), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/theme/typography.dart b/lib/src/theme/typography.dart index 62857bbe..c5515f42 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography.dart @@ -32,7 +32,7 @@ class ZetaTextStyles { /// Headline styles are smaller than display styles. They're best-suited for /// short, high-emphasis text on smaller screens. /// {@endtemplate} - static const TextStyle heading1 = TextStyle(fontSize: 32, fontWeight: FontWeight.w500, height: 40 / 32); + static const TextStyle heading1 = TextStyle(fontSize: 32, fontWeight: FontWeight.w500, height: 36 / 32); /// Middle size of the headline styles. /// diff --git a/lib/zeta_flutter.dart b/lib/zeta_flutter.dart index c968f8fc..90877946 100644 --- a/lib/zeta_flutter.dart +++ b/lib/zeta_flutter.dart @@ -20,6 +20,7 @@ export 'src/components/buttons/fab.dart'; export 'src/components/buttons/icon_button.dart'; export 'src/components/checkbox/checkbox.dart'; export 'src/components/chips/chip.dart'; +export 'src/components/dial_pad/dial_pad_buttons.dart'; export 'src/components/password/password_input.dart'; export 'src/theme/color_extensions.dart'; export 'src/theme/color_scheme.dart';