feat(ui_kit): add symbol button + enhance bloc control over state

This commit is contained in:
Hugo Pointcheval 2023-02-13 21:21:05 +01:00
parent aea71fa32c
commit 168d840b87
Signed by: hugo
GPG Key ID: 3AAC487E131E00BC
14 changed files with 721 additions and 59 deletions

View File

@ -24,11 +24,12 @@ const _color2 = Color(0xFF446DF4);
const _color3 = Color.fromARGB(255, 26, 132, 247);
const _color4 = Color.fromARGB(255, 19, 68, 228);
const _colors = [_color1, _color2];
const _colorsDarken = [_color2, _color3];
const _colorsDarken = [_color3, _color4];
const _disabled = Color(0xFF6B7280);
const _background = Color(0xFF16191D);
const _disabledBackground = Color(0xFF16191D + 0x66FFFFFF);
const _disabledColors = [Color(0xFF60656A), Color(0xFF383C40)];
const _selectedColors = [Color(0xFF50CE99), Color(0xFF339572)];
class Buttons extends StatelessWidget {
const Buttons({super.key});
@ -41,30 +42,30 @@ class Buttons extends StatelessWidget {
style: Theme.of(context).textTheme.titleLarge,
),
const Gap(20),
const FlatButton(
label: TextWrapper('Voir notre savoir faire'),
normalStyle: FlatButtonStyle(
FlatButton(
label: const TextWrapper('Voir notre savoir faire'),
normalStyle: const FlatButtonStyle(
foregroundColors: MultiColor(_colors),
backgroundColors: MultiColor.single(Colors.transparent),
borderColors: MultiColor(_colors),
stroke: 3,
),
hoveredStyle: FlatButtonStyle(
hoveredStyle: const FlatButtonStyle(
foregroundColors: MultiColor.single(Colors.white),
backgroundColors: MultiColor(_colors),
borderColors: MultiColor(_colors),
stroke: 3,
),
tappedStyle: FlatButtonStyle(
tappedStyle: const FlatButtonStyle(
foregroundColors: MultiColor.single(Colors.white),
backgroundColors: MultiColor(_colorsDarken),
borderColors: MultiColor(_colorsDarken),
stroke: 3,
),
prefix: Icon(
prefix: const Icon(
Icons.arrow_forward_rounded,
),
suffix: Icon(
suffix: const Icon(
Icons.arrow_forward_rounded,
),
),
@ -169,7 +170,7 @@ class Buttons extends StatelessWidget {
),
const Gap(20),
FlatButton(
onPressed: () => print('pressed'),
onPressed: (state) => print('pressed, from $state'),
label: TextWrapper(
'Démarrer mon projet',
style:
@ -191,6 +192,72 @@ class Buttons extends StatelessWidget {
backgroundColors: MultiColor.single(Colors.red),
),
),
const Gap(20),
ColoredBox(
color: _background,
child: Center(
child: Column(
children: [
const Gap(20),
SymbolButton(
icon: const Icon(
Icons.android,
size: 25,
),
label: const TextWrapper('Texte'),
normalStyle: const SymbolButtonStyle(
borderColors: MultiColor(_disabledColors),
foregroundColors: MultiColor.single(Colors.white),
backgroundColors: MultiColor.single(_disabledBackground),
stroke: 1,
),
hoveredStyle: const SymbolButtonStyle(
borderColors: MultiColor(_disabledColors),
foregroundColors: MultiColor.single(Colors.white),
backgroundColors: MultiColor.single(_background),
stroke: 1,
),
selectedStyle: const SymbolButtonStyle(
borderColors: MultiColor(_selectedColors),
foregroundColors: MultiColor.single(Colors.white),
backgroundColors: MultiColor.single(_disabledBackground),
stroke: 1,
),
)
..bloc.onClickUpIn()
..bloc.freeze(),
const Gap(20),
SymbolButton(
icon: const Icon(
Icons.android,
size: 25,
),
label: const TextWrapper('Texte'),
normalStyle: const SymbolButtonStyle(
borderColors: MultiColor(_disabledColors),
foregroundColors: MultiColor.single(Colors.white),
backgroundColors: MultiColor.single(_disabledBackground),
stroke: 1,
),
hoveredStyle: const SymbolButtonStyle(
borderColors: MultiColor(_disabledColors),
foregroundColors: MultiColor.single(Colors.white),
backgroundColors: MultiColor.single(_background),
stroke: 1,
),
selectedStyle: const SymbolButtonStyle(
borderColors: MultiColor(_selectedColors),
foregroundColors: MultiColor.single(Colors.white),
backgroundColors: MultiColor.single(_disabledBackground),
stroke: 1,
),
)..bloc.freeze(),
const Gap(20),
],
),
),
),
const Gap(20),
],
);
}

View File

@ -15,3 +15,4 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export './flat_button/flat_button.dart';
export './symbol_button/symbol_button.dart';

View File

@ -1,65 +1,99 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart';
class ButtonCubit extends Cubit<ControlState> {
ButtonCubit() : super(ControlState.normal);
part 'button_state.dart';
class ButtonCubit extends Cubit<ButtonState> {
ButtonCubit() : super(const ButtonState.initial(ControlState.normal));
FutureOr<void> onMouseEnter() async {
if (state == ControlState.disabled) {
if (state.isDisabled || state.isFreezed) {
return;
}
emit(ControlState.hovered);
emit(state.copyWith(state: ControlState.hovered));
}
FutureOr<void> onMouseLeave() async {
if (state == ControlState.disabled) {
if (state.isDisabled || state.isFreezed) {
return;
}
emit(ControlState.normal);
emit(state.copyWith(state: ControlState.normal));
}
FutureOr<void> onFocus() async {
if (state == ControlState.disabled) {
if (state.isDisabled || state.isFreezed) {
return;
}
emit(ControlState.focused);
emit(state.copyWith(state: ControlState.focused));
}
FutureOr<void> onUnfocus() async {
if (state == ControlState.disabled) {
if (state.isDisabled || state.isFreezed) {
return;
}
emit(ControlState.normal);
emit(state.copyWith(state: ControlState.normal));
}
FutureOr<void> onClickDown() async {
if (state == ControlState.disabled) {
if (state.isDisabled || state.isFreezed) {
return;
}
emit(ControlState.tapped);
emit(state.copyWith(state: ControlState.tapped));
}
FutureOr<void> onClickUpIn() async {
if (state == ControlState.disabled) {
if (state.isDisabled) {
return;
}
emit(ControlState.hovered);
emit(state.copyWith(state: ControlState.hovered));
}
FutureOr<void> onClickUpOut() async {
if (state == ControlState.disabled) {
if (state.isDisabled) {
return;
}
emit(ControlState.normal);
emit(state.copyWith(state: ControlState.normal));
}
FutureOr<void> disable() async {
emit(ControlState.disabled);
if (state.isFreezed) {
return;
}
emit(state.copyWith(state: ControlState.disabled));
}
FutureOr<void> enable() async {
emit(ControlState.normal);
if (state.isFreezed) {
return;
}
emit(state.copyWith(state: ControlState.normal));
}
FutureOr<void> freeze() async {
emit(state.copyWith(freezed: true));
}
FutureOr<void> unfreeze() async {
emit(state.copyWith(freezed: false));
}
}

View File

@ -0,0 +1,71 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
part of 'button_cubit.dart';
class ButtonState extends Equatable {
const ButtonState({
required this.state,
required this.selected,
required this.invalid,
required this.freezed,
});
const ButtonState.initial(this.state)
: selected = false,
invalid = false,
freezed = false;
final ControlState state;
// Not in control state, because a button state can be
// a control state + extra state
// e.g : hover + invalid, or selected + tapped
final bool selected;
final bool invalid;
final bool freezed;
bool get isDisabled => state.isDisabled();
bool get isEnabled => state.isEnabled();
bool get isFocused => state.isFocused();
bool get isHovered => state.isHovered();
bool get isTapped => state.isTapped();
// only for consistence
bool get isSelected => selected;
bool get isInvalid => invalid;
bool get isFreezed => freezed;
@override
List<Object?> get props => [state, selected, invalid, freezed];
@override
bool? get stringify => true;
ButtonState copyWith({
ControlState? state,
bool? selected,
bool? invalid,
bool? freezed,
}) =>
ButtonState(
state: state ?? this.state,
selected: selected ?? this.selected,
invalid: invalid ?? this.invalid,
freezed: freezed ?? this.freezed,
);
}

View File

@ -0,0 +1,40 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:async';
import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart';
import 'package:wyatt_ui_kit/src/components/buttons/cubit/button_cubit.dart';
class SelectableButtonCubit extends ButtonCubit {
@override
FutureOr<void> onClickUpIn() async {
if (state.isDisabled || state.isFreezed) {
return;
}
emit(
state.copyWith(state: ControlState.hovered, selected: !state.selected),
);
}
@override
FutureOr<void> onClickUpOut() async {
if (state.isDisabled || state.isFreezed) {
return;
}
emit(state.copyWith(state: ControlState.normal, selected: !state.selected));
}
}

View File

@ -17,13 +17,16 @@
import 'package:flutter/material.dart' hide ButtonStyle;
import 'package:wyatt_component_copy_with_extension/component_copy_with_extension.dart';
import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart';
import 'package:wyatt_ui_kit/src/components/buttons/cubit/button_cubit.dart';
import 'package:wyatt_ui_kit/src/components/buttons/flat_button/flat_button_screen.dart';
import 'package:wyatt_ui_kit/src/core/mixin/export_bloc_mixin.dart';
part 'flat_button.g.dart';
@ComponentCopyWithExtension()
class FlatButton extends FlatButtonComponent with $FlatButtonCWMixin {
const FlatButton({
class FlatButton extends FlatButtonComponent
with $FlatButtonCWMixin, ExportBloc<ButtonCubit> {
FlatButton({
super.prefix,
super.suffix,
super.label,
@ -33,9 +36,15 @@ class FlatButton extends FlatButtonComponent with $FlatButtonCWMixin {
super.focusedStyle,
super.tappedStyle,
super.onPressed,
super.mainAxisSize,
super.key,
});
final ButtonCubit _cubit = ButtonCubit();
@override
ButtonCubit get bloc => _cubit;
@override
FlatButtonStyle? get disabledStyle => super.disabledStyle as FlatButtonStyle?;
@ -52,7 +61,8 @@ class FlatButton extends FlatButtonComponent with $FlatButtonCWMixin {
FlatButtonStyle? get tappedStyle => super.tappedStyle as FlatButtonStyle?;
@override
Widget build(BuildContext context) => FlatButtonScreen(
Widget build(BuildContext context) => exportBloc(
child: FlatButtonScreen(
prefix: prefix,
suffix: suffix,
label: label,
@ -64,5 +74,6 @@ class FlatButton extends FlatButtonComponent with $FlatButtonCWMixin {
onPressed: onPressed,
mainAxisSize: mainAxisSize,
key: key,
),
);
}

View File

@ -10,6 +10,9 @@ class $FlatButtonCWProxyImpl implements $FlatButtonComponentCWProxy {
const $FlatButtonCWProxyImpl(this._value);
final FlatButton _value;
@override
FlatButton mainAxisSize(MainAxisSize? mainAxisSize) =>
this(mainAxisSize: mainAxisSize);
@override
FlatButton prefix(Widget? prefix) => this(prefix: prefix);
@override
FlatButton suffix(Widget? suffix) => this(suffix: suffix);
@ -31,15 +34,13 @@ class $FlatButtonCWProxyImpl implements $FlatButtonComponentCWProxy {
FlatButton tappedStyle(ButtonStyle? tappedStyle) =>
this(tappedStyle: tappedStyle);
@override
FlatButton onPressed(void Function()? onPressed) =>
FlatButton onPressed(void Function(ControlState)? onPressed) =>
this(onPressed: onPressed);
@override
FlatButton mainAxisSize(MainAxisSize? mainAxisSize) =>
this(mainAxisSize: mainAxisSize);
@override
FlatButton key(Key? key) => this(key: key);
@override
FlatButton call({
MainAxisSize? mainAxisSize,
Widget? prefix,
Widget? suffix,
TextWrapper? label,
@ -48,8 +49,7 @@ class $FlatButtonCWProxyImpl implements $FlatButtonComponentCWProxy {
ButtonStyle? hoveredStyle,
ButtonStyle? focusedStyle,
ButtonStyle? tappedStyle,
void Function()? onPressed,
MainAxisSize? mainAxisSize,
void Function(ControlState)? onPressed,
Key? key,
}) =>
FlatButton(

View File

@ -25,7 +25,7 @@ import 'package:wyatt_ui_kit/src/components/gradients/gradient_text.dart';
import 'package:wyatt_ui_kit/src/core/extensions/theme_extensions.dart';
import 'package:wyatt_ui_kit/src/core/helpers/linear_gradient_helper.dart';
class FlatButtonScreen extends CubitScreen<ButtonCubit, ControlState> {
class FlatButtonScreen extends CubitScreen<ButtonCubit, ButtonState> {
const FlatButtonScreen({
this.prefix,
this.suffix,
@ -50,7 +50,7 @@ class FlatButtonScreen extends CubitScreen<ButtonCubit, ControlState> {
final FlatButtonStyle? focusedStyle;
final FlatButtonStyle? tappedStyle;
final VoidCallback? onPressed;
final void Function(ControlState state)? onPressed;
final MainAxisSize? mainAxisSize;
@ -58,11 +58,11 @@ class FlatButtonScreen extends CubitScreen<ButtonCubit, ControlState> {
ButtonCubit create(BuildContext context) => ButtonCubit();
@override
Widget onBuild(BuildContext context, ControlState state) {
Widget onBuild(BuildContext context, ButtonState state) {
// Set a default style
FlatButtonStyle? style = normalStyle ?? const FlatButtonStyle();
switch (state) {
switch (state.state) {
case ControlState.disabled:
style = disabledStyle ?? style;
break;
@ -76,8 +76,7 @@ class FlatButtonScreen extends CubitScreen<ButtonCubit, ControlState> {
style = focusedStyle ?? style;
break;
case ControlState.normal:
case ControlState.selected:
case ControlState.invalid:
// already done
break;
}
@ -85,8 +84,8 @@ class FlatButtonScreen extends CubitScreen<ButtonCubit, ControlState> {
onFocusChange: (hasFocus) =>
hasFocus ? bloc(context).onFocus() : bloc(context).onUnfocus(),
onKeyEvent: (focusNode, event) {
if (event.logicalKey == LogicalKeyboardKey.enter && state.isFocused()) {
onPressed?.call();
if (event.logicalKey == LogicalKeyboardKey.enter && state.isFocused) {
onPressed?.call(state.state);
bloc(context).onClickUpOut();
return KeyEventResult.handled;
}
@ -105,11 +104,11 @@ class FlatButtonScreen extends CubitScreen<ButtonCubit, ControlState> {
bloc(context).onClickDown();
},
onTapUp: (details) {
onPressed?.call();
onPressed?.call(state.state);
bloc(context).onClickUpIn();
},
onTapCancel: () {
onPressed?.call();
onPressed?.call(state.state);
bloc(context).onClickUpOut();
},
child: Material(

View File

@ -0,0 +1,86 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart' hide ButtonStyle;
import 'package:wyatt_component_copy_with_extension/component_copy_with_extension.dart';
import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart';
import 'package:wyatt_ui_kit/src/components/buttons/cubit/selectable_button_cubit.dart';
import 'package:wyatt_ui_kit/src/components/buttons/symbol_button/symbol_button_screen.dart';
import 'package:wyatt_ui_kit/src/core/mixin/export_bloc_mixin.dart';
part 'symbol_button.g.dart';
@ComponentCopyWithExtension()
class SymbolButton extends SymbolButtonComponent
with $SymbolButtonCWMixin, ExportBloc<SelectableButtonCubit> {
SymbolButton({
super.icon,
super.label,
super.disabledStyle,
super.normalStyle,
super.hoveredStyle,
super.focusedStyle,
super.tappedStyle,
super.selectedStyle,
super.mainAxisSize,
super.onPressed,
super.key,
});
final SelectableButtonCubit _cubit = SelectableButtonCubit();
@override
SelectableButtonCubit get bloc => _cubit;
@override
SymbolButtonStyle? get disabledStyle =>
super.disabledStyle as SymbolButtonStyle?;
@override
SymbolButtonStyle? get normalStyle => super.normalStyle as SymbolButtonStyle?;
@override
SymbolButtonStyle? get hoveredStyle =>
super.hoveredStyle as SymbolButtonStyle?;
@override
SymbolButtonStyle? get focusedStyle =>
super.focusedStyle as SymbolButtonStyle?;
@override
SymbolButtonStyle? get tappedStyle => super.tappedStyle as SymbolButtonStyle?;
@override
SymbolButtonStyle? get selectedStyle =>
super.selectedStyle as SymbolButtonStyle?;
@override
Widget build(BuildContext context) => exportBloc(
child: SymbolButtonScreen(
icon: icon,
label: label,
disabledStyle: disabledStyle,
normalStyle: normalStyle,
hoveredStyle: hoveredStyle,
focusedStyle: focusedStyle,
tappedStyle: tappedStyle,
selectedStyle: selectedStyle,
onPressed: onPressed,
mainAxisSize: mainAxisSize,
key: key,
),
);
}

View File

@ -0,0 +1,65 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'symbol_button.dart';
// **************************************************************************
// ComponentCopyWithGenerator
// **************************************************************************
class $SymbolButtonCWProxyImpl implements $SymbolButtonComponentCWProxy {
const $SymbolButtonCWProxyImpl(this._value);
final SymbolButton _value;
@override
SymbolButton icon(Widget? icon) => this(icon: icon);
@override
SymbolButton disabledStyle(ButtonStyle? disabledStyle) =>
this(disabledStyle: disabledStyle);
@override
SymbolButton normalStyle(ButtonStyle? normalStyle) =>
this(normalStyle: normalStyle);
@override
SymbolButton hoveredStyle(ButtonStyle? hoveredStyle) =>
this(hoveredStyle: hoveredStyle);
@override
SymbolButton focusedStyle(ButtonStyle? focusedStyle) =>
this(focusedStyle: focusedStyle);
@override
SymbolButton tappedStyle(ButtonStyle? tappedStyle) =>
this(tappedStyle: tappedStyle);
@override
SymbolButton selectedStyle(ButtonStyle? selectedStyle) =>
this(selectedStyle: selectedStyle);
@override
SymbolButton onPressed(void Function(ControlState)? onPressed) =>
this(onPressed: onPressed);
@override
SymbolButton key(Key? key) => this(key: key);
@override
SymbolButton call({
Widget? icon,
ButtonStyle? disabledStyle,
ButtonStyle? normalStyle,
ButtonStyle? hoveredStyle,
ButtonStyle? focusedStyle,
ButtonStyle? tappedStyle,
ButtonStyle? selectedStyle,
void Function(ControlState)? onPressed,
Key? key,
}) =>
SymbolButton(
icon: icon ?? _value.icon,
disabledStyle: disabledStyle ?? _value.disabledStyle,
normalStyle: normalStyle ?? _value.normalStyle,
hoveredStyle: hoveredStyle ?? _value.hoveredStyle,
focusedStyle: focusedStyle ?? _value.focusedStyle,
tappedStyle: tappedStyle ?? _value.tappedStyle,
selectedStyle: selectedStyle ?? _value.selectedStyle,
onPressed: onPressed ?? _value.onPressed,
key: key ?? _value.key,
);
}
mixin $SymbolButtonCWMixin on Component {
$SymbolButtonComponentCWProxy get copyWith =>
$SymbolButtonCWProxyImpl(this as SymbolButton);
}

View File

@ -0,0 +1,249 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart' hide ButtonStyle;
import 'package:flutter/services.dart';
import 'package:gap/gap.dart';
import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart';
import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart';
import 'package:wyatt_ui_kit/src/components/buttons/cubit/button_cubit.dart';
import 'package:wyatt_ui_kit/src/components/buttons/cubit/selectable_button_cubit.dart';
import 'package:wyatt_ui_kit/src/components/gradients/gradient_box_border.dart';
import 'package:wyatt_ui_kit/src/components/gradients/gradient_text.dart';
import 'package:wyatt_ui_kit/src/core/extensions/theme_extensions.dart';
import 'package:wyatt_ui_kit/src/core/helpers/linear_gradient_helper.dart';
class SymbolButtonScreen
extends CubitScreen<SelectableButtonCubit, ButtonState> {
const SymbolButtonScreen({
this.icon,
this.label,
this.disabledStyle,
this.normalStyle,
this.hoveredStyle,
this.focusedStyle,
this.tappedStyle,
this.selectedStyle,
this.onPressed,
this.mainAxisSize,
super.key,
});
final Widget? icon;
final TextWrapper? label;
final SymbolButtonStyle? disabledStyle;
final SymbolButtonStyle? normalStyle;
final SymbolButtonStyle? hoveredStyle;
final SymbolButtonStyle? focusedStyle;
final SymbolButtonStyle? tappedStyle;
final SymbolButtonStyle? selectedStyle;
final void Function(ControlState state)? onPressed;
final MainAxisSize? mainAxisSize;
@override
SelectableButtonCubit create(BuildContext context) => SelectableButtonCubit();
@override
Widget onBuild(BuildContext context, ButtonState state) {
// Set a default style
SymbolButtonStyle? style = normalStyle ?? const SymbolButtonStyle();
switch (state.state) {
case ControlState.disabled:
style = disabledStyle ?? style;
break;
case ControlState.hovered:
style = hoveredStyle ?? style;
break;
case ControlState.tapped:
style = tappedStyle ?? style;
break;
case ControlState.focused:
style = focusedStyle ?? style;
break;
case ControlState.normal:
break;
}
if (state.isSelected) {
style = selectedStyle ?? style;
}
return Focus(
onFocusChange: (hasFocus) =>
hasFocus ? bloc(context).onFocus() : bloc(context).onUnfocus(),
onKeyEvent: (focusNode, event) {
if (event.logicalKey == LogicalKeyboardKey.enter && state.isFocused) {
onPressed?.call(state.state);
bloc(context).onClickUpOut();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
child: MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (event) {
bloc(context).onMouseEnter();
},
onExit: (event) {
bloc(context).onMouseLeave();
},
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapDown: (details) {
bloc(context).onClickDown();
},
onTapUp: (details) {
onPressed?.call(state.state);
bloc(context).onClickUpIn();
},
onTapCancel: () {
onPressed?.call(state.state);
bloc(context).onClickUpOut();
},
child: Row(
mainAxisSize: mainAxisSize ?? MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox.square(
dimension: 60,
child: AspectRatio(
aspectRatio: 1,
child: DecoratedBox(
decoration: BoxDecoration(
color: style.backgroundColors?.color ??
context.colorScheme.primary,
// If no border color => no default value
border: (style.borderColors != null)
? (style.borderColors?.isGradient ?? false)
? GradientBoxBorder(
gradient: LinearGradient(
colors: style.borderColors!.colors,
),
width: style.stroke ?? 2,
)
: Border.all(
color: style.borderColors!.color,
width: style.stroke ?? 2,
)
: null,
// if no gradient colors => no default value
gradient: (style.backgroundColors?.isGradient ?? false)
? LinearGradient(
colors: style.backgroundColors!.colors,
)
: null,
boxShadow: [
if (style.shadow != null) ...[style.shadow!]
],
borderRadius: BorderRadius.all(
Radius.circular(style.radius ?? 0),
),
),
child: ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 50,
), // min sizes for Material buttons
child: Padding(
padding: EdgeInsets.all(style.padding ?? 0),
child: Center(
// Choose color
// button.foreground.colors (gradient) ??
// buttonStyle.foregroundColor.color ??
// context.colorScheme.secondary
child: Builder(
builder: (context) {
final gradient =
(style?.foregroundColors?.isGradient ?? false)
? LinearGradient(
colors:
style!.foregroundColors!.colors,
)
: null;
final color = style?.foregroundColors?.color ??
context.colorScheme.secondary;
if (gradient != null) {
return ShaderMask(
blendMode: BlendMode.srcIn,
shaderCallback: (bounds) =>
gradient.createShader(
Rect.fromLTWH(
0,
0,
bounds.width,
bounds.height,
),
),
child: icon,
);
}
return ColorFiltered(
colorFilter:
ColorFilter.mode(color, BlendMode.srcIn),
child: icon,
);
},
),
),
),
),
),
),
),
// Choose color
// label.style.color ??
// buttonStyle.foregroundColor.color ??
// context.textTheme.titleLarge.color
//
// Choose gradient
// label.gradient ??
// buttonStyle.foregroundColor.colors ??
// null
if (label != null) ...[
const Gap(10),
Builder(
builder: (context) {
final color = label?.style?.color ??
style?.foregroundColors?.color ??
context.textTheme.titleLarge?.color;
final buttonStyleGradient =
(style?.foregroundColors?.isGradient ?? false)
? style?.foregroundColors?.colors
: null;
final gradient = label?.gradient ?? buttonStyleGradient;
return Text(
label!.text,
style: (label!.style ?? context.textTheme.titleMedium)
?.copyWith(color: color),
).toGradient(
LinearGradientHelper.fromNullableColors(
gradient,
),
);
},
),
],
],
),
),
),
);
}
}

View File

@ -0,0 +1,26 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
mixin ExportBloc<T extends StateStreamableSource<Object?>> {
T get bloc;
Widget exportBloc({required Widget child}) => BlocProvider<T>.value(
value: bloc,
child: child,
);
}

View File

@ -8,6 +8,5 @@ tapped --> hovered : on mouse click up in (the cursor is in button)
tapped --> normal : on mouse click up out (the cursor is out of button)
normal --> focused : on focus
focused --> normal : on unfocus
focused --> tapped : on space bar press down
tapped --> focused : on space bar press up
focused --> tapped : on enter press down
@enduml

View File

@ -0,0 +1,14 @@
@startuml selectable_button_state_diagram
[*] --> normal
[*] --> disabled
normal --> hovered : on mouse enter
hovered --> normal : on mouse leave
hovered --> tapped : on mouse click down
tapped --> selected : on mouse click up and the previous state was not selected
selected --> tapped : on mouse click down
tapped --> hovered : on mouse click up in (the cursor is in button)
tapped --> normal : on mouse click up out (the cursor is out of button)
normal --> focused : on focus
focused --> normal : on unfocus
focused --> tapped : on enter press down
@enduml