From 78081d550ef7b312779e97a11c3ca524a25d33d5 Mon Sep 17 00:00:00 2001 From: Vitalij Vascenko Date: Thu, 9 Nov 2023 12:25:30 +0100 Subject: [PATCH 1/2] feat!: Upgrade to DCM 1.11.0 --- .github/workflows/dcm.yml | 2 +- mews_pedantic/lib/analysis_options.yaml | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dcm.yml b/.github/workflows/dcm.yml index a8862765..bfd8614e 100644 --- a/.github/workflows/dcm.yml +++ b/.github/workflows/dcm.yml @@ -16,7 +16,7 @@ jobs: uses: CQLabs/setup-dcm@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }} - version: "1.10.3" + version: "1.11.1" - uses: ./.github/actions/setup diff --git a/mews_pedantic/lib/analysis_options.yaml b/mews_pedantic/lib/analysis_options.yaml index c38e9a5c..a1183db6 100644 --- a/mews_pedantic/lib/analysis_options.yaml +++ b/mews_pedantic/lib/analysis_options.yaml @@ -245,6 +245,7 @@ dart_code_metrics: - avoid-accessing-collections-by-constant-index # - avoid-accessing-other-classes-private-members # - avoid-async-call-in-sync-function + # - avoid-banned-annotations # - avoid-banned-file-names # - avoid-banned-types - avoid-barrel-files @@ -253,12 +254,15 @@ dart_code_metrics: - avoid-cascade-after-if-null # - avoid-collapsible-if - avoid-collection-methods-with-unrelated-types + # - avoid-collection-mutating-methods # - avoid-declaring-call-method - avoid-double-slash-imports + - avoid-duplicate-cascades - avoid-duplicate-exports - avoid-duplicate-mixins - avoid-duplicate-named-imports - avoid-duplicate-patterns + - avoid-duplicate-switch-case-conditions # - avoid-dynamic - avoid-equal-expressions - avoid-explicit-pattern-field-name @@ -271,6 +275,7 @@ dart_code_metrics: # - avoid-identical-exception-handling-blocks # - avoid-ignoring-return-values # - avoid-importing-entrypoint-exports + # - avoid-inferrable-type-arguments - avoid-inverted-boolean-checks - avoid-keywords-in-wildcard-pattern # - avoid-late-keyword @@ -283,6 +288,7 @@ dart_code_metrics: - avoid-missed-calls # - avoid-missing-enum-constant-in-map - avoid-missing-interpolation + # - avoid-missing-test-files - avoid-misused-wildcard-pattern - avoid-mixing-named-and-positional-fields # - avoid-mutating-parameters @@ -324,11 +330,12 @@ dart_code_metrics: - avoid-unnecessary-futures - avoid-unnecessary-getter - avoid-unnecessary-if - - avoid-unnecessary-late + - avoid-unnecessary-local-late - avoid-unnecessary-negations - avoid-unnecessary-nullable-return-type - avoid-unnecessary-reassignment - avoid-unnecessary-return + - avoid-unnecessary-super - avoid-unnecessary-type-assertions - avoid-unnecessary-type-casts - avoid-unrelated-type-assertions @@ -350,6 +357,7 @@ dart_code_metrics: # - map-keys-ordering # - match-class-name-pattern - match-getter-setter-field-names + - match-lib-folder-structure # - match-positional-field-names-on-assignment # - member-ordering # - missing-test-assertion @@ -368,15 +376,20 @@ dart_code_metrics: # - no-magic-number - no-object-declaration # - parameters-ordering + # - prefer-addition-subtraction-assignments # - prefer-async-await + - prefer-both-inlining-annotations # - prefer-bytes-builder - prefer-commenting-analyzer-ignores # - prefer-conditional-expressions + # - prefer-correct-callback-field-name - prefer-correct-error-name - prefer-correct-for-loop-increment - prefer-correct-future-return-type + - prefer-correct-handler-name # - prefer-correct-identifier-length - prefer-correct-json-casts + - prefer-correct-setter-parameter-name - prefer-correct-stream-return-type # - prefer-correct-switch-length - prefer-correct-test-file-name: @@ -386,7 +399,9 @@ dart_code_metrics: ignore-abstract: false - prefer-early-return # - prefer-enums-by-name + - prefer-explicit-function-type - prefer-explicit-parameter-names + - prefer-explicit-type-arguments # - prefer-first # - prefer-getter-over-method - prefer-immediate-return @@ -397,15 +412,20 @@ dart_code_metrics: - prefer-named-boolean-parameters: ignore-single: true # - prefer-named-imports + - prefer-null-aware-spread - prefer-parentheses-with-if-null + # - prefer-prefixed-global-constants - prefer-public-exception-classes - prefer-return-await - prefer-returning-conditional-expressions - prefer-simpler-patterns-null-check + - prefer-specific-cases-first # - prefer-static-class # - prefer-test-matchers # - prefer-trailing-comma - prefer-type-over-var + - prefer-typedefs-for-callbacks + - prefer-unique-test-names # - prefer-unwrapping-future-or # - prefer-visible-for-testing-on-members - prefer-wildcard-pattern From 55b942cc7fed1e4704b6f4796102fe626e67ec55 Mon Sep 17 00:00:00 2001 From: Vitalij Vascenko Date: Mon, 13 Nov 2023 13:33:10 +0100 Subject: [PATCH 2/2] refactor: Fix linter errors --- optimus/lib/src/banner.dart | 2 +- .../lib/src/button/base_dropdown_button.dart | 88 +++++++++--------- optimus/lib/src/button/icon.dart | 63 +++++++------ optimus/lib/src/chat/optimus_chat_input.dart | 4 +- optimus/lib/src/checkbox/checkbox.dart | 6 +- optimus/lib/src/checkbox/checkbox_group.dart | 4 +- optimus/lib/src/checkbox/checkbox_tick.dart | 45 +++++---- optimus/lib/src/chip.dart | 80 ++++++++-------- optimus/lib/src/common/field_wrapper.dart | 13 ++- optimus/lib/src/common/gesture_wrapper.dart | 29 ++++++ optimus/lib/src/date_time_field.dart | 10 +- optimus/lib/src/dialogs/dialog_wrapper.dart | 8 +- optimus/lib/src/dropdown/dropdown.dart | 6 +- optimus/lib/src/dropdown/dropdown_select.dart | 12 ++- .../dropdown/dropdown_tap_interceptor.dart | 2 +- optimus/lib/src/expansion/expansion_tile.dart | 83 ++++++++--------- optimus/lib/src/form/date_input_field.dart | 4 +- optimus/lib/src/link/base_link.dart | 23 +++-- .../lib/src/number_picker/number_picker.dart | 10 +- optimus/lib/src/radio/radio.dart | 91 +++++++++---------- .../segmented_control/segmented_control.dart | 69 +++++++------- optimus/lib/src/select.dart | 7 +- optimus/lib/src/slidable/slide_action.dart | 2 +- .../lib/src/step_bar/step_bar_compact.dart | 8 +- optimus/lib/src/toggle.dart | 91 ++++++++++--------- .../lib/src/tooltip/tooltip_controller.dart | 14 +-- optimus/lib/src/typography/highlight.dart | 3 + optimus/lib/src/typography/title.dart | 4 + .../{ => src/form}/date_formatter_test.dart | 0 storybook/lib/main.dart | 4 +- storybook/lib/stories/checkbox.dart | 6 +- storybook/lib/stories/checkbox_group.dart | 4 +- storybook/lib/stories/checkbox_nested.dart | 23 ++--- storybook/lib/stories/date_time_field.dart | 7 +- storybook/lib/stories/dialog.dart | 28 +++--- storybook/lib/stories/nested_overlays.dart | 4 +- storybook/lib/stories/number_picker.dart | 4 +- storybook/lib/stories/radio.dart | 11 ++- storybook/lib/stories/segmented_control.dart | 4 +- storybook/lib/stories/select_input.dart | 32 ++++--- storybook/lib/stories/toggle.dart | 5 +- storybook/lib/stories/welcome.dart | 4 +- 42 files changed, 488 insertions(+), 429 deletions(-) create mode 100644 optimus/lib/src/common/gesture_wrapper.dart rename optimus/test/{ => src/form}/date_formatter_test.dart (100%) diff --git a/optimus/lib/src/banner.dart b/optimus/lib/src/banner.dart index bc1047f2..58f2ed33 100644 --- a/optimus/lib/src/banner.dart +++ b/optimus/lib/src/banner.dart @@ -127,7 +127,7 @@ class OptimusBanner extends StatelessWidget { top: spacing100, right: spacing100, child: OptimusIconButton( - onPressed: () => onDismiss, + onPressed: onDismiss, icon: const Icon(OptimusIcons.cross_close), size: OptimusWidgetSize.small, variant: OptimusButtonVariant.ghost, diff --git a/optimus/lib/src/button/base_dropdown_button.dart b/optimus/lib/src/button/base_dropdown_button.dart index f5a5e1b4..01b57097 100644 --- a/optimus/lib/src/button/base_dropdown_button.dart +++ b/optimus/lib/src/button/base_dropdown_button.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:optimus/optimus.dart'; import 'package:optimus/src/button/common.dart'; +import 'package:optimus/src/common/gesture_wrapper.dart'; import 'package:optimus/src/overlay_controller.dart'; import 'package:optimus/src/typography/presets.dart'; @@ -64,6 +65,12 @@ class _BaseDropDownButtonState extends State> void _onFocusChanged() => setState(() {}); + void _handleHoverChanged(bool isHovered) => + setState(() => _isHovered = isHovered); + + void _handlePressedChanged(bool isPressed) => + setState(() => _isPressed = isPressed); + bool get _isEnabled => widget.onItemSelected != null; Color get _textColor => widget.variant.toButtonVariant().foregroundColor( @@ -106,51 +113,46 @@ class _BaseDropDownButtonState extends State> onHidden: _controller.reverse, child: IgnorePointer( ignoring: !_isEnabled, - child: MouseRegion( - onEnter: (_) => setState(() => _isHovered = true), - onExit: (_) => setState(() => _isHovered = false), - child: GestureDetector( - onTap: _node.requestFocus, - onTapDown: (_) => setState(() => _isPressed = true), - onTapUp: (_) => setState(() => _isPressed = false), - onTapCancel: () => setState(() => _isPressed = false), - child: Focus( - focusNode: _node, - child: SizedBox( - height: widget.size.value, - child: AnimatedContainer( - padding: const EdgeInsets.symmetric(horizontal: spacing200), - key: _selectFieldKey, - decoration: BoxDecoration( - color: _color, - borderRadius: widget.borderRadius, - border: borderColor != null - ? Border.all(color: borderColor, width: 1) - : null, - ), - duration: buttonAnimationDuration, - curve: buttonAnimationCurve, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (child != null) - Padding( - padding: const EdgeInsets.only(right: 12), - child: DefaultTextStyle.merge( - style: _labelStyle, - child: child, - ), - ), - RotationTransition( - turns: _iconTurns, - child: Icon( - OptimusIcons.chevron_down, - size: widget.size.iconSize, - color: _textColor, + child: GestureWrapper( + onHoverChanged: _handleHoverChanged, + onPressedChanged: _handlePressedChanged, + onTap: _node.requestFocus, + child: Focus( + focusNode: _node, + child: SizedBox( + height: widget.size.value, + child: AnimatedContainer( + padding: const EdgeInsets.symmetric(horizontal: spacing200), + key: _selectFieldKey, + decoration: BoxDecoration( + color: _color, + borderRadius: widget.borderRadius, + border: borderColor != null + ? Border.all(color: borderColor, width: 1) + : null, + ), + duration: buttonAnimationDuration, + curve: buttonAnimationCurve, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (child != null) + Padding( + padding: const EdgeInsets.only(right: 12), + child: DefaultTextStyle.merge( + style: _labelStyle, + child: child, ), ), - ], - ), + RotationTransition( + turns: _iconTurns, + child: Icon( + OptimusIcons.chevron_down, + size: widget.size.iconSize, + color: _textColor, + ), + ), + ], ), ), ), diff --git a/optimus/lib/src/button/icon.dart b/optimus/lib/src/button/icon.dart index 2a11c979..21f55307 100644 --- a/optimus/lib/src/button/icon.dart +++ b/optimus/lib/src/button/icon.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:optimus/optimus.dart'; import 'package:optimus/src/button/common.dart'; +import 'package:optimus/src/common/gesture_wrapper.dart'; /// When you don’t have enough space for regular buttons, or the action is /// clear enough, you can use an icon button without text. @@ -36,9 +37,12 @@ class _OptimusIconButtonState extends State bool _isHovered = false; bool _isPressed = false; - void _onHoverChanged(bool isHovering) => + void _handleHoverChanged(bool isHovering) => setState(() => _isHovered = isHovering); + void _handlePressedChanged(bool isPressed) => + setState(() => _isPressed = isPressed); + bool get _isEnabled => widget.onPressed != null; @override @@ -53,43 +57,38 @@ class _OptimusIconButtonState extends State return IgnorePointer( ignoring: !isEnabled, - child: MouseRegion( - onEnter: (_) => _onHoverChanged(true), - onExit: (_) => _onHoverChanged(false), - child: GestureDetector( - onTap: widget.onPressed, - onTapDown: (_) => setState(() => _isPressed = true), - onTapUp: (_) => setState(() => _isPressed = false), - onTapCancel: () => setState(() => _isPressed = false), - child: AnimatedContainer( - height: widget.size.containerSize, - width: widget.size.containerSize, - padding: EdgeInsets.zero, - decoration: BoxDecoration( - color: widget.variant.backgroundColor( + child: GestureWrapper( + onHoverChanged: _handleHoverChanged, + onPressedChanged: _handlePressedChanged, + onTap: widget.onPressed, + child: AnimatedContainer( + height: widget.size.containerSize, + width: widget.size.containerSize, + padding: EdgeInsets.zero, + decoration: BoxDecoration( + color: widget.variant.backgroundColor( + tokens, + isEnabled: _isEnabled, + isPressed: _isPressed, + isHovered: _isHovered, + ), + border: borderColor != null + ? Border.all(color: borderColor, width: 1) + : null, + borderRadius: const BorderRadius.all(borderRadius50), + ), + duration: buttonAnimationDuration, + child: IconTheme.merge( + data: IconThemeData( + color: widget.variant.foregroundColor( tokens, isEnabled: _isEnabled, isPressed: _isPressed, isHovered: _isHovered, ), - border: borderColor != null - ? Border.all(color: borderColor, width: 1) - : null, - borderRadius: const BorderRadius.all(borderRadius50), - ), - duration: buttonAnimationDuration, - child: IconTheme.merge( - data: IconThemeData( - color: widget.variant.foregroundColor( - tokens, - isEnabled: _isEnabled, - isPressed: _isPressed, - isHovered: _isHovered, - ), - size: widget.size.iconSize, - ), - child: widget.icon, + size: widget.size.iconSize, ), + child: widget.icon, ), ), ), diff --git a/optimus/lib/src/chat/optimus_chat_input.dart b/optimus/lib/src/chat/optimus_chat_input.dart index 83cfb009..7c2088a3 100644 --- a/optimus/lib/src/chat/optimus_chat_input.dart +++ b/optimus/lib/src/chat/optimus_chat_input.dart @@ -24,7 +24,7 @@ class _OptimusChatInputState extends State { void _handleControllerChanged() => setState(() {}); - void _onTap() { + void _handleTap() { widget.onSendPressed(_controller.text); _controller.clear(); } @@ -45,7 +45,7 @@ class _OptimusChatInputState extends State { trailing: OptimusEnabled( isEnabled: _isSendEnabled, child: GestureDetector( - onTap: _onTap, + onTap: _handleTap, child: const OptimusIcon( iconData: OptimusIcons.send_message, colorOption: OptimusIconColorOption.basic, diff --git a/optimus/lib/src/checkbox/checkbox.dart b/optimus/lib/src/checkbox/checkbox.dart index 9cdc8c27..9f0b91bf 100644 --- a/optimus/lib/src/checkbox/checkbox.dart +++ b/optimus/lib/src/checkbox/checkbox.dart @@ -111,7 +111,7 @@ class OptimusCheckbox extends StatelessWidget { return false; } - void _onTap() { + void _handleTap() { final newValue = isChecked ?? false; onChanged(!newValue); } @@ -122,7 +122,7 @@ class OptimusCheckbox extends StatelessWidget { child: GroupWrapper( error: error, child: GestureDetector( - onTap: _onTap, + onTap: _handleTap, child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -134,7 +134,7 @@ class OptimusCheckbox extends StatelessWidget { isChecked: isChecked, isEnabled: isEnabled, onChanged: onChanged, - onTap: _onTap, + onTap: _handleTap, ), ), Expanded( diff --git a/optimus/lib/src/checkbox/checkbox_group.dart b/optimus/lib/src/checkbox/checkbox_group.dart index c0aa21d8..6e0c991f 100644 --- a/optimus/lib/src/checkbox/checkbox_group.dart +++ b/optimus/lib/src/checkbox/checkbox_group.dart @@ -45,7 +45,7 @@ class OptimusCheckboxGroup extends StatelessWidget { /// Controls whether the whole group is enabled. final bool isEnabled; - void _onChanged(OptimusGroupItem v, bool checked) { + void _handleChanged(OptimusGroupItem v, bool checked) { if (checked) { _values.add(v.value); } else { @@ -69,7 +69,7 @@ class OptimusCheckboxGroup extends StatelessWidget { size: size, label: v.label, isEnabled: isEnabled, - onChanged: (checked) => _onChanged(v, checked), + onChanged: (checked) => _handleChanged(v, checked), ), ) .toList(), diff --git a/optimus/lib/src/checkbox/checkbox_tick.dart b/optimus/lib/src/checkbox/checkbox_tick.dart index 85ed28fd..ea15ca5b 100644 --- a/optimus/lib/src/checkbox/checkbox_tick.dart +++ b/optimus/lib/src/checkbox/checkbox_tick.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:optimus/optimus_icons.dart'; import 'package:optimus/src/border_radius.dart'; +import 'package:optimus/src/common/gesture_wrapper.dart'; import 'package:optimus/src/theme/theme.dart'; class CheckboxTick extends StatefulWidget { @@ -25,47 +26,45 @@ class CheckboxTick extends StatefulWidget { class _CheckboxTickState extends State with ThemeGetter { bool _isHovering = false; - bool _isTappedDown = false; + bool _isPressed = false; _TickState get _state => widget.isChecked.toState; _InteractionState get _interactionState { if (!widget.isEnabled) return _InteractionState.disabled; - if (_isTappedDown) return _InteractionState.active; + if (_isPressed) return _InteractionState.active; if (_isHovering) return _InteractionState.hover; return _InteractionState.basic; } - void _onHoverChanged(bool hovered) => setState(() => _isHovering = hovered); + void _handleHoverChanged(bool hovered) => + setState(() => _isHovering = hovered); + + void _handlePressedChanged(bool pressed) => + setState(() => _isPressed = pressed); Color get _borderColor => widget.isError ? theme.tokens.backgroundInteractiveDangerDefault : _interactionState.borderColor(context); @override - Widget build(BuildContext context) => GestureDetector( + Widget build(BuildContext context) => GestureWrapper( + onHoverChanged: _handleHoverChanged, + onPressedChanged: _handlePressedChanged, onTap: widget.onTap, - onTapDown: (_) => setState(() => _isTappedDown = true), - onTapUp: (_) => setState(() => _isTappedDown = false), - onTapCancel: () => setState(() => _isTappedDown = false), - child: MouseRegion( - onEnter: (_) => _onHoverChanged(true), - onExit: (_) => _onHoverChanged(false), - child: AnimatedContainer( - duration: const Duration(milliseconds: 100), - decoration: BoxDecoration( - color: _interactionState.fillColor(context, _state), - border: _state.isUnchecked - ? Border.all(color: _borderColor, width: 1.5) - : null, - borderRadius: const BorderRadius.all(borderRadius25), - ), - width: 16, - height: 16, - child: - _CheckboxIcon(icon: _state.icon, isEnabled: widget.isEnabled), + child: AnimatedContainer( + duration: const Duration(milliseconds: 100), + decoration: BoxDecoration( + color: _interactionState.fillColor(context, _state), + border: _state.isUnchecked + ? Border.all(color: _borderColor, width: 1.5) + : null, + borderRadius: const BorderRadius.all(borderRadius25), ), + width: 16, + height: 16, + child: _CheckboxIcon(icon: _state.icon, isEnabled: widget.isEnabled), ), ); } diff --git a/optimus/lib/src/chip.dart b/optimus/lib/src/chip.dart index 983a4a9d..09e83bac 100644 --- a/optimus/lib/src/chip.dart +++ b/optimus/lib/src/chip.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:optimus/optimus.dart'; +import 'package:optimus/src/common/gesture_wrapper.dart'; import 'package:optimus/src/typography/presets.dart'; /// Chips are a visual representation of a keyword or phrase that the user has @@ -36,13 +37,13 @@ class OptimusChip extends StatefulWidget { class _OptimusChipState extends State with ThemeGetter { bool _isHovered = false; - bool _isTapped = false; + bool _isPressed = false; Color get _backgroundColor => !widget.isEnabled ? theme.tokens.backgroundDisabled : widget.hasError ? theme.tokens.backgroundAlertDangerSecondary - : _isTapped + : _isPressed ? theme.tokens.backgroundInteractiveNeutralActive : _isHovered ? theme.tokens.backgroundInteractiveNeutralHover @@ -54,48 +55,49 @@ class _OptimusChipState extends State with ThemeGetter { ? theme.tokens.textAlertDanger : theme.tokens.textStaticPrimary; + void _handleHoverChanged(bool isHovered) => + setState(() => _isHovered = isHovered); + + void _handlePressedChanged(bool isPressed) => + setState(() => _isPressed = isPressed); + @override Widget build(BuildContext context) => IgnorePointer( ignoring: !widget.isEnabled, - child: MouseRegion( - onEnter: (event) => setState(() => _isHovered = true), - onExit: (event) => setState(() => _isHovered = false), - child: GestureDetector( - onTapDown: (_) => setState(() => _isTapped = true), - onTapUp: (_) => setState(() => _isTapped = false), - onTapCancel: () => setState(() => _isTapped = false), - onTap: widget.onTap, - child: SizedBox( - height: _height, - child: AnimatedContainer( - duration: const Duration(milliseconds: 100), - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(borderRadius100), - color: _backgroundColor, - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: spacing50), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: - const EdgeInsets.symmetric(horizontal: spacing50), - child: DefaultTextStyle.merge( - style: preset200r.copyWith(color: _foregroundColor), - child: widget.child, - ), + child: GestureWrapper( + onHoverChanged: _handleHoverChanged, + onPressedChanged: _handlePressedChanged, + onTap: widget.onTap, + child: SizedBox( + height: _height, + child: AnimatedContainer( + duration: const Duration(milliseconds: 100), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(borderRadius100), + color: _backgroundColor, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: spacing50), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: + const EdgeInsets.symmetric(horizontal: spacing50), + child: DefaultTextStyle.merge( + style: preset200r.copyWith(color: _foregroundColor), + child: widget.child, ), - GestureDetector( - onTap: widget.onRemoved, - child: Icon( - OptimusIcons.cross_close, - size: 16, - color: _foregroundColor, - ), + ), + GestureDetector( + onTap: widget.onRemoved, + child: Icon( + OptimusIcons.cross_close, + size: 16, + color: _foregroundColor, ), - ], - ), + ), + ], ), ), ), diff --git a/optimus/lib/src/common/field_wrapper.dart b/optimus/lib/src/common/field_wrapper.dart index baec3b81..93d8b274 100644 --- a/optimus/lib/src/common/field_wrapper.dart +++ b/optimus/lib/src/common/field_wrapper.dart @@ -60,12 +60,12 @@ class _FieldWrapper extends State with ThemeGetter { @override void initState() { super.initState(); - widget.focusNode.addListener(_onFocusChanged); + widget.focusNode.addListener(_handleFocusChanged); } @override void dispose() { - widget.focusNode.removeListener(_onFocusChanged); + widget.focusNode.removeListener(_handleFocusChanged); super.dispose(); } @@ -80,7 +80,10 @@ class _FieldWrapper extends State with ThemeGetter { return error; } - void _onFocusChanged() => setState(() {}); + void _handleFocusChanged() => setState(() {}); + + void _handleHoverChanged(bool isHovered) => + setState(() => _isHovered = isHovered); bool get _isFocused => widget.isFocused ?? widget.focusNode.hasFocus; @@ -145,8 +148,8 @@ class _FieldWrapper extends State with ThemeGetter { ) : null, child: MouseRegion( - onEnter: (_) => setState(() => _isHovered = true), - onExit: (_) => setState(() => _isHovered = false), + onEnter: (_) => _handleHoverChanged(true), + onExit: (_) => _handleHoverChanged(false), child: AnimatedContainer( duration: const Duration(milliseconds: 200), height: widget.size.height, diff --git a/optimus/lib/src/common/gesture_wrapper.dart b/optimus/lib/src/common/gesture_wrapper.dart new file mode 100644 index 00000000..c5036ac0 --- /dev/null +++ b/optimus/lib/src/common/gesture_wrapper.dart @@ -0,0 +1,29 @@ +import 'package:flutter/widgets.dart'; + +class GestureWrapper extends StatelessWidget { + const GestureWrapper({ + super.key, + required this.child, + required this.onHoverChanged, + required this.onPressedChanged, + this.onTap, + }); + + final Widget child; + final ValueChanged onHoverChanged; + final ValueChanged onPressedChanged; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) => GestureDetector( + onTap: onTap, + onTapDown: (_) => onPressedChanged(true), + onTapUp: (_) => onPressedChanged(false), + onTapCancel: () => onPressedChanged(false), + child: MouseRegion( + onEnter: (_) => onHoverChanged(true), + onExit: (_) => onHoverChanged(false), + child: child, + ), + ); +} diff --git a/optimus/lib/src/date_time_field.dart b/optimus/lib/src/date_time_field.dart index 1732ff2a..3bde6a07 100644 --- a/optimus/lib/src/date_time_field.dart +++ b/optimus/lib/src/date_time_field.dart @@ -63,7 +63,7 @@ class _OptimusDateTimeFieldState extends State _isClearVisible = value != null && widget.isClearEnabled; } - void _showPickerDialog() { + void _handleTap() { FocusScope.of(context).requestFocus(FocusNode()); showModalBottomSheet( context: context, @@ -76,7 +76,7 @@ class _OptimusDateTimeFieldState extends State ); } - void _onInputChanged(String v) { + void _handleInputChanged(String v) { if (v.isEmpty) { widget.onChanged(null); } @@ -89,12 +89,12 @@ class _OptimusDateTimeFieldState extends State Widget build(BuildContext context) => OptimusInputField( controller: _controller, readOnly: true, - onTap: _showPickerDialog, + onTap: _handleTap, error: widget.error, label: widget.label, isClearEnabled: _isClearVisible, trailing: GestureDetector( - onTap: _showPickerDialog, + onTap: _handleTap, child: Icon( OptimusIcons.calendar, size: 20, @@ -102,6 +102,6 @@ class _OptimusDateTimeFieldState extends State ), ), placeholder: widget.placeholder, - onChanged: _onInputChanged, + onChanged: _handleInputChanged, ); } diff --git a/optimus/lib/src/dialogs/dialog_wrapper.dart b/optimus/lib/src/dialogs/dialog_wrapper.dart index fbefafe4..d73dd349 100644 --- a/optimus/lib/src/dialogs/dialog_wrapper.dart +++ b/optimus/lib/src/dialogs/dialog_wrapper.dart @@ -57,6 +57,8 @@ class _DialogWrapperState extends State super.dispose(); } + void _handleClose() => hide(); + @override void show({ required Widget title, @@ -71,7 +73,7 @@ class _DialogWrapperState extends State builder: (context) => OptimusDialog.nonModal( title: title, content: content, - close: hide, + close: _handleClose, isDismissible: isDismissible, actions: actions, size: size, @@ -95,11 +97,11 @@ class _DialogWrapperState extends State children: [ GestureDetector( behavior: HitTestBehavior.opaque, - onTap: hide, + onTap: _handleClose, ), OptimusInlineDialog( content: content, - close: hide, + close: _handleClose, actions: actions, anchorKey: anchorKey, ), diff --git a/optimus/lib/src/dropdown/dropdown.dart b/optimus/lib/src/dropdown/dropdown.dart index 65ee0bad..3f257b1f 100644 --- a/optimus/lib/src/dropdown/dropdown.dart +++ b/optimus/lib/src/dropdown/dropdown.dart @@ -356,14 +356,16 @@ class _DropdownItemState extends State<_DropdownItem> with ThemeGetter { DropdownTapInterceptor.of(context)?.onTap(); } + void _handleHighlightChanged(bool isHighlighted) => + setState(() => _isHighlighted = isHighlighted); + @override Widget build(BuildContext context) => SizedBox( width: AnchoredOverlay.of(context)?.width, height: _itemMinHeight, child: InkWell( highlightColor: theme.colors.primary, - onHighlightChanged: (isHighlighted) => - setState(() => _isHighlighted = isHighlighted), + onHighlightChanged: _handleHighlightChanged, onTap: _handleItemTap, child: _isHighlighted ? OptimusTheme( diff --git a/optimus/lib/src/dropdown/dropdown_select.dart b/optimus/lib/src/dropdown/dropdown_select.dart index 8b822dc7..411b0c65 100644 --- a/optimus/lib/src/dropdown/dropdown_select.dart +++ b/optimus/lib/src/dropdown/dropdown_select.dart @@ -187,11 +187,13 @@ class _DropdownSelectState extends State> { VoidCallback? get _onTap => widget.embeddedSearch != null ? _showOverlay : null; - void _onClearAllTap() { + void _handleClearAllTap() { _effectiveController.clear(); widget.onTextChanged?.call(''); } + void _handleClose() => _removeOverlay(); + void _handleChipTap() { if (_effectiveFocusNode.hasFocus) { _removeOverlay(); @@ -231,7 +233,7 @@ class _DropdownSelectState extends State> { OverlayEntry _createOverlayEntry() => OverlayEntry( builder: (context) { - void onTapDown(TapDownDetails details) { + void handleTapDown(TapDownDetails details) { bool hitTest(RenderBox box) => box.hitTest( BoxHitTestResult(), position: box.globalToLocal(details.globalPosition), @@ -255,9 +257,9 @@ class _DropdownSelectState extends State> { return GestureDetector( key: const Key('OptimusDropdownOverlay'), behavior: HitTestBehavior.translucent, - onTapDown: onTapDown, + onTapDown: handleTapDown, child: DropdownTapInterceptor( - onTap: widget.multiselect ? () {} : _removeOverlay, + onTap: widget.multiselect ? () {} : _handleClose, child: OptimusDropdown( items: widget.items, anchorKey: _fieldBoxKey, @@ -275,7 +277,7 @@ class _DropdownSelectState extends State> { @override Widget build(BuildContext context) { final clearAll = _isClearAllButtonVisible - ? _ClearAllButton(onTap: _onClearAllTap) + ? _ClearAllButton(onTap: _handleClearAllTap) : null; final trailing = clearAll == null && widget.trailing == null && diff --git a/optimus/lib/src/dropdown/dropdown_tap_interceptor.dart b/optimus/lib/src/dropdown/dropdown_tap_interceptor.dart index daccb840..46a47e7c 100644 --- a/optimus/lib/src/dropdown/dropdown_tap_interceptor.dart +++ b/optimus/lib/src/dropdown/dropdown_tap_interceptor.dart @@ -8,7 +8,7 @@ class DropdownTapInterceptor extends InheritedWidget { }); static DropdownTapInterceptor? of(BuildContext context) => - context.dependOnInheritedWidgetOfExactType(); + context.dependOnInheritedWidgetOfExactType(); final VoidCallback onTap; diff --git a/optimus/lib/src/expansion/expansion_tile.dart b/optimus/lib/src/expansion/expansion_tile.dart index 3e1bbccc..201bf5a5 100644 --- a/optimus/lib/src/expansion/expansion_tile.dart +++ b/optimus/lib/src/expansion/expansion_tile.dart @@ -132,49 +132,6 @@ class _OptimusExpansionTileState extends State widget.onExpansionChanged?.call(_isExpanded); } - Widget _buildChildren(BuildContext context, Widget? child) { - final theme = OptimusTheme.of(context); - final textColor = - theme.isDark ? theme.colors.neutral0 : theme.colors.neutral1000; - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTileTheme.merge( - iconColor: textColor, - textColor: textColor, - child: widget.slidableActions.isEmpty - ? _buildListTile() - : _buildSlidable(widget.slidableActions, _buildListTile()), - ), - ClipRect( - child: Align(heightFactor: _heightFactor.value, child: child), - ), - ], - ); - } - - Widget _buildListTile() => OptimusListTile( - onTap: _handleTap, - prefix: widget.leading, - title: widget.title, - contentPadding: widget.contentPadding, - subtitle: widget.subtitle, - suffix: widget.trailing ?? - RotationTransition( - turns: _iconTurns, - child: const Icon(Icons.expand_more), - ), - ); - - Widget _buildSlidable(List actions, Widget child) => OptimusSlidable( - actions: actions, - hasBorders: widget.hasBorders, - actionsWidth: widget.actionsWidth, - isEnabled: widget.slidableActions.isNotEmpty, - child: child, - ); - @override void didChangeDependencies() { final ThemeData theme = Theme.of(context); @@ -186,10 +143,48 @@ class _OptimusExpansionTileState extends State @override Widget build(BuildContext context) { final bool closed = !_isExpanded && _controller.isDismissed; + final theme = OptimusTheme.of(context); + final textColor = + theme.isDark ? theme.colors.neutral0 : theme.colors.neutral1000; return AnimatedBuilder( animation: _controller.view, - builder: _buildChildren, + builder: (context, child) { + final listTile = OptimusListTile( + onTap: _handleTap, + prefix: widget.leading, + title: widget.title, + contentPadding: widget.contentPadding, + subtitle: widget.subtitle, + suffix: widget.trailing ?? + RotationTransition( + turns: _iconTurns, + child: const Icon(Icons.expand_more), + ), + ); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTileTheme.merge( + iconColor: textColor, + textColor: textColor, + child: widget.slidableActions.isEmpty + ? listTile + : OptimusSlidable( + actions: widget.slidableActions, + hasBorders: widget.hasBorders, + actionsWidth: widget.actionsWidth, + isEnabled: widget.slidableActions.isNotEmpty, + child: listTile, + ), + ), + ClipRect( + child: Align(heightFactor: _heightFactor.value, child: child), + ), + ], + ); + }, child: closed ? null : Column(children: widget.children), ); } diff --git a/optimus/lib/src/form/date_input_field.dart b/optimus/lib/src/form/date_input_field.dart index 2f1303c7..c1235283 100644 --- a/optimus/lib/src/form/date_input_field.dart +++ b/optimus/lib/src/form/date_input_field.dart @@ -119,7 +119,7 @@ class _OptimusDateInputFieldState extends State String _formatValue(DateTime? value) => value != null ? _formatOutput(value) : ''; - String _onChanged(String value) { + String _handleChanged(String value) { if (_previousValue != value || value.isEmpty) { final result = _controller.isInputComplete ? _getDateTime(widget.format, value) @@ -202,7 +202,7 @@ class _OptimusDateInputFieldState extends State controller: _controller, error: widget.error, onSubmitted: _handleSubmitted, - onChanged: _onChanged, + onChanged: _handleChanged, keyboardType: TextInputType.number, isRequired: widget.isRequired, onTap: widget.onTap, diff --git a/optimus/lib/src/link/base_link.dart b/optimus/lib/src/link/base_link.dart index cf37b54e..77611282 100644 --- a/optimus/lib/src/link/base_link.dart +++ b/optimus/lib/src/link/base_link.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:optimus/src/common/gesture_wrapper.dart'; import 'package:optimus/src/enabled.dart'; import 'package:optimus/src/link/link_variant.dart'; import 'package:optimus/src/spacing.dart'; @@ -33,16 +34,19 @@ class BaseLink extends StatefulWidget { class _BaseLinkState extends State with ThemeGetter { bool _isHovering = false; - bool _isTappedDown = false; + bool _isPressed = false; void _handleHoverChange(bool isHovering) => setState(() => _isHovering = isHovering); + void _handlePressedChange(bool isPressed) => + setState(() => _isPressed = isPressed); + Color get _effectiveColor => widget.inherit ? _inheritedColor : _color; Color get _color { if (!_isEnabled) return widget.variant.getDisabledColor(tokens); - if (_isTappedDown) return widget.variant.getTappedColor(tokens); + if (_isPressed) return widget.variant.getTappedColor(tokens); if (_isHovering) return widget.variant.getHoveredColor(tokens); return widget.variant.getDefaultColor(tokens); @@ -87,16 +91,11 @@ class _BaseLinkState extends State with ThemeGetter { return OptimusEnabled( isEnabled: _isEnabled, - child: MouseRegion( - onEnter: (_) => _handleHoverChange(true), - onExit: (_) => _handleHoverChange(false), - child: GestureDetector( - onTap: widget.onPressed, - onTapDown: (_) => setState(() => _isTappedDown = true), - onTapUp: (_) => setState(() => _isTappedDown = false), - onTapCancel: () => setState(() => _isTappedDown = false), - child: child, - ), + child: GestureWrapper( + onHoverChanged: _handleHoverChange, + onPressedChanged: _handlePressedChange, + onTap: widget.onPressed, + child: child, ), ); } diff --git a/optimus/lib/src/number_picker/number_picker.dart b/optimus/lib/src/number_picker/number_picker.dart index 76f4387f..8b53cc9e 100644 --- a/optimus/lib/src/number_picker/number_picker.dart +++ b/optimus/lib/src/number_picker/number_picker.dart @@ -107,7 +107,7 @@ class _OptimusNumberPickerState extends State<_OptimusNumberPicker> { super.dispose(); } - void _onMinusTap() { + void _handleMinusTap() { final value = _value; final int newValue; if (value != null) { @@ -122,7 +122,7 @@ class _OptimusNumberPickerState extends State<_OptimusNumberPicker> { _updateController(newValue); } - void _onPlusTap() { + void _handlePlusTap() { final value = _value; final int newValue; if (value != null) { @@ -170,11 +170,13 @@ class _OptimusNumberPickerState extends State<_OptimusNumberPicker> { controller: _effectiveController, leading: NumberPickerButton( iconData: OptimusIcons.minus_simple, - onPressed: value == null || value > widget.min ? _onMinusTap : null, + onPressed: + value == null || value > widget.min ? _handleMinusTap : null, ), trailing: NumberPickerButton( iconData: OptimusIcons.plus_simple, - onPressed: value == null || value < widget.max ? _onPlusTap : null, + onPressed: + value == null || value < widget.max ? _handlePlusTap : null, ), focusNode: _effectiveFocusNode, inputFormatters: [ diff --git a/optimus/lib/src/radio/radio.dart b/optimus/lib/src/radio/radio.dart index a7d06f73..9df7f211 100644 --- a/optimus/lib/src/radio/radio.dart +++ b/optimus/lib/src/radio/radio.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:optimus/optimus.dart'; +import 'package:optimus/src/common/gesture_wrapper.dart'; import 'package:optimus/src/common/group_wrapper.dart'; import 'package:optimus/src/typography/presets.dart'; @@ -82,7 +83,7 @@ class OptimusRadio extends StatefulWidget { class _OptimusRadioState extends State> with ThemeGetter { bool _isHovering = false; - bool _isTappedDown = false; + bool _isPressed = false; bool get _isSelected => widget.value == widget.groupValue; @@ -95,10 +96,13 @@ class _OptimusRadioState extends State> with ThemeGetter { OptimusRadioSize.large => preset300s.copyWith(color: _textColor), }; - void _onHoverChanged(bool isHovering) => + void _handleHoverChanged(bool isHovering) => setState(() => _isHovering = isHovering); - void _onChanged() { + void _handlePressedChanged(bool isPressed) => + setState(() => _isPressed = isPressed); + + void _handleChanged() { if (!_isSelected) { widget.onChanged(widget.value); } @@ -106,7 +110,7 @@ class _OptimusRadioState extends State> with ThemeGetter { _RadioState get _state { if (!widget.isEnabled) return _RadioState.disabled; - if (_isTappedDown) return _RadioState.active; + if (_isPressed) return _RadioState.active; if (_isHovering) return _RadioState.hover; return _RadioState.basic; @@ -117,53 +121,48 @@ class _OptimusRadioState extends State> with ThemeGetter { error: widget.error, child: IgnorePointer( ignoring: !widget.isEnabled, - child: MouseRegion( - onEnter: (_) => _onHoverChanged(true), - onExit: (_) => _onHoverChanged(false), - child: GestureDetector( - onTap: _onChanged, - onTapDown: (_) => setState(() => _isTappedDown = true), - onTapUp: (_) => setState(() => _isTappedDown = false), - onTapCancel: () => setState(() => _isTappedDown = false), - child: Stack( - children: [ - Positioned( - left: 0, - top: 0, - bottom: 0, - width: _leadingSize, - child: Align( - alignment: Alignment.topLeft, - child: _RadioCircle( - state: _state, - isSelected: _isSelected, - ), + child: GestureWrapper( + onHoverChanged: _handleHoverChanged, + onPressedChanged: _handlePressedChanged, + onTap: _handleChanged, + child: Stack( + children: [ + Positioned( + left: 0, + top: 0, + bottom: 0, + width: _leadingSize, + child: Align( + alignment: Alignment.topLeft, + child: _RadioCircle( + state: _state, + isSelected: _isSelected, ), ), - ConstrainedBox( - constraints: const BoxConstraints( - minHeight: _leadingSize, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(width: _leadingSize), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: spacing25, - ), - child: DefaultTextStyle.merge( - style: _labelStyle, - child: widget.label, - ), + ), + ConstrainedBox( + constraints: const BoxConstraints( + minHeight: _leadingSize, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox(width: _leadingSize), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: spacing25, + ), + child: DefaultTextStyle.merge( + style: _labelStyle, + child: widget.label, ), ), - ], - ), + ), + ], ), - ], - ), + ), + ], ), ), ), diff --git a/optimus/lib/src/segmented_control/segmented_control.dart b/optimus/lib/src/segmented_control/segmented_control.dart index e8d3bf5f..537fa9bc 100644 --- a/optimus/lib/src/segmented_control/segmented_control.dart +++ b/optimus/lib/src/segmented_control/segmented_control.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:optimus/optimus.dart'; +import 'package:optimus/src/common/gesture_wrapper.dart'; import 'package:optimus/src/common/group_wrapper.dart'; import 'package:optimus/src/typography/presets.dart'; @@ -132,14 +133,17 @@ class _OptimusSegmentedControlItem extends StatefulWidget { class _OptimusSegmentedControlItemState extends State<_OptimusSegmentedControlItem> with ThemeGetter { bool _isHovering = false; - bool _isTappedDown = false; + bool _isPressed = false; bool get _isSelected => widget.value == widget.groupValue; - void _onHoverChanged(bool isHovering) => + void _handleHoverChanged(bool isHovering) => setState(() => _isHovering = isHovering); - void _onChanged() { + void _handlePressedChanged(bool isPressed) => + setState(() => _isPressed = isPressed); + + void _handleChanged() { if (!_isSelected) { widget.onItemSelected(widget.value); } @@ -147,7 +151,7 @@ class _OptimusSegmentedControlItemState Color _color(OptimusTokens tokens) { if (!widget.isEnabled) return tokens.backgroundInteractiveNeutralDefault; - if (_isTappedDown) return tokens.backgroundInteractiveNeutralActive; + if (_isPressed) return tokens.backgroundInteractiveNeutralActive; if (_isHovering) return tokens.backgroundInteractiveNeutralHover; return _isSelected @@ -158,7 +162,7 @@ class _OptimusSegmentedControlItemState Color _foregroundColor(OptimusTokens tokens) { if (!widget.isEnabled) return tokens.textDisabled; - return (_isSelected || _isHovering || _isTappedDown) + return (_isSelected || _isHovering || _isPressed) ? tokens.textStaticPrimary : tokens.textStaticTertiary; } @@ -168,37 +172,32 @@ class _OptimusSegmentedControlItemState final tokens = OptimusTheme.of(context).tokens; return LayoutBuilder( - builder: (context, constrains) => MouseRegion( - onEnter: (_) => _onHoverChanged(true), - onExit: (_) => _onHoverChanged(false), - child: GestureDetector( - onTap: _onChanged, - onTapDown: (_) => setState(() => _isTappedDown = true), - onTapUp: (_) => setState(() => _isTappedDown = false), - onTapCancel: () => setState(() => _isTappedDown = false), - child: Padding( - padding: const EdgeInsets.all(spacing25), - child: AnimatedContainer( - duration: const Duration(milliseconds: 100), - curve: Curves.easeOut, - constraints: BoxConstraints(minHeight: widget.size.value), - padding: const EdgeInsets.symmetric(vertical: spacing50), - decoration: BoxDecoration( - color: _color(tokens), - borderRadius: const BorderRadius.all(borderRadius100), - ), - alignment: Alignment.center, - child: DefaultTextStyle.merge( - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - maxLines: widget.maxLines, - style: preset200s.copyWith(color: _foregroundColor(tokens)), - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: constrains.adaptivePadding, - ), - child: widget.child, + builder: (context, constrains) => GestureWrapper( + onTap: _handleChanged, + onHoverChanged: _handleHoverChanged, + onPressedChanged: _handlePressedChanged, + child: Padding( + padding: const EdgeInsets.all(spacing25), + child: AnimatedContainer( + duration: const Duration(milliseconds: 100), + curve: Curves.easeOut, + constraints: BoxConstraints(minHeight: widget.size.value), + padding: const EdgeInsets.symmetric(vertical: spacing50), + decoration: BoxDecoration( + color: _color(tokens), + borderRadius: const BorderRadius.all(borderRadius100), + ), + alignment: Alignment.center, + child: DefaultTextStyle.merge( + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + maxLines: widget.maxLines, + style: preset200s.copyWith(color: _foregroundColor(tokens)), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: constrains.adaptivePadding, ), + child: widget.child, ), ), ), diff --git a/optimus/lib/src/select.dart b/optimus/lib/src/select.dart index ab174035..65f79607 100644 --- a/optimus/lib/src/select.dart +++ b/optimus/lib/src/select.dart @@ -55,14 +55,17 @@ class _OptimusSelectState extends State> with ThemeGetter { super.dispose(); } + void _handleOpenedChanged(bool isOpened) => + setState(() => _isOpened = isOpened); + @override Widget build(BuildContext context) => OverlayController( onItemSelected: widget.onItemSelected, focusNode: _node, anchorKey: _selectFieldKey, items: widget.items, - onShown: () => setState(() => _isOpened = true), - onHidden: () => setState(() => _isOpened = false), + onShown: () => _handleOpenedChanged(true), + onHidden: () => _handleOpenedChanged(false), child: GestureDetector( onTap: () => widget.isEnabled ? _node.requestFocus() : null, child: Focus( diff --git a/optimus/lib/src/slidable/slide_action.dart b/optimus/lib/src/slidable/slide_action.dart index 37f776fe..c803d344 100644 --- a/optimus/lib/src/slidable/slide_action.dart +++ b/optimus/lib/src/slidable/slide_action.dart @@ -11,7 +11,7 @@ class OptimusSlideAction extends StatelessWidget { final Widget child; final Color color; - final void Function() onTap; + final VoidCallback onTap; @override Widget build(BuildContext context) => CustomSlidableAction( diff --git a/optimus/lib/src/step_bar/step_bar_compact.dart b/optimus/lib/src/step_bar/step_bar_compact.dart index 42c21af4..5c812c2b 100644 --- a/optimus/lib/src/step_bar/step_bar_compact.dart +++ b/optimus/lib/src/step_bar/step_bar_compact.dart @@ -57,7 +57,7 @@ class _OptimusCompactStepBarState extends State }); } - void _expand() { + void _handleExpand() { if (_overlayEntry != null) return; _overlayEntry = _createOverlayEntry().also((it) { @@ -66,7 +66,7 @@ class _OptimusCompactStepBarState extends State }); } - void _collapse() { + void _handleCollapse() { final overlay = _overlayEntry; if (overlay != null) { @@ -84,7 +84,7 @@ class _OptimusCompactStepBarState extends State builder: (context) => IgnorePointer( ignoring: _animationController.isAnimating, child: GestureDetector( - onTap: _collapse, + onTap: _handleCollapse, child: Material( color: Colors.transparent, child: _StepBarData( @@ -111,7 +111,7 @@ class _OptimusCompactStepBarState extends State currentItem: widget.currentItem, maxItem: widget.maxItem, child: GestureDetector( - onTap: _expand, + onTap: _handleExpand, child: CompositedTransformTarget( link: _layerLink, child: _CollapsedCompactStepBar( diff --git a/optimus/lib/src/toggle.dart b/optimus/lib/src/toggle.dart index f9254317..6f32ae82 100644 --- a/optimus/lib/src/toggle.dart +++ b/optimus/lib/src/toggle.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:optimus/optimus.dart'; +import 'package:optimus/src/common/gesture_wrapper.dart'; /// The toggle component serves as a visual representation of a binary choice, /// providing users with a clear way to control settings, preferences, or switch @@ -36,7 +37,7 @@ class OptimusToggle extends StatefulWidget { class _OptimusToggleState extends State with ThemeGetter { bool _isHovered = false; - bool _isTapped = false; + bool _isPressed = false; bool get _isEnabled => widget.onChanged != null; @@ -56,59 +57,61 @@ class _OptimusToggleState extends State with ThemeGetter { Color get _color => !_isEnabled ? context.tokens.backgroundDisabled - : _isTapped + : _isPressed ? _tappedColor : _isHovered ? _hoveredColor : _defaultColor; + void _handleHoveredChanged(bool isHovered) => + setState(() => _isHovered = isHovered); + + void _handlePressedChanged(bool isPressed) => + setState(() => _isPressed = isPressed); + @override Widget build(BuildContext context) => IgnorePointer( ignoring: !_isEnabled, - child: MouseRegion( - onEnter: (event) => setState(() => _isHovered = true), - onExit: (event) => setState(() => _isHovered = false), - child: GestureDetector( - onTapDown: (details) => setState(() => _isTapped = true), - onTapUp: (details) => setState(() => _isTapped = false), - onTap: () => widget.onChanged?.call(!widget.isChecked), - child: AnimatedContainer( - width: _toggleWidth, - height: _toggleHeight, - duration: _animationDuration, - padding: const EdgeInsets.all(spacing50), - decoration: ShapeDecoration( - color: _color, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(56)), - ), + child: GestureWrapper( + onHoverChanged: _handleHoveredChanged, + onPressedChanged: _handlePressedChanged, + onTap: () => widget.onChanged?.call(!widget.isChecked), + child: AnimatedContainer( + width: _toggleWidth, + height: _toggleHeight, + duration: _animationDuration, + padding: const EdgeInsets.all(spacing50), + decoration: ShapeDecoration( + color: _color, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(56)), ), - child: Stack( - children: [ - if (widget.offIcon != null || widget.onChanged != null) - Row( - children: [ - _Icon( - icon: widget.offIcon, - isVisible: widget.isChecked, - isEnabled: _isEnabled, - ), - const SizedBox(width: spacing50), - _Icon( - icon: widget.onIcon, - isVisible: !widget.isChecked, - isEnabled: _isEnabled, - ), - ], - ), - AnimatedPositioned( - duration: _animationDuration, - curve: _animationCurve, - left: _leftPadding, - child: const _Knob(), + ), + child: Stack( + children: [ + if (widget.offIcon != null || widget.onChanged != null) + Row( + children: [ + _Icon( + icon: widget.offIcon, + isVisible: widget.isChecked, + isEnabled: _isEnabled, + ), + const SizedBox(width: spacing50), + _Icon( + icon: widget.onIcon, + isVisible: !widget.isChecked, + isEnabled: _isEnabled, + ), + ], ), - ], - ), + AnimatedPositioned( + duration: _animationDuration, + curve: _animationCurve, + left: _leftPadding, + child: const _Knob(), + ), + ], ), ), ), diff --git a/optimus/lib/src/tooltip/tooltip_controller.dart b/optimus/lib/src/tooltip/tooltip_controller.dart index 6d167c96..25b403d2 100644 --- a/optimus/lib/src/tooltip/tooltip_controller.dart +++ b/optimus/lib/src/tooltip/tooltip_controller.dart @@ -49,7 +49,7 @@ class _TooltipControllerState extends State { OverlayEntry _createEntry() => OverlayEntry( builder: (context) => GestureDetector( - onTapDown: (_) => _hideTooltip(), + onTapDown: (_) => _handleHide(), child: Stack( alignment: AlignmentDirectional.topCenter, children: [ @@ -64,16 +64,16 @@ class _TooltipControllerState extends State { ), ); - void _showTooltip({bool autoHide = true}) { + void _handleShow({bool autoHide = true}) { if (_entry != null) return; _entry = _createEntry().also((it) { Overlay.of(context).insert(it); - if (autoHide) Future.delayed(widget.autoHideDuration, _hideTooltip); + if (autoHide) Future.delayed(widget.autoHideDuration, _handleHide); }); } - void _hideTooltip() { + void _handleHide() { _entry?.remove(); _entry?.dispose(); _entry = null; @@ -81,10 +81,10 @@ class _TooltipControllerState extends State { @override Widget build(BuildContext context) => MouseRegion( - onEnter: (_) => _showTooltip(autoHide: false), - onExit: (_) => _hideTooltip(), + onEnter: (_) => _handleShow(autoHide: false), + onExit: (_) => _handleHide(), child: GestureDetector( - onTap: _showTooltip, + onTap: _handleShow, child: widget.child, ), ); diff --git a/optimus/lib/src/typography/highlight.dart b/optimus/lib/src/typography/highlight.dart index 64e151a4..ced28686 100644 --- a/optimus/lib/src/typography/highlight.dart +++ b/optimus/lib/src/typography/highlight.dart @@ -26,6 +26,7 @@ class OptimusHighlightMajor extends StatelessWidget { @override Widget build(BuildContext context) => OptimusTypography( + // ignore: prefer-correct-handler-name, false trigger on global function resolveStyle: preset600b, align: align, child: child, @@ -51,6 +52,7 @@ class OptimusHighlightModerate extends StatelessWidget { @override Widget build(BuildContext context) => OptimusTypography( + // ignore: prefer-correct-handler-name, false trigger on global function resolveStyle: preset500b, align: align, child: child, @@ -76,6 +78,7 @@ class OptimusHighlightMinor extends StatelessWidget { @override Widget build(BuildContext context) => OptimusTypography( + // ignore: prefer-correct-handler-name, false trigger on global function resolveStyle: preset400b, align: align, child: child, diff --git a/optimus/lib/src/typography/title.dart b/optimus/lib/src/typography/title.dart index 91869037..fc1404be 100644 --- a/optimus/lib/src/typography/title.dart +++ b/optimus/lib/src/typography/title.dart @@ -24,6 +24,7 @@ class OptimusPageTitle extends StatelessWidget { @override Widget build(BuildContext context) => OptimusTypography( + // ignore: prefer-correct-handler-name, false trigger on global function resolveStyle: preset700b, align: align, child: child, @@ -48,6 +49,7 @@ class OptimusSectionTitle extends StatelessWidget { @override Widget build(BuildContext context) => OptimusTypography( + // ignore: prefer-correct-handler-name, false trigger on global function resolveStyle: preset600b, align: align, child: child, @@ -73,6 +75,7 @@ class OptimusSubsectionTitle extends StatelessWidget { @override Widget build(BuildContext context) => OptimusTypography( + // ignore: prefer-correct-handler-name, false trigger on global function resolveStyle: preset500b, align: align, child: child, @@ -98,6 +101,7 @@ class OptimusSubtitle extends StatelessWidget { @override Widget build(BuildContext context) => OptimusTypography( color: OptimusTypographyColor.secondary, + // ignore: prefer-correct-handler-name, false trigger on global function resolveStyle: preset400b, align: align, child: child, diff --git a/optimus/test/date_formatter_test.dart b/optimus/test/src/form/date_formatter_test.dart similarity index 100% rename from optimus/test/date_formatter_test.dart rename to optimus/test/src/form/date_formatter_test.dart diff --git a/storybook/lib/main.dart b/storybook/lib/main.dart index aa5eef44..82489c2d 100644 --- a/storybook/lib/main.dart +++ b/storybook/lib/main.dart @@ -77,7 +77,7 @@ class _MyAppState extends State { .getString(_keyTheme) .toThemeMode(); - Future _saveThemeMode(ThemeMode themeMode) async => + Future _handleThemeChanged(ThemeMode themeMode) async => (await SharedPreferences.getInstance()) .setString(_keyTheme, themeMode.name); @@ -92,7 +92,7 @@ class _MyAppState extends State { plugins: [ ThemeModePlugin( initialTheme: snapshot.data, - onThemeChanged: _saveThemeMode, + onThemeChanged: _handleThemeChanged, ), DeviceFramePlugin(), ], diff --git a/storybook/lib/stories/checkbox.dart b/storybook/lib/stories/checkbox.dart index b05e373e..db072a6a 100644 --- a/storybook/lib/stories/checkbox.dart +++ b/storybook/lib/stories/checkbox.dart @@ -20,6 +20,8 @@ class _CheckboxStory extends StatefulWidget { class _CheckboxStoryState extends State<_CheckboxStory> { bool _checked = false; + void _handleChanged(bool isChecked) => setState(() => _checked = isChecked); + @override Widget build(BuildContext context) { final k = widget.knobs; @@ -37,9 +39,7 @@ class _CheckboxStoryState extends State<_CheckboxStory> { initial: OptimusCheckboxSize.large, ), isChecked: _checked, - onChanged: (b) => setState(() { - _checked = b; - }), + onChanged: _handleChanged, ), ), ); diff --git a/storybook/lib/stories/checkbox_group.dart b/storybook/lib/stories/checkbox_group.dart index 4ccbc210..2f4ed4fb 100644 --- a/storybook/lib/stories/checkbox_group.dart +++ b/storybook/lib/stories/checkbox_group.dart @@ -22,6 +22,8 @@ class _CheckboxGroupStory extends StatefulWidget { class _CheckboxGroupStoryState extends State<_CheckboxGroupStory> { Iterable _checks = [1, 3]; + void _handleChanged(Iterable values) => setState(() => _checks = values); + @override Widget build(BuildContext context) { final k = widget.knobs; @@ -29,7 +31,7 @@ class _CheckboxGroupStoryState extends State<_CheckboxGroupStory> { return OptimusCheckboxGroup( label: k.text(label: 'Label', initial: 'Checkbox Group Label'), error: k.text(label: 'Error'), - onChanged: (values) => setState(() => _checks = values), + onChanged: _handleChanged, isEnabled: k.boolean(label: 'Enabled', initial: true), values: _checks, items: const [ diff --git a/storybook/lib/stories/checkbox_nested.dart b/storybook/lib/stories/checkbox_nested.dart index e7f7635e..44131cb4 100644 --- a/storybook/lib/stories/checkbox_nested.dart +++ b/storybook/lib/stories/checkbox_nested.dart @@ -23,6 +23,12 @@ class _CheckboxGroupStoryState extends State<_CheckboxGroupStory> { final List _values = [false, true, false, false, false]; bool _outsideCheckbox = false; + void _handleChanged(int position, bool value) => + setState(() => _values[position] = value); + + void _handleOutsideChanged(bool value) => + setState(() => _outsideCheckbox = value); + @override Widget build(BuildContext context) { final k = widget.knobs; @@ -37,11 +43,7 @@ class _CheckboxGroupStoryState extends State<_CheckboxGroupStory> { label: const Text('Outside Checkbox'), isChecked: _outsideCheckbox, isEnabled: enabled, - onChanged: (bool value) { - setState(() { - _outsideCheckbox = value; - }); - }, + onChanged: _handleOutsideChanged, ), OptimusNestedCheckboxGroup( parent: const Text('Parent'), @@ -52,28 +54,27 @@ class _CheckboxGroupStoryState extends State<_CheckboxGroupStory> { OptimusNestedCheckbox( label: const Text('Checkbox 1'), isChecked: _values.first, - onChanged: (bool value) => - setState(() => _values.first = value), + onChanged: (bool value) => _handleChanged(0, value), ), OptimusNestedCheckbox( label: const Text('Checkbox 2'), isChecked: _values[1], - onChanged: (bool value) => setState(() => _values[1] = value), + onChanged: (bool value) => _handleChanged(1, value), ), OptimusNestedCheckbox( isChecked: _values[2], label: const Text('Checkbox 3'), - onChanged: (bool value) => setState(() => _values[2] = value), + onChanged: (bool value) => _handleChanged(2, value), ), OptimusNestedCheckbox( isChecked: _values[3], label: const Text('Checkbox 4'), - onChanged: (bool value) => setState(() => _values[3] = value), + onChanged: (bool value) => _handleChanged(3, value), ), OptimusNestedCheckbox( isChecked: _values.last, label: const Text('Checkbox 5'), - onChanged: (bool value) => setState(() => _values.last = value), + onChanged: (bool value) => _handleChanged(4, value), ), ], ), diff --git a/storybook/lib/stories/date_time_field.dart b/storybook/lib/stories/date_time_field.dart index 6daf867a..f74c1bf6 100644 --- a/storybook/lib/stories/date_time_field.dart +++ b/storybook/lib/stories/date_time_field.dart @@ -25,6 +25,9 @@ class _ContentState extends State<_Content> { _dateTime = DateTime.now(); } + void _handleChanged(DateTime? dateTime) => + setState(() => _dateTime = dateTime); + @override Widget build(BuildContext context) => ConstrainedBox( constraints: const BoxConstraints(maxWidth: 400), @@ -40,9 +43,7 @@ class _ContentState extends State<_Content> { return '${d.day}/${d.month}/${d.year} ${hours.padded()}:${d.minute.padded()} $am'; }, - onChanged: (v) => setState(() { - _dateTime = v; - }), + onChanged: _handleChanged, ), ); } diff --git a/storybook/lib/stories/dialog.dart b/storybook/lib/stories/dialog.dart index 8c2dbd29..6ba77255 100644 --- a/storybook/lib/stories/dialog.dart +++ b/storybook/lib/stories/dialog.dart @@ -31,7 +31,7 @@ final Story dialogStory = Story( children: [ OptimusButton( variant: OptimusButtonVariant.primary, - onPressed: () => _showOneActionDialog( + onPressed: () => _handleShowOneActionDialog( context: context, isDismissible: isDismissible, size: OptimusDialogSize.small, @@ -43,7 +43,7 @@ final Story dialogStory = Story( ), OptimusButton( variant: OptimusButtonVariant.primary, - onPressed: () => _showTwoActionDialog( + onPressed: () => _handleShowTwoActionDialog( context: context, isDismissible: isDismissible, size: OptimusDialogSize.small, @@ -55,7 +55,7 @@ final Story dialogStory = Story( ), OptimusButton( variant: OptimusButtonVariant.primary, - onPressed: () => _showThreeActionDialog( + onPressed: () => _handleShowThreeActionDialog( context: context, isDismissible: isDismissible, size: OptimusDialogSize.small, @@ -76,7 +76,7 @@ final Story dialogStory = Story( children: [ OptimusButton( variant: OptimusButtonVariant.primary, - onPressed: () => _showOneActionDialog( + onPressed: () => _handleShowOneActionDialog( context: context, isDismissible: isDismissible, size: OptimusDialogSize.regular, @@ -88,7 +88,7 @@ final Story dialogStory = Story( ), OptimusButton( variant: OptimusButtonVariant.primary, - onPressed: () => _showTwoActionDialog( + onPressed: () => _handleShowTwoActionDialog( context: context, isDismissible: isDismissible, size: OptimusDialogSize.regular, @@ -100,7 +100,7 @@ final Story dialogStory = Story( ), OptimusButton( variant: OptimusButtonVariant.primary, - onPressed: () => _showThreeActionDialog( + onPressed: () => _handleShowThreeActionDialog( context: context, isDismissible: isDismissible, size: OptimusDialogSize.regular, @@ -121,7 +121,7 @@ final Story dialogStory = Story( children: [ OptimusButton( variant: OptimusButtonVariant.primary, - onPressed: () => _showOneActionDialog( + onPressed: () => _handleShowOneActionDialog( context: context, isDismissible: isDismissible, size: OptimusDialogSize.large, @@ -133,7 +133,7 @@ final Story dialogStory = Story( ), OptimusButton( variant: OptimusButtonVariant.primary, - onPressed: () => _showTwoActionDialog( + onPressed: () => _handleShowTwoActionDialog( context: context, isDismissible: isDismissible, size: OptimusDialogSize.large, @@ -145,7 +145,7 @@ final Story dialogStory = Story( ), OptimusButton( variant: OptimusButtonVariant.primary, - onPressed: () => _showThreeActionDialog( + onPressed: () => _handleShowThreeActionDialog( context: context, isDismissible: isDismissible, size: OptimusDialogSize.large, @@ -166,7 +166,7 @@ final Story dialogStory = Story( children: [ OptimusButton( variant: OptimusButtonVariant.primary, - onPressed: () => _showCustomContentDialog( + onPressed: () => _handleShowCustomContentDialog( context: context, isDismissible: isDismissible, size: OptimusDialogSize.large, @@ -184,7 +184,7 @@ final Story dialogStory = Story( }, ); -Future _showThreeActionDialog({ +Future _handleShowThreeActionDialog({ required BuildContext context, required bool isDismissible, required OptimusDialogSize size, @@ -206,7 +206,7 @@ Future _showThreeActionDialog({ ], ); -Future _showTwoActionDialog({ +Future _handleShowTwoActionDialog({ required BuildContext context, required bool isDismissible, required OptimusDialogSize size, @@ -227,7 +227,7 @@ Future _showTwoActionDialog({ ], ); -Future _showOneActionDialog({ +Future _handleShowOneActionDialog({ required BuildContext context, required bool isDismissible, required OptimusDialogSize size, @@ -245,7 +245,7 @@ Future _showOneActionDialog({ actions: const [OptimusDialogAction(title: Text('Close'))], ); -Future _showCustomContentDialog({ +Future _handleShowCustomContentDialog({ required BuildContext context, required bool isDismissible, required OptimusDialogSize size, diff --git a/storybook/lib/stories/nested_overlays.dart b/storybook/lib/stories/nested_overlays.dart index 1ecf602e..73b0d81a 100644 --- a/storybook/lib/stories/nested_overlays.dart +++ b/storybook/lib/stories/nested_overlays.dart @@ -29,7 +29,7 @@ class _Content extends StatelessWidget { final WidgetBuilder contentBuilder; - Route _onGenerateRoute(RouteSettings settings) { + Route _handleGenerateRoute(RouteSettings settings) { late WidgetBuilder builder; switch (settings.name) { case 'initialRoute': @@ -54,7 +54,7 @@ class _Content extends StatelessWidget { Expanded( child: Navigator( initialRoute: 'initialRoute', - onGenerateRoute: _onGenerateRoute, + onGenerateRoute: _handleGenerateRoute, ), ), const _Bar(), diff --git a/storybook/lib/stories/number_picker.dart b/storybook/lib/stories/number_picker.dart index 97da8be1..90c1db51 100644 --- a/storybook/lib/stories/number_picker.dart +++ b/storybook/lib/stories/number_picker.dart @@ -30,6 +30,8 @@ class _Content extends StatefulWidget { class _ContentState extends State<_Content> { int? _value = 8; + void _handleChanged(int? value) => setState(() => _value = value); + @override Widget build(BuildContext context) => Column( mainAxisAlignment: MainAxisAlignment.center, @@ -38,7 +40,7 @@ class _ContentState extends State<_Content> { const SizedBox(height: 16), OptimusNumberPickerFormField( enabled: widget.isEnabled, - onChanged: (v) => setState(() => _value = v), + onChanged: _handleChanged, initialValue: 8, min: 5, max: 15, diff --git a/storybook/lib/stories/radio.dart b/storybook/lib/stories/radio.dart index 866b536f..ce65f9e0 100644 --- a/storybook/lib/stories/radio.dart +++ b/storybook/lib/stories/radio.dart @@ -39,9 +39,8 @@ class RadioExample extends StatefulWidget { class _RadioExampleState extends State { String _groupValue = _options.first; - void _onChanged(String newValue) { - setState(() => _groupValue = newValue); - } + void _handleChanged(String newValue) => + setState(() => _groupValue = newValue); @override Widget build(BuildContext context) => SingleChildScrollView( @@ -55,7 +54,7 @@ class _RadioExampleState extends State { size: widget.size, value: i, groupValue: _groupValue, - onChanged: _onChanged, + onChanged: _handleChanged, error: widget.error, ), ) @@ -106,6 +105,8 @@ class _RadioGroupExample extends StatefulWidget { class _RadioGroupExampleState extends State<_RadioGroupExample> { String _groupValue = ''; + void _handleChanged(String value) => setState(() => _groupValue = value); + @override Widget build(BuildContext context) => OptimusRadioGroup( size: widget.size, @@ -113,7 +114,7 @@ class _RadioGroupExampleState extends State<_RadioGroupExample> { label: widget.label, error: widget.error, isEnabled: widget.isEnabled, - onChanged: (value) => setState(() => _groupValue = value), + onChanged: _handleChanged, items: _options .map((i) => OptimusGroupItem(label: Text(i), value: i)) .toList(), diff --git a/storybook/lib/stories/segmented_control.dart b/storybook/lib/stories/segmented_control.dart index 4e06efd6..857c7356 100644 --- a/storybook/lib/stories/segmented_control.dart +++ b/storybook/lib/stories/segmented_control.dart @@ -128,6 +128,8 @@ class _SegmentedControlExample extends StatefulWidget { class _SegmentedControlExampleState extends State<_SegmentedControlExample> { String _value = 'Another long option'; + void _handleItemSelected(String value) => setState(() => _value = value); + @override Widget build(BuildContext context) => OptimusSegmentedControl( value: _value, @@ -138,7 +140,7 @@ class _SegmentedControlExampleState extends State<_SegmentedControlExample> { error: widget.error, isEnabled: widget.isEnabled, direction: widget.direction, - onItemSelected: (value) => setState(() => _value = value), + onItemSelected: _handleItemSelected, items: widget.options .map((i) => OptimusGroupItem(label: Text(i), value: i)) .toList(), diff --git a/storybook/lib/stories/select_input.dart b/storybook/lib/stories/select_input.dart index 0ebfcd31..276df9e7 100644 --- a/storybook/lib/stories/select_input.dart +++ b/storybook/lib/stories/select_input.dart @@ -22,9 +22,19 @@ class _SelectInputStoryState extends State { final List _selectedValues = []; String _searchToken = ''; - void _onTextChanged(String text) { - setState(() => _searchToken = text.toLowerCase()); - } + void _handleTextChanged(String text) => + setState(() => _searchToken = text.toLowerCase()); + + void _handleChanged(bool isMultiselect, String value) => setState(() { + _selectedValue = value; + if (isMultiselect) { + if (_selectedValues.contains(value)) { + _selectedValues.remove(value); + } else { + _selectedValues.add(value); + } + } + }); @override Widget build(BuildContext context) { @@ -54,21 +64,13 @@ class _SelectInputStoryState extends State { leading: k.boolean(label: 'Leading Icon') ? const Icon(OptimusIcons.search) : null, - onTextChanged: k.boolean(label: 'Searchable') ? _onTextChanged : null, + onTextChanged: + k.boolean(label: 'Searchable') ? _handleTextChanged : null, prefix: prefix.isNotEmpty ? Text(prefix) : null, suffix: suffix.isNotEmpty ? Text(suffix) : null, trailing: trailing != null ? Icon(trailing) : null, showLoader: showLoader, - onChanged: (i) => setState(() { - _selectedValue = i; - if (multiselect) { - if (_selectedValues.contains(i)) { - _selectedValues.remove(i); - } else { - _selectedValues.add(i); - } - } - }), + onChanged: (value) => _handleChanged(multiselect, value), size: k.options( label: 'Size', initial: OptimusWidgetSize.large, @@ -101,7 +103,7 @@ class _SelectInputStoryState extends State { embeddedSearch: embeddedSearch ? OptimusDropdownEmbeddedSearch( initialValue: _searchToken, - onTextChanged: _onTextChanged, + onTextChanged: _handleTextChanged, placeholder: 'Search', ) : null, diff --git a/storybook/lib/stories/toggle.dart b/storybook/lib/stories/toggle.dart index c9f20d46..36b91a7d 100644 --- a/storybook/lib/stories/toggle.dart +++ b/storybook/lib/stories/toggle.dart @@ -17,6 +17,8 @@ class _ToggleStory extends StatefulWidget { class _ToggleStoryState extends State<_ToggleStory> { bool _isChecked = false; + void _handleChanged(bool isChecked) => setState(() => _isChecked = isChecked); + @override Widget build(BuildContext context) { final k = context.knobs; @@ -27,8 +29,7 @@ class _ToggleStoryState extends State<_ToggleStory> { offIcon: useIcons ? OptimusIcons.lock : null, onIcon: useIcons ? OptimusIcons.unlock : null, isChecked: _isChecked, - onChanged: - isEnabled ? (value) => setState(() => _isChecked = value) : null, + onChanged: isEnabled ? _handleChanged : null, ); } } diff --git a/storybook/lib/stories/welcome.dart b/storybook/lib/stories/welcome.dart index 28182833..957dde0d 100644 --- a/storybook/lib/stories/welcome.dart +++ b/storybook/lib/stories/welcome.dart @@ -33,14 +33,14 @@ final welcomeStory = Story( text: Text('mews.design'), size: OptimusStandaloneLinkSize.large, isExternal: true, - onPressed: _launchUrl, + onPressed: _handlePressed, ), ], ), ), ); -Future _launchUrl() async { +Future _handlePressed() async { if (!await launchUrl(_url)) { throw Exception('Could not launch $_url'); }