ui_kit/feat/button-components #134

Merged
hugo merged 35 commits from ui_kit/feat/button-components into master 2023-02-16 08:58:03 +00:00
26 changed files with 611 additions and 346 deletions
Showing only changes of commit 96781880f4 - Show all commits

View File

@ -19,7 +19,4 @@ import 'package:wyatt_ui_components/src/features/features.dart';
extension ThemeComponentBuildContext on BuildContext {
ComponentThemeData get components => ComponentTheme.of(this);
TextTheme get textTheme => Theme.of(this).textTheme;
ColorScheme get colorScheme => Theme.of(this).colorScheme;
ButtonThemeData get buttonTheme => Theme.of(this).buttonTheme;
}

View File

@ -1,4 +1,3 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
@ -52,4 +51,7 @@ class MultiColor {
}
return b;
}
@override
String toString() => 'MultiColor(_colors: $_colors, _color: $_color)';
}

View File

@ -62,4 +62,10 @@ abstract class ButtonStyle<T> {
///
/// Default to `null`
final BoxShadow? shadow;
@override
String toString() =>
'ButtonStyle(radius: $radius, padding: $padding, foregroundColors: '
'$foregroundColors, backgroundColors: $backgroundColors, borderColors: '
'$borderColors, stroke: $stroke, shadow: $shadow)';
}

View File

@ -18,7 +18,6 @@ import 'dart:ui';
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:flutter/widgets.dart';
import 'package:wyatt_ui_components/src/core/extensions/build_context_extensions.dart';
import 'package:wyatt_ui_components/src/core/utils/multi_color.dart';
import 'package:wyatt_ui_components/src/domain/entities/buttons/button_style.dart';
@ -38,19 +37,6 @@ class FileSelectionButtonStyle extends ButtonStyle<FileSelectionButtonStyle> {
super.shadow,
});
/// Used in negociation to build a style from Flutter default values.
factory FileSelectionButtonStyle.fromFlutter(BuildContext context) =>
FileSelectionButtonStyle(
title: context.textTheme.labelLarge,
subTitle: context.textTheme.labelSmall,
radius: (context.buttonTheme.shape is RoundedRectangleBorder)
? (context.buttonTheme.shape as RoundedRectangleBorder).borderRadius
: null,
padding: context.buttonTheme.padding,
foregroundColors: MultiColor.single(context.colorScheme.onPrimary),
backgroundColors: MultiColor.single(context.colorScheme.primary),
);
/// Used for interpolation.
static FileSelectionButtonStyle? lerp(
FileSelectionButtonStyle? a,

View File

@ -18,7 +18,6 @@ import 'dart:ui';
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:flutter/widgets.dart';
import 'package:wyatt_ui_components/src/core/extensions/build_context_extensions.dart';
import 'package:wyatt_ui_components/src/core/utils/multi_color.dart';
import 'package:wyatt_ui_components/src/domain/entities/buttons/button_style.dart';
@ -37,17 +36,6 @@ class FlatButtonStyle extends ButtonStyle<FlatButtonStyle> {
super.shadow,
});
/// Used in negociation to build a style from Flutter default values.
factory FlatButtonStyle.fromFlutter(BuildContext context) => FlatButtonStyle(
label: context.textTheme.labelLarge,
radius: (context.buttonTheme.shape is RoundedRectangleBorder)
? (context.buttonTheme.shape as RoundedRectangleBorder).borderRadius
: null,
padding: context.buttonTheme.padding,
foregroundColors: MultiColor.single(context.colorScheme.onPrimary),
backgroundColors: MultiColor.single(context.colorScheme.primary),
);
/// Used for interpolation.
static FlatButtonStyle? lerp(
FlatButtonStyle? a,
@ -86,4 +74,8 @@ class FlatButtonStyle extends ButtonStyle<FlatButtonStyle> {
///
/// Default to `TextTheme.labelLarge`
final TextStyle? label;
@override
String toString() =>
'FlatButtonStyle(label: $label), inherited: ${super.toString()}';
}

View File

@ -18,7 +18,6 @@ import 'dart:ui';
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:flutter/widgets.dart';
import 'package:wyatt_ui_components/src/core/extensions/build_context_extensions.dart';
import 'package:wyatt_ui_components/src/core/utils/multi_color.dart';
import 'package:wyatt_ui_components/src/domain/entities/buttons/button_style.dart';
@ -37,18 +36,6 @@ class SimpleIconButtonStyle extends ButtonStyle<SimpleIconButtonStyle> {
super.shadow,
});
/// Used in negociation to build a style from Flutter default values.
factory SimpleIconButtonStyle.fromFlutter(BuildContext context) =>
SimpleIconButtonStyle(
dimension: context.buttonTheme.height,
radius: (context.buttonTheme.shape is RoundedRectangleBorder)
? (context.buttonTheme.shape as RoundedRectangleBorder).borderRadius
: null,
padding: context.buttonTheme.padding,
foregroundColors: MultiColor.single(context.colorScheme.onPrimary),
backgroundColors: MultiColor.single(context.colorScheme.primary),
);
/// Used for interpolation.
static SimpleIconButtonStyle? lerp(
SimpleIconButtonStyle? a,

View File

@ -18,7 +18,6 @@ import 'dart:ui';
import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:flutter/widgets.dart';
import 'package:wyatt_ui_components/src/core/extensions/build_context_extensions.dart';
import 'package:wyatt_ui_components/src/core/utils/multi_color.dart';
import 'package:wyatt_ui_components/src/domain/entities/buttons/button_style.dart';
@ -38,19 +37,6 @@ class SymbolButtonStyle extends ButtonStyle<SymbolButtonStyle> {
super.shadow,
});
/// Used in negociation to build a style from Flutter default values.
factory SymbolButtonStyle.fromFlutter(BuildContext context) =>
SymbolButtonStyle(
label: context.textTheme.labelLarge,
dimension: context.buttonTheme.height,
radius: (context.buttonTheme.shape is RoundedRectangleBorder)
? (context.buttonTheme.shape as RoundedRectangleBorder).borderRadius
: null,
padding: context.buttonTheme.padding,
foregroundColors: MultiColor.single(context.colorScheme.onPrimary),
backgroundColors: MultiColor.single(context.colorScheme.primary),
);
/// Used for interpolation.
static SymbolButtonStyle? lerp(
SymbolButtonStyle? a,

View File

@ -25,7 +25,7 @@
UIKit and Design System used in Wyatt Studio.
## Theme negociation
## Theme negotiation
When building a component, most of its attributes can be 'null'.
The `build()` method then starts to negotiate the theme in the tree to obtain the most consistent style possible.
@ -36,7 +36,11 @@ When you build a component `Button({double? radius})`.
You have several possibilities:
1) Pass the "radius" into the constructor, `Button(radius: 12)`.
2) Set up a theme extension `ButtonThemeExtension(radius: 15)`.
3) Let `wyatt_ui_kit` "negotiate" and try to find a suitable style in the flutter theme. If this negotiation phase fails, then the style is simply not applied.
3) Let `wyatt_ui_kit` "negotiate" and try to find a suitable style in the flutter theme.
If this negotiation phase fails, then:
- If the value is mandatory: a hardcoded value in "wyatt_ui_kit" is chosen.
- If not, the style is simply not applied.
If, for example, you don't use option 1, then the radius will be 15. If you use neither option 1 nor option 2 then the radius will be 4 as this is the [official Material Design value](https://m2.material.io/design/shape/about-shape.html#shape-customization-tool).

View File

@ -19,6 +19,7 @@ import 'package:wyatt_component_copy_with_extension/component_copy_with_extensio
import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart';
import 'package:wyatt_ui_kit/src/components/buttons/cubit/invalid_button_cubit.dart';
import 'package:wyatt_ui_kit/src/components/buttons/file_selection_button/file_selection_button_screen.dart';
import 'package:wyatt_ui_kit/src/components/buttons/file_selection_button/file_selection_button_theme_resolver.dart';
import 'package:wyatt_ui_kit/src/core/mixin/export_bloc_mixin.dart';
part 'file_selection_button.g.dart';
@ -39,6 +40,7 @@ class FileSelectionButton extends FileSelectionButtonComponent
super.invalidStyle,
super.onPressed,
super.mainAxisSize,
this.themeResolver,
super.key,
});
@ -74,6 +76,8 @@ class FileSelectionButton extends FileSelectionButtonComponent
@override
FileSelectionButtonStyle? get invalidStyle =>
super.invalidStyle as FileSelectionButtonStyle?;
final FileSelectionButtonThemeResolver? themeResolver;
@override
Widget build(BuildContext context) => exportBloc(
@ -90,6 +94,7 @@ class FileSelectionButton extends FileSelectionButtonComponent
invalidStyle: invalidStyle,
onPressed: onPressed,
mainAxisSize: mainAxisSize,
themeResolver: themeResolver,
key: key,
),
);

View File

@ -75,6 +75,7 @@ class $FileSelectionButtonCWProxyImpl
invalidStyle: invalidStyle ?? _value.invalidStyle,
onPressed: onPressed ?? _value.onPressed,
mainAxisSize: mainAxisSize ?? _value.mainAxisSize,
themeResolver: themeResolver ?? _value.themeResolver,
key: key ?? _value.key,
);
}

View File

@ -22,10 +22,9 @@ 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/invalid_button_cubit.dart';
import 'package:wyatt_ui_kit/src/components/buttons/file_selection_button/file_selection_button_theme_resolver.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';
import 'package:wyatt_ui_kit/src/domain/button_theme_extension/file_selection_button_theme_extension.dart';
class FileSelectionButtonScreen
extends CubitScreen<InvalidButtonCubit, ButtonState> {
@ -42,6 +41,7 @@ class FileSelectionButtonScreen
this.invalidStyle,
this.onPressed,
this.mainAxisSize,
this.themeResolver,
super.key,
});
@ -59,138 +59,80 @@ class FileSelectionButtonScreen
final FileSelectionButtonStyle? invalidStyle;
final void Function(ControlState state)? onPressed;
final FileSelectionButtonThemeResolver? themeResolver;
@override
InvalidButtonCubit create(BuildContext context) => InvalidButtonCubit();
/// Negotiate the theme to get a complete style.
FileSelectionButtonStyle negotiate(BuildContext context, ButtonState state) {
// Define default style from Flutter values.
FileSelectionButtonStyle style =
FileSelectionButtonStyle.fromFlutter(context);
FileSelectionButtonStyle resolve(BuildContext context, ButtonState state) {
final FileSelectionButtonThemeResolver resolver = themeResolver ??
FileSelectionButtonThemeResolver(
computeExtensionValueFn: (
context,
defaultValue,
themeExtension, {
extra,
}) {
FileSelectionButtonStyle? style = defaultValue;
switch (extra?.state) {
case ControlState.disabled:
style = themeExtension.disabledStyle;
break;
case ControlState.focused:
style = themeExtension.focusedStyle;
break;
case ControlState.hovered:
style = themeExtension.hoveredStyle;
break;
case ControlState.tapped:
style = themeExtension.tappedStyle;
break;
case ControlState.normal:
case null:
style = themeExtension.normalStyle;
break;
}
if (extra?.isSelected ?? false) {
style = themeExtension.selectedStyle;
}
if (extra?.isInvalid ?? false) {
style = themeExtension.invalidStyle;
}
// Try to retrieve custom theme extension
final fileSelectionButtonThemeExtension =
context.themeExtension<FileSelectionButtonThemeExtension>();
return style;
},
customStyleFn: (context, {extra}) {
FileSelectionButtonStyle? style;
switch (extra?.state) {
case ControlState.disabled:
style = disabledStyle;
break;
case ControlState.focused:
style = focusedStyle;
break;
case ControlState.hovered:
style = hoveredStyle;
break;
case ControlState.tapped:
style = tappedStyle;
break;
case ControlState.normal:
case null:
style = normalStyle;
break;
}
if (extra?.isSelected ?? false) {
style = selectedStyle;
}
if (extra?.isInvalid ?? false) {
style = invalidStyle;
}
switch (state.state) {
case ControlState.disabled:
style = disabledStyle ??
fileSelectionButtonThemeExtension?.disabledStyle ??
style.copyWith(
foregroundColors:
MultiColor.single(context.colorScheme.onSurface),
backgroundColors: MultiColor.single(context.colorScheme.surface),
);
break;
case ControlState.hovered:
style = hoveredStyle ??
fileSelectionButtonThemeExtension?.hoveredStyle ??
style;
break;
case ControlState.tapped:
style = tappedStyle ??
fileSelectionButtonThemeExtension?.tappedStyle ??
style;
break;
case ControlState.focused:
style = focusedStyle ??
fileSelectionButtonThemeExtension?.focusedStyle ??
style;
break;
case ControlState.normal:
style = normalStyle ??
fileSelectionButtonThemeExtension?.normalStyle ??
style;
break;
}
// Apply extra theme
if (state.isSelected) {
// TODO(hpcl): enhance copyWith to copy only non-null attributes of an object
style = style.copyWith(
title: (selectedStyle ??
fileSelectionButtonThemeExtension?.selectedStyle ??
style)
.title,
subTitle: (selectedStyle ??
fileSelectionButtonThemeExtension?.selectedStyle ??
style)
.subTitle,
radius: (selectedStyle ??
fileSelectionButtonThemeExtension?.selectedStyle ??
style)
.radius,
padding: (selectedStyle ??
fileSelectionButtonThemeExtension?.selectedStyle ??
style)
.padding,
foregroundColors: (selectedStyle ??
fileSelectionButtonThemeExtension?.selectedStyle ??
style)
.foregroundColors,
backgroundColors: (selectedStyle ??
fileSelectionButtonThemeExtension?.selectedStyle ??
style)
.backgroundColors,
borderColors: (selectedStyle ??
fileSelectionButtonThemeExtension?.selectedStyle ??
style)
.borderColors,
stroke: (selectedStyle ??
fileSelectionButtonThemeExtension?.selectedStyle ??
style)
.stroke,
shadow: (selectedStyle ??
fileSelectionButtonThemeExtension?.selectedStyle ??
style)
.shadow,
);
}
if (state.isInvalid) {
// TODO(hpcl): enhance copyWith to copy only non-null attributes of an object
style = style.copyWith(
title: (invalidStyle ??
fileSelectionButtonThemeExtension?.invalidStyle ??
style)
.title,
subTitle: (invalidStyle ??
fileSelectionButtonThemeExtension?.invalidStyle ??
style)
.subTitle,
radius: (invalidStyle ??
fileSelectionButtonThemeExtension?.invalidStyle ??
style)
.radius,
padding: (invalidStyle ??
fileSelectionButtonThemeExtension?.invalidStyle ??
style)
.padding,
foregroundColors: (invalidStyle ??
fileSelectionButtonThemeExtension?.invalidStyle ??
style)
.foregroundColors,
backgroundColors: (invalidStyle ??
fileSelectionButtonThemeExtension?.invalidStyle ??
style)
.backgroundColors,
borderColors: (invalidStyle ??
fileSelectionButtonThemeExtension?.invalidStyle ??
style)
.borderColors,
stroke: (invalidStyle ??
fileSelectionButtonThemeExtension?.invalidStyle ??
style)
.stroke,
shadow: (invalidStyle ??
fileSelectionButtonThemeExtension?.invalidStyle ??
style)
.shadow,
);
}
return style;
return style;
},
);
return resolver.negotiate(context, extra: state);
}
Widget _border(
@ -217,7 +159,7 @@ class FileSelectionButtonScreen
@override
Widget onBuild(BuildContext context, ButtonState state) {
final style = negotiate(context, state);
final style = resolve(context, state);
return Focus(
onFocusChange: (hasFocus) =>

View File

@ -0,0 +1,59 @@
// 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';
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/core/helpers/theme_resolver.dart';
import 'package:wyatt_ui_kit/wyatt_ui_kit.dart';
class FileSelectionButtonThemeResolver extends ThemeResolver<
FileSelectionButtonStyle, FileSelectionButtonThemeExtension, ButtonState> {
const FileSelectionButtonThemeResolver({
required this.computeExtensionValueFn,
required this.customStyleFn,
});
@override
FileSelectionButtonStyle computeDefaultValue(
BuildContext context, {
ButtonState? extra,
}) =>
FileSelectionButtonStyle(
title: context.textTheme.labelLarge,
subTitle: context.textTheme.labelSmall,
radius: (context.buttonTheme.shape is RoundedRectangleBorder)
? (context.buttonTheme.shape as RoundedRectangleBorder).borderRadius
: null,
padding: context.buttonTheme.padding,
foregroundColors: MultiColor.single(context.colorScheme.onPrimary),
backgroundColors: MultiColor.single(context.colorScheme.primary),
);
@override
final FileSelectionButtonStyle? Function(
BuildContext context,
FileSelectionButtonStyle defaultValue,
FileSelectionButtonThemeExtension themeExtension, {
ButtonState? extra,
}) computeExtensionValueFn;
@override
final FileSelectionButtonStyle? Function(
BuildContext context, {
ButtonState? extra,
}) customStyleFn;
}

View File

@ -19,6 +19,7 @@ import 'package:wyatt_component_copy_with_extension/component_copy_with_extensio
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/components/buttons/flat_button/flat_button_theme_resolver.dart';
import 'package:wyatt_ui_kit/src/core/mixin/export_bloc_mixin.dart';
part 'flat_button.g.dart';
@ -37,6 +38,7 @@ class FlatButton extends FlatButtonComponent
super.tappedStyle,
super.onPressed,
super.mainAxisSize,
this.themeResolver,
super.key,
});
@ -60,6 +62,8 @@ class FlatButton extends FlatButtonComponent
@override
FlatButtonStyle? get tappedStyle => super.tappedStyle as FlatButtonStyle?;
final FlatButtonThemeResolver? themeResolver;
@override
Widget build(BuildContext context) => exportBloc(
child: FlatButtonScreen(
@ -73,6 +77,7 @@ class FlatButton extends FlatButtonComponent
tappedStyle: tappedStyle,
onPressed: onPressed,
mainAxisSize: mainAxisSize,
themeResolver: themeResolver,
key: key,
),
);

View File

@ -63,6 +63,7 @@ class $FlatButtonCWProxyImpl implements $FlatButtonComponentCWProxy {
tappedStyle: tappedStyle ?? _value.tappedStyle,
onPressed: onPressed ?? _value.onPressed,
mainAxisSize: mainAxisSize ?? _value.mainAxisSize,
themeResolver: themeResolver ?? _value.themeResolver,
key: key ?? _value.key,
);
}

View File

@ -20,11 +20,10 @@ 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/flat_button/flat_button_theme_resolver.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';
import 'package:wyatt_ui_kit/src/domain/button_theme_extension/flat_button_theme_extension.dart';
class FlatButtonScreen extends CubitScreen<ButtonCubit, ButtonState> {
const FlatButtonScreen({
@ -38,6 +37,7 @@ class FlatButtonScreen extends CubitScreen<ButtonCubit, ButtonState> {
this.tappedStyle,
this.onPressed,
this.mainAxisSize,
this.themeResolver,
super.key,
});
@ -53,49 +53,57 @@ class FlatButtonScreen extends CubitScreen<ButtonCubit, ButtonState> {
final FlatButtonStyle? tappedStyle;
final void Function(ControlState state)? onPressed;
final FlatButtonThemeResolver? themeResolver;
@override
ButtonCubit create(BuildContext context) => ButtonCubit();
/// Negotiate the theme to get a complete style.
FlatButtonStyle negotiate(BuildContext context, ControlState state) {
// Define default style from Flutter values.
FlatButtonStyle style = FlatButtonStyle.fromFlutter(context);
// Try to retrieve custom theme extension
final flatButtonThemeExtension =
context.themeExtension<FlatButtonThemeExtension>();
switch (state) {
case ControlState.disabled:
style = disabledStyle ??
flatButtonThemeExtension?.disabledStyle ??
style.copyWith(
foregroundColors:
MultiColor.single(context.colorScheme.onSurface),
backgroundColors: MultiColor.single(context.colorScheme.surface),
);
break;
case ControlState.hovered:
style = hoveredStyle ?? flatButtonThemeExtension?.hoveredStyle ?? style;
break;
case ControlState.tapped:
style = tappedStyle ?? flatButtonThemeExtension?.tappedStyle ?? style;
break;
case ControlState.focused:
style = focusedStyle ?? flatButtonThemeExtension?.focusedStyle ?? style;
break;
case ControlState.normal:
style = normalStyle ?? flatButtonThemeExtension?.normalStyle ?? style;
break;
}
return style;
FlatButtonStyle resolve(BuildContext context, ControlState state) {
final FlatButtonThemeResolver resolver = themeResolver ??
FlatButtonThemeResolver(
computeExtensionValueFn: (
context,
defaultValue,
themeExtension, {
extra,
}) {
switch (extra) {
case ControlState.disabled:
return themeExtension.disabledStyle;
case ControlState.focused:
return themeExtension.focusedStyle;
case ControlState.hovered:
return themeExtension.hoveredStyle;
case ControlState.tapped:
return themeExtension.tappedStyle;
case ControlState.normal:
case null:
return themeExtension.normalStyle;
}
},
customStyleFn: (context, {extra}) {
switch (extra) {
case ControlState.disabled:
return disabledStyle;
case ControlState.focused:
return focusedStyle;
case ControlState.hovered:
return hoveredStyle;
case ControlState.tapped:
return tappedStyle;
case ControlState.normal:
case null:
return normalStyle;
}
},
);
return resolver.negotiate(context, extra: state);
}
@override
Widget onBuild(BuildContext context, ButtonState state) {
final style = negotiate(context, state.state);
final style = resolve(context, state.state);
return Focus(
onFocusChange: (hasFocus) =>
@ -182,7 +190,6 @@ class FlatButtonScreen extends CubitScreen<ButtonCubit, ButtonState> {
Gap(style.padding?.vertical ?? 10),
/// Choose color
/// label.style.color ??
/// buttonStyle.label.style.color ??
/// context.textTheme.labelLarge.color
///
@ -191,7 +198,7 @@ class FlatButtonScreen extends CubitScreen<ButtonCubit, ButtonState> {
/// buttonStyle.foregroundColor.colors ??
/// null
///
/// More infos in `negociate()` method
/// More infos in ThemeResolver class
if (label != null) ...[
Text(
label!.text,

View File

@ -0,0 +1,84 @@
// 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';
import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart';
import 'package:wyatt_ui_kit/src/core/helpers/theme_resolver.dart';
import 'package:wyatt_ui_kit/wyatt_ui_kit.dart';
class FlatButtonThemeResolver extends ThemeResolver<FlatButtonStyle,
FlatButtonThemeExtension, ControlState> {
const FlatButtonThemeResolver({
required this.computeExtensionValueFn,
required this.customStyleFn,
});
@override
/// Values taken from <https://api.flutter.dev/flutter/material/ElevatedButton/defaultStyleOf.html>
FlatButtonStyle computeDefaultValue(
BuildContext context, {
ControlState? extra,
}) {
MultiColor backgroundColor = MultiColor.single(context.colorScheme.primary);
MultiColor foregroundColor =
MultiColor.single(context.colorScheme.onPrimary);
switch (extra) {
case ControlState.disabled:
backgroundColor =
MultiColor.single(context.colorScheme.onSurface.withOpacity(0.12));
foregroundColor =
MultiColor.single(context.colorScheme.onSurface.withOpacity(0.38));
break;
case ControlState.hovered:
backgroundColor =
MultiColor.single(context.colorScheme.primary.withOpacity(0.92));
break;
case ControlState.tapped:
backgroundColor =
MultiColor.single(context.colorScheme.primary.withOpacity(0.92));
break;
case null:
case ControlState.normal:
case ControlState.focused:
break;
}
return FlatButtonStyle(
label:
context.textTheme.labelLarge?.copyWith(color: foregroundColor.color),
radius: (context.buttonTheme.shape is RoundedRectangleBorder)
? (context.buttonTheme.shape as RoundedRectangleBorder).borderRadius
: null,
padding: context.buttonTheme.padding,
foregroundColors: foregroundColor,
backgroundColors: backgroundColor,
);
}
@override
final FlatButtonStyle? Function(
BuildContext context,
FlatButtonStyle defaultValue,
FlatButtonThemeExtension themeExtension, {
ControlState? extra,
}) computeExtensionValueFn;
@override
final FlatButtonStyle? Function(BuildContext context, {ControlState? extra})
customStyleFn;
}

View File

@ -18,6 +18,7 @@ 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/simple_icon_button/simple_icon_button_theme_resolver.dart';
import 'package:wyatt_ui_kit/src/components/buttons/simple_icon_button/simple_icon_screen.dart';
import 'package:wyatt_ui_kit/src/core/mixin/export_bloc_mixin.dart';
@ -34,6 +35,7 @@ class SimpleIconButton extends SimpleIconButtonComponent
super.focusedStyle,
super.tappedStyle,
super.onPressed,
this.themeResolver,
super.key,
});
@ -62,6 +64,7 @@ class SimpleIconButton extends SimpleIconButtonComponent
SimpleIconButtonStyle? get tappedStyle =>
super.tappedStyle as SimpleIconButtonStyle?;
final SimpleIconButtonThemeResolver? themeResolver;
@override
Widget build(BuildContext context) => exportBloc(
@ -73,6 +76,7 @@ class SimpleIconButton extends SimpleIconButtonComponent
focusedStyle: focusedStyle,
tappedStyle: tappedStyle,
onPressed: onPressed,
themeResolver: themeResolver,
key: key,
),
);

View File

@ -51,6 +51,7 @@ class $SimpleIconButtonCWProxyImpl
focusedStyle: focusedStyle ?? _value.focusedStyle,
tappedStyle: tappedStyle ?? _value.tappedStyle,
onPressed: onPressed ?? _value.onPressed,
themeResolver: themeResolver ?? _value.themeResolver,
key: key ?? _value.key,
);
}

View File

@ -0,0 +1,57 @@
// 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';
import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart';
import 'package:wyatt_ui_kit/src/core/helpers/theme_resolver.dart';
import 'package:wyatt_ui_kit/wyatt_ui_kit.dart';
class SimpleIconButtonThemeResolver extends ThemeResolver<SimpleIconButtonStyle,
SimpleIconButtonThemeExtension, ControlState> {
const SimpleIconButtonThemeResolver({
required this.computeExtensionValueFn,
required this.customStyleFn,
});
@override
SimpleIconButtonStyle computeDefaultValue(
BuildContext context, {
ControlState? extra,
}) =>
SimpleIconButtonStyle(
dimension: context.buttonTheme.height,
radius: (context.buttonTheme.shape is RoundedRectangleBorder)
? (context.buttonTheme.shape as RoundedRectangleBorder).borderRadius
: null,
padding: context.buttonTheme.padding,
foregroundColors: MultiColor.single(context.colorScheme.onPrimary),
backgroundColors: MultiColor.single(context.colorScheme.primary),
);
@override
final SimpleIconButtonStyle? Function(
BuildContext context,
SimpleIconButtonStyle defaultValue,
SimpleIconButtonThemeExtension themeExtension, {
ControlState? extra,
}) computeExtensionValueFn;
@override
final SimpleIconButtonStyle? Function(
BuildContext context, {
ControlState? extra,
}) customStyleFn;
}

View File

@ -19,11 +19,10 @@ import 'package:flutter/services.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/simple_icon_button/simple_icon_button_theme_resolver.dart';
import 'package:wyatt_ui_kit/src/components/gradients/gradient_box_border.dart';
import 'package:wyatt_ui_kit/src/components/gradients/gradient_icon.dart';
import 'package:wyatt_ui_kit/src/core/extensions/theme_extensions.dart';
import 'package:wyatt_ui_kit/src/core/helpers/linear_gradient_helper.dart';
import 'package:wyatt_ui_kit/src/domain/button_theme_extension/simple_icon_button_theme_extension.dart';
class SimpleIconButtonScreen extends CubitScreen<ButtonCubit, ButtonState> {
const SimpleIconButtonScreen({
@ -34,6 +33,7 @@ class SimpleIconButtonScreen extends CubitScreen<ButtonCubit, ButtonState> {
this.focusedStyle,
this.tappedStyle,
this.onPressed,
this.themeResolver,
super.key,
});
@ -46,55 +46,57 @@ class SimpleIconButtonScreen extends CubitScreen<ButtonCubit, ButtonState> {
final SimpleIconButtonStyle? tappedStyle;
final void Function(ControlState state)? onPressed;
final SimpleIconButtonThemeResolver? themeResolver;
@override
ButtonCubit create(BuildContext context) => ButtonCubit();
/// Negotiate the theme to get a complete style.
SimpleIconButtonStyle negotiate(BuildContext context, ControlState state) {
// Define default style from Flutter values.
SimpleIconButtonStyle style = SimpleIconButtonStyle.fromFlutter(context);
// Try to retrieve custom theme extension
final simpleIconButtonThemeExtension =
context.themeExtension<SimpleIconButtonThemeExtension>();
switch (state) {
case ControlState.disabled:
style = disabledStyle ??
simpleIconButtonThemeExtension?.disabledStyle ??
style.copyWith(
foregroundColors:
MultiColor.single(context.colorScheme.onSurface),
backgroundColors: MultiColor.single(context.colorScheme.surface),
);
break;
case ControlState.hovered:
style = hoveredStyle ??
simpleIconButtonThemeExtension?.hoveredStyle ??
style;
break;
case ControlState.tapped:
style =
tappedStyle ?? simpleIconButtonThemeExtension?.tappedStyle ?? style;
break;
case ControlState.focused:
style = focusedStyle ??
simpleIconButtonThemeExtension?.focusedStyle ??
style;
break;
case ControlState.normal:
style =
normalStyle ?? simpleIconButtonThemeExtension?.normalStyle ?? style;
break;
}
return style;
SimpleIconButtonStyle resolve(BuildContext context, ControlState state) {
final SimpleIconButtonThemeResolver resolver = themeResolver ??
SimpleIconButtonThemeResolver(
computeExtensionValueFn: (
context,
defaultValue,
themeExtension, {
extra,
}) {
switch (extra) {
case ControlState.disabled:
return themeExtension.disabledStyle;
case ControlState.focused:
return themeExtension.focusedStyle;
case ControlState.hovered:
return themeExtension.hoveredStyle;
case ControlState.tapped:
return themeExtension.tappedStyle;
case ControlState.normal:
case null:
return themeExtension.normalStyle;
}
},
customStyleFn: (context, {extra}) {
switch (extra) {
case ControlState.disabled:
return disabledStyle;
case ControlState.focused:
return focusedStyle;
case ControlState.hovered:
return hoveredStyle;
case ControlState.tapped:
return tappedStyle;
case ControlState.normal:
case null:
return normalStyle;
}
},
);
return resolver.negotiate(context, extra: state);
}
@override
Widget onBuild(BuildContext context, ButtonState state) {
final style = negotiate(context, state.state);
final style = resolve(context, state.state);
return Focus(
onFocusChange: (hasFocus) =>

View File

@ -19,6 +19,7 @@ import 'package:wyatt_component_copy_with_extension/component_copy_with_extensio
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/components/buttons/symbol_button/symbol_button_theme_resolver.dart';
import 'package:wyatt_ui_kit/src/core/mixin/export_bloc_mixin.dart';
part 'symbol_button.g.dart';
@ -36,6 +37,7 @@ class SymbolButton extends SymbolButtonComponent
super.tappedStyle,
super.selectedStyle,
super.mainAxisSize,
this.themeResolver,
super.onPressed,
super.key,
});
@ -67,6 +69,8 @@ class SymbolButton extends SymbolButtonComponent
SymbolButtonStyle? get selectedStyle =>
super.selectedStyle as SymbolButtonStyle?;
final SymbolButtonThemeResolver? themeResolver;
@override
Widget build(BuildContext context) => exportBloc(
child: SymbolButtonScreen(
@ -80,6 +84,7 @@ class SymbolButton extends SymbolButtonComponent
selectedStyle: selectedStyle,
onPressed: onPressed,
mainAxisSize: mainAxisSize,
themeResolver: themeResolver,
key: key,
),
);

View File

@ -63,6 +63,7 @@ class $SymbolButtonCWProxyImpl implements $SymbolButtonComponentCWProxy {
tappedStyle: tappedStyle ?? _value.tappedStyle,
selectedStyle: selectedStyle ?? _value.selectedStyle,
mainAxisSize: mainAxisSize ?? _value.mainAxisSize,
themeResolver: themeResolver ?? _value.themeResolver,
onPressed: onPressed ?? _value.onPressed,
key: key ?? _value.key,
);

View File

@ -21,11 +21,10 @@ 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/buttons/symbol_button/symbol_button_theme_resolver.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';
import 'package:wyatt_ui_kit/src/domain/button_theme_extension/symbol_button_theme_extension.dart';
class SymbolButtonScreen
extends CubitScreen<SelectableButtonCubit, ButtonState> {
@ -40,6 +39,7 @@ class SymbolButtonScreen
this.selectedStyle,
this.onPressed,
this.mainAxisSize,
this.themeResolver,
super.key,
});
@ -55,94 +55,79 @@ class SymbolButtonScreen
final SymbolButtonStyle? selectedStyle;
final void Function(ControlState state)? onPressed;
final SymbolButtonThemeResolver? themeResolver;
@override
SelectableButtonCubit create(BuildContext context) => SelectableButtonCubit();
/// Negotiate the theme to get a complete style.
SymbolButtonStyle negotiate(BuildContext context, ButtonState state) {
// Define default style from Flutter values.
SymbolButtonStyle style = SymbolButtonStyle.fromFlutter(context);
SymbolButtonStyle resolve(BuildContext context, ButtonState state) {
final SymbolButtonThemeResolver resolver = themeResolver ??
SymbolButtonThemeResolver(
computeExtensionValueFn: (
context,
defaultValue,
themeExtension, {
extra,
}) {
SymbolButtonStyle? style = defaultValue;
switch (extra?.state) {
case ControlState.disabled:
style = themeExtension.disabledStyle;
break;
case ControlState.focused:
style = themeExtension.focusedStyle;
break;
case ControlState.hovered:
style = themeExtension.hoveredStyle;
break;
case ControlState.tapped:
style = themeExtension.tappedStyle;
break;
case ControlState.normal:
case null:
style = themeExtension.normalStyle;
break;
}
if (extra?.isSelected ?? false) {
style = themeExtension.selectedStyle;
}
// Try to retrieve custom theme extension
final symbolButtonThemeExtension =
context.themeExtension<SymbolButtonThemeExtension>();
return style;
},
customStyleFn: (context, {extra}) {
SymbolButtonStyle? style;
switch (extra?.state) {
case ControlState.disabled:
style = disabledStyle;
break;
case ControlState.focused:
style = focusedStyle;
break;
case ControlState.hovered:
style = hoveredStyle;
break;
case ControlState.tapped:
style = tappedStyle;
break;
case ControlState.normal:
case null:
style = normalStyle;
break;
}
if (extra?.isSelected ?? false) {
style = selectedStyle;
}
switch (state.state) {
case ControlState.disabled:
style = disabledStyle ??
symbolButtonThemeExtension?.disabledStyle ??
style.copyWith(
foregroundColors:
MultiColor.single(context.colorScheme.onSurface),
backgroundColors: MultiColor.single(context.colorScheme.surface),
);
break;
case ControlState.hovered:
style =
hoveredStyle ?? symbolButtonThemeExtension?.hoveredStyle ?? style;
break;
case ControlState.tapped:
style = tappedStyle ?? symbolButtonThemeExtension?.tappedStyle ?? style;
break;
case ControlState.focused:
style =
focusedStyle ?? symbolButtonThemeExtension?.focusedStyle ?? style;
break;
case ControlState.normal:
style = normalStyle ?? symbolButtonThemeExtension?.normalStyle ?? style;
break;
}
// Apply extra theme
if (state.isSelected) {
// TODO(hpcl): enhance copyWith to copy only non-null attributes of an object
style = style.copyWith(
label: (selectedStyle ??
symbolButtonThemeExtension?.selectedStyle ??
style)
.label,
dimension: (selectedStyle ??
symbolButtonThemeExtension?.selectedStyle ??
style)
.dimension,
radius: (selectedStyle ??
symbolButtonThemeExtension?.selectedStyle ??
style)
.radius,
padding: (selectedStyle ??
symbolButtonThemeExtension?.selectedStyle ??
style)
.padding,
foregroundColors: (selectedStyle ??
symbolButtonThemeExtension?.selectedStyle ??
style)
.foregroundColors,
backgroundColors: (selectedStyle ??
symbolButtonThemeExtension?.selectedStyle ??
style)
.backgroundColors,
borderColors: (selectedStyle ??
symbolButtonThemeExtension?.selectedStyle ??
style)
.borderColors,
stroke: (selectedStyle ??
symbolButtonThemeExtension?.selectedStyle ??
style)
.stroke,
shadow: (selectedStyle ??
symbolButtonThemeExtension?.selectedStyle ??
style)
.shadow,
);
}
return style;
return style;
},
);
return resolver.negotiate(context, extra: state);
}
@override
Widget onBuild(BuildContext context, ButtonState state) {
final style = negotiate(context, state);
final style = resolve(context, state);
return Focus(
onFocusChange: (hasFocus) =>

View File

@ -0,0 +1,59 @@
// 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';
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/core/helpers/theme_resolver.dart';
import 'package:wyatt_ui_kit/wyatt_ui_kit.dart';
class SymbolButtonThemeResolver extends ThemeResolver<SymbolButtonStyle,
SymbolButtonThemeExtension, ButtonState> {
const SymbolButtonThemeResolver({
required this.computeExtensionValueFn,
required this.customStyleFn,
});
@override
SymbolButtonStyle computeDefaultValue(
BuildContext context, {
ButtonState? extra,
}) =>
SymbolButtonStyle(
label: context.textTheme.labelLarge,
dimension: context.buttonTheme.height,
radius: (context.buttonTheme.shape is RoundedRectangleBorder)
? (context.buttonTheme.shape as RoundedRectangleBorder).borderRadius
: null,
padding: context.buttonTheme.padding,
foregroundColors: MultiColor.single(context.colorScheme.onPrimary),
backgroundColors: MultiColor.single(context.colorScheme.primary),
);
@override
final SymbolButtonStyle? Function(
BuildContext context,
SymbolButtonStyle defaultValue,
SymbolButtonThemeExtension themeExtension, {
ButtonState? extra,
}) computeExtensionValueFn;
@override
final SymbolButtonStyle? Function(
BuildContext context, {
ButtonState? extra,
}) customStyleFn;
}

View File

@ -18,4 +18,7 @@ import 'package:flutter/material.dart';
extension BuildContextThemeExtension on BuildContext {
T? themeExtension<T>() => Theme.of(this).extension<T>();
TextTheme get textTheme => Theme.of(this).textTheme;
ColorScheme get colorScheme => Theme.of(this).colorScheme;
ButtonThemeData get buttonTheme => Theme.of(this).buttonTheme;
}

View File

@ -0,0 +1,84 @@
// 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';
import 'package:wyatt_ui_kit/src/core/extensions/theme_extensions.dart';
/// {@template theme_resolver}
/// In charge of theme negotiation and merge.
///
/// When you build a component `Component({double? radius})`.
/// You have several possibilities:
/// 1) Pass the "radius" into the constructor, `Component(radius: 12)`.
/// 2) Set up a theme extension `ComponentThemeExtension(radius: 15)`.
/// 3) Let `wyatt_ui_kit` "negotiate" and try to find a suitable style in the
/// flutter theme.
///
/// If this negotiation phase fails, then:
/// - If the value is mandatory: a hardcoded value in "wyatt_ui_kit" is chosen.
/// - If not, the style is simply not applied.
/// {@endtemplate}
abstract class ThemeResolver<S, T, E> {
/// {@macro theme_resolver}
const ThemeResolver();
S? Function(
BuildContext context,
S defaultValue,
T themeExtension, {
E? extra,
}) get computeExtensionValueFn;
S? Function(BuildContext context, {E? extra}) get customStyleFn;
/// Compute default value from Flutter Theme or with hardcoded values.
S computeDefaultValue(BuildContext context, {E? extra});
/// Compute values from the extension if found
S? computeExtensionValue(
BuildContext context,
S defaultValue, {
E? extra,
}) {
final themeExtension = findExtension(context);
if (themeExtension != null) {
return computeExtensionValueFn(context, defaultValue, themeExtension);
}
return defaultValue;
}
/// Compute custom value
S? computeCustomValue(
BuildContext context,
S previousPhaseValue, {
E? extra,
}) {
final customStyle = customStyleFn(context, extra: extra);
if (customStyle != null) {
return customStyle;
}
return previousPhaseValue;
}
T? findExtension(BuildContext context) => context.themeExtension<T>();
/// Choose most suitable style for a given context.
S negotiate(BuildContext context, {E? extra}) {
S style = computeDefaultValue(context, extra: extra);
style = computeExtensionValue(context, style, extra: extra) ?? style;
style = computeCustomValue(context, style, extra: extra) ?? style;
return style;
}
}