diff --git a/example/lib/inputs/text_field.dart b/example/lib/inputs/text_field.dart new file mode 100644 index 0000000..845d7e2 --- /dev/null +++ b/example/lib/inputs/text_field.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:rio/rio.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; + +@widgetbook.UseCase( + name: 'Default', + type: RioTextField, + path: "inputs", +) +Widget useCaseRioTextFieldDefault(BuildContext context) { + return _buildUseCase(context, filled: false); +} + +@widgetbook.UseCase( + name: 'Filled', + type: RioTextField, + path: "inputs", +) +Widget useCaseRioTextFieldFilled(BuildContext context) { + return _buildUseCase(context, filled: true); +} + +Widget _buildUseCase( + BuildContext context, { + required bool filled, +}) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RioTextField( + theme: RioTextFieldTheme( + filled: filled, + ), + decoration: const RioTextFieldDecoration( + label: Text("Text"), + hintText: "Hint", + helperText: "Helper", + ), + ), + const SizedBox(height: 16), + RioTextField( + theme: RioTextFieldTheme( + filled: filled, + ), + decoration: const RioTextFieldDecoration( + label: Text("Text"), + hintText: "Hint", + helperText: "Helper", + errorText: "Error", + ), + ), + ], + ), + ), + ); +} diff --git a/example/lib/main.directories.g.dart b/example/lib/main.directories.g.dart index 0eb5bcc..7b1d834 100644 --- a/example/lib/main.directories.g.dart +++ b/example/lib/main.directories.g.dart @@ -13,6 +13,7 @@ import 'package:widgetbook/widgetbook.dart' as _i1; import 'core/color_scheme.dart' as _i2; import 'inputs/button.dart' as _i3; +import 'inputs/text_field.dart' as _i4; final directories = <_i1.WidgetbookNode>[ _i1.WidgetbookFolder( @@ -50,7 +51,20 @@ final directories = <_i1.WidgetbookNode>[ builder: _i3.useCaseRioButtonSolid, ), ], - ) + ), + _i1.WidgetbookComponent( + name: 'RioTextField', + useCases: [ + _i1.WidgetbookUseCase( + name: 'Default', + builder: _i4.useCaseRioTextFieldDefault, + ), + _i1.WidgetbookUseCase( + name: 'Filled', + builder: _i4.useCaseRioTextFieldFilled, + ), + ], + ), ], ), ]; diff --git a/lib/rio.dart b/lib/rio.dart index 4972e65..315a683 100644 --- a/lib/rio.dart +++ b/lib/rio.dart @@ -3,3 +3,4 @@ library rio; export 'src/color_scheme.dart'; export 'src/theme.dart'; export 'src/button.dart'; +export 'src/text_field.dart'; diff --git a/lib/src/text_field.dart b/lib/src/text_field.dart new file mode 100644 index 0000000..c50adee --- /dev/null +++ b/lib/src/text_field.dart @@ -0,0 +1,283 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:rio/rio.dart'; +import 'package:theme_tailor_annotation/theme_tailor_annotation.dart'; + +part 'text_field.tailor.dart'; +part 'text_field.freezed.dart'; + +@freezed +class RioTextFieldDecoration with _$RioTextFieldDecoration { + const factory RioTextFieldDecoration({ + Widget? label, + String? hintText, + String? helperText, + String? errorText, + }) = _RioTextFieldDecoration; +} + +@TailorMixinComponent() +class RioTextFieldTheme extends ThemeExtension + with DiagnosticableTreeMixin, _$RioTextFieldThemeTailorMixin { + const RioTextFieldTheme({ + this.margin = EdgeInsets.zero, + this.contentPadding = + const EdgeInsets.symmetric(horizontal: 12, vertical: 16), + this.borderRadius, + this.filled = false, + }); + @override + final EdgeInsets margin; + @override + final EdgeInsets contentPadding; + @override + final BorderRadius? borderRadius; + @override + final bool filled; +} + +class RioTextField extends StatefulWidget { + const RioTextField({ + super.key, + this.theme, + this.decoration, + this.color, + this.autofocus = false, + this.focusNode, + this.autocorrect = true, + this.autofillHints, + this.buildCounter, + this.inputFormatters, + this.controller, + this.obscureText = false, + this.keyboardAppearance, + this.keyboardType, + this.maxLength, + this.onChanged, + this.textInputAction, + this.strutStyle, + this.textAlign = TextAlign.start, + this.textAlignVertical, + this.textDirection, + this.textCapitalization = TextCapitalization.none, + this.readOnly = false, + this.showCursor, + this.enableSuggestions = false, + this.maxLengthEnforcement, + this.minLines, + this.maxLines = 1, + this.expands = false, + this.onTap, + this.onEditingComplete, + this.onSubmitted, + this.disabled = false, + this.cursorWidth = 2.0, + this.cursorHeight, + this.cursorRadius, + this.cursorColor, + this.scrollPadding = const EdgeInsets.all(20.0), + this.enableInteractiveSelection = true, + this.dragStartBehavior = DragStartBehavior.start, + this.scrollController, + this.scrollPhysics, + this.smartDashesType, + this.smartQuotesType, + this.contextMenuBuilder, + this.mouseCursor, + this.obscuringCharacter = '•', + }); + final Color? color; + final RioTextFieldTheme? theme; + final RioTextFieldDecoration? decoration; + + final bool autofocus; + final FocusNode? focusNode; + final bool autocorrect; + final Iterable? autofillHints; + final Widget? Function( + BuildContext, { + required int currentLength, + required bool isFocused, + required int? maxLength, + })? buildCounter; + final List? inputFormatters; + final TextEditingController? controller; + final bool obscureText; + final Brightness? keyboardAppearance; + final TextInputType? keyboardType; + final int? maxLength; + final void Function(String)? onChanged; + final TextInputAction? textInputAction; + final StrutStyle? strutStyle; + final TextAlign textAlign; + final TextAlignVertical? textAlignVertical; + final TextDirection? textDirection; + final TextCapitalization textCapitalization; + final bool readOnly; + final bool? showCursor; + final bool enableSuggestions; + final MaxLengthEnforcement? maxLengthEnforcement; + final int? maxLines; + final int? minLines; + final bool expands; + final void Function()? onTap; + final void Function()? onEditingComplete; + final void Function(String?)? onSubmitted; + final bool disabled; + final double cursorWidth; + final double? cursorHeight; + final Radius? cursorRadius; + final Color? cursorColor; + final EdgeInsets scrollPadding; + final bool enableInteractiveSelection; + final DragStartBehavior dragStartBehavior; + final ScrollController? scrollController; + final ScrollPhysics? scrollPhysics; + + final SmartDashesType? smartDashesType; + final SmartQuotesType? smartQuotesType; + + final EditableTextContextMenuBuilder? contextMenuBuilder; + final MouseCursor? mouseCursor; + final String obscuringCharacter; + + @override + State createState() => _RioTextFieldState(); +} + +class _RioTextFieldState extends State { + late FocusNode _focusNode; + + RioTextFieldTheme get _theme => + widget.theme ?? RioTheme.of(context).textFieldTheme; + BorderRadius get _borderRadius => + _theme.borderRadius ?? + BorderRadius.circular(RioTheme.of(context).defaultBorderRadius); + + @override + void initState() { + super.initState(); + + _focusNode = widget.focusNode ?? FocusNode(); + } + + @override + Widget build(BuildContext context) { + var color = widget.color ?? Theme.of(context).colorScheme.primary; + final decoration = widget.decoration; + + if (decoration?.errorText != null) { + color = Theme.of(context).colorScheme.error; + } + + return Padding( + padding: _theme.margin, + child: GestureDetector( + onTap: () { + _focusNode.requestFocus(); + }, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (decoration?.label != null) + Padding( + padding: const EdgeInsets.only(bottom: 4), + child: DefaultTextStyle( + style: Theme.of(context) + .textTheme + .labelLarge! + .copyWith(fontWeight: FontWeight.bold), + child: decoration!.label!, + ), + ), + TextField( + autofocus: widget.autofocus, + autocorrect: widget.autocorrect, + autofillHints: widget.autofillHints, + buildCounter: widget.buildCounter, + inputFormatters: widget.inputFormatters, + controller: widget.controller, + obscureText: widget.obscureText, + keyboardAppearance: widget.keyboardAppearance, + keyboardType: widget.keyboardType, + maxLength: widget.maxLength, + cursorColor: widget.cursorColor ?? color, + focusNode: _focusNode, + onChanged: widget.onChanged, + textInputAction: widget.textInputAction, + strutStyle: widget.strutStyle, + textAlignVertical: widget.textAlignVertical, + textDirection: widget.textDirection, + textCapitalization: widget.textCapitalization, + readOnly: widget.readOnly, + showCursor: widget.showCursor, + enableSuggestions: widget.enableSuggestions, + maxLengthEnforcement: widget.maxLengthEnforcement, + maxLines: widget.maxLines, + minLines: widget.minLines, + expands: widget.expands, + cursorHeight: widget.cursorHeight, + cursorRadius: widget.cursorRadius, + cursorWidth: widget.cursorWidth, + enableInteractiveSelection: widget.enableInteractiveSelection, + enabled: !widget.disabled, + dragStartBehavior: widget.dragStartBehavior, + mouseCursor: widget.disabled + ? SystemMouseCursors.forbidden + : widget.mouseCursor, + obscuringCharacter: widget.obscuringCharacter, + onTap: widget.onTap, + onSubmitted: widget.onSubmitted, + onEditingComplete: widget.onEditingComplete, + contextMenuBuilder: widget.contextMenuBuilder, + smartDashesType: widget.smartDashesType, + smartQuotesType: widget.smartQuotesType, + scrollController: widget.scrollController, + scrollPhysics: widget.scrollPhysics, + scrollPadding: widget.scrollPadding, + decoration: InputDecoration( + filled: _theme.filled == true, + fillColor: color.withOpacity(0.05), + focusColor: color, + contentPadding: _theme.contentPadding, + hintText: decoration?.hintText, + isDense: true, + errorText: decoration?.errorText != null ? "" : null, + errorStyle: const TextStyle(height: 0), + border: OutlineInputBorder( + borderRadius: _borderRadius, + ), + ), + ), + if (decoration?.helperText != null || + (decoration?.errorText != null && + decoration!.errorText!.isNotEmpty)) + Padding( + padding: const EdgeInsets.only(top: 6), + child: Text( + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: decoration?.errorText != null + ? Theme.of(context).colorScheme.error + : null, + ), + decoration?.errorText ?? decoration!.helperText!, + ), + ), + ], + ), + ), + ); + } + + @override + void dispose() { + if (widget.focusNode == null) { + _focusNode.dispose(); + } + super.dispose(); + } +} diff --git a/lib/src/text_field.freezed.dart b/lib/src/text_field.freezed.dart new file mode 100644 index 0000000..55b0c4d --- /dev/null +++ b/lib/src/text_field.freezed.dart @@ -0,0 +1,209 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'text_field.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$RioTextFieldDecoration { + Widget? get label => throw _privateConstructorUsedError; + String? get hintText => throw _privateConstructorUsedError; + String? get helperText => throw _privateConstructorUsedError; + String? get errorText => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $RioTextFieldDecorationCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RioTextFieldDecorationCopyWith<$Res> { + factory $RioTextFieldDecorationCopyWith(RioTextFieldDecoration value, + $Res Function(RioTextFieldDecoration) then) = + _$RioTextFieldDecorationCopyWithImpl<$Res, RioTextFieldDecoration>; + @useResult + $Res call( + {Widget? label, String? hintText, String? helperText, String? errorText}); +} + +/// @nodoc +class _$RioTextFieldDecorationCopyWithImpl<$Res, + $Val extends RioTextFieldDecoration> + implements $RioTextFieldDecorationCopyWith<$Res> { + _$RioTextFieldDecorationCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? label = freezed, + Object? hintText = freezed, + Object? helperText = freezed, + Object? errorText = freezed, + }) { + return _then(_value.copyWith( + label: freezed == label + ? _value.label + : label // ignore: cast_nullable_to_non_nullable + as Widget?, + hintText: freezed == hintText + ? _value.hintText + : hintText // ignore: cast_nullable_to_non_nullable + as String?, + helperText: freezed == helperText + ? _value.helperText + : helperText // ignore: cast_nullable_to_non_nullable + as String?, + errorText: freezed == errorText + ? _value.errorText + : errorText // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$RioTextFieldDecorationImplCopyWith<$Res> + implements $RioTextFieldDecorationCopyWith<$Res> { + factory _$$RioTextFieldDecorationImplCopyWith( + _$RioTextFieldDecorationImpl value, + $Res Function(_$RioTextFieldDecorationImpl) then) = + __$$RioTextFieldDecorationImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {Widget? label, String? hintText, String? helperText, String? errorText}); +} + +/// @nodoc +class __$$RioTextFieldDecorationImplCopyWithImpl<$Res> + extends _$RioTextFieldDecorationCopyWithImpl<$Res, + _$RioTextFieldDecorationImpl> + implements _$$RioTextFieldDecorationImplCopyWith<$Res> { + __$$RioTextFieldDecorationImplCopyWithImpl( + _$RioTextFieldDecorationImpl _value, + $Res Function(_$RioTextFieldDecorationImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? label = freezed, + Object? hintText = freezed, + Object? helperText = freezed, + Object? errorText = freezed, + }) { + return _then(_$RioTextFieldDecorationImpl( + label: freezed == label + ? _value.label + : label // ignore: cast_nullable_to_non_nullable + as Widget?, + hintText: freezed == hintText + ? _value.hintText + : hintText // ignore: cast_nullable_to_non_nullable + as String?, + helperText: freezed == helperText + ? _value.helperText + : helperText // ignore: cast_nullable_to_non_nullable + as String?, + errorText: freezed == errorText + ? _value.errorText + : errorText // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$RioTextFieldDecorationImpl + with DiagnosticableTreeMixin + implements _RioTextFieldDecoration { + const _$RioTextFieldDecorationImpl( + {this.label, this.hintText, this.helperText, this.errorText}); + + @override + final Widget? label; + @override + final String? hintText; + @override + final String? helperText; + @override + final String? errorText; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'RioTextFieldDecoration(label: $label, hintText: $hintText, helperText: $helperText, errorText: $errorText)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'RioTextFieldDecoration')) + ..add(DiagnosticsProperty('label', label)) + ..add(DiagnosticsProperty('hintText', hintText)) + ..add(DiagnosticsProperty('helperText', helperText)) + ..add(DiagnosticsProperty('errorText', errorText)); + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RioTextFieldDecorationImpl && + (identical(other.label, label) || other.label == label) && + (identical(other.hintText, hintText) || + other.hintText == hintText) && + (identical(other.helperText, helperText) || + other.helperText == helperText) && + (identical(other.errorText, errorText) || + other.errorText == errorText)); + } + + @override + int get hashCode => + Object.hash(runtimeType, label, hintText, helperText, errorText); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$RioTextFieldDecorationImplCopyWith<_$RioTextFieldDecorationImpl> + get copyWith => __$$RioTextFieldDecorationImplCopyWithImpl< + _$RioTextFieldDecorationImpl>(this, _$identity); +} + +abstract class _RioTextFieldDecoration implements RioTextFieldDecoration { + const factory _RioTextFieldDecoration( + {final Widget? label, + final String? hintText, + final String? helperText, + final String? errorText}) = _$RioTextFieldDecorationImpl; + + @override + Widget? get label; + @override + String? get hintText; + @override + String? get helperText; + @override + String? get errorText; + @override + @JsonKey(ignore: true) + _$$RioTextFieldDecorationImplCopyWith<_$RioTextFieldDecorationImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/src/text_field.tailor.dart b/lib/src/text_field.tailor.dart new file mode 100644 index 0000000..dfe9220 --- /dev/null +++ b/lib/src/text_field.tailor.dart @@ -0,0 +1,79 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, unused_element, unnecessary_cast + +part of 'text_field.dart'; + +// ************************************************************************** +// TailorAnnotationsGenerator +// ************************************************************************** + +mixin _$RioTextFieldThemeTailorMixin + on ThemeExtension, DiagnosticableTreeMixin { + EdgeInsets get margin; + EdgeInsets get contentPadding; + BorderRadius? get borderRadius; + bool get filled; + + @override + RioTextFieldTheme copyWith({ + EdgeInsets? margin, + EdgeInsets? contentPadding, + BorderRadius? borderRadius, + bool? filled, + }) { + return RioTextFieldTheme( + margin: margin ?? this.margin, + contentPadding: contentPadding ?? this.contentPadding, + borderRadius: borderRadius ?? this.borderRadius, + filled: filled ?? this.filled, + ); + } + + @override + RioTextFieldTheme lerp( + covariant ThemeExtension? other, double t) { + if (other is! RioTextFieldTheme) return this as RioTextFieldTheme; + return RioTextFieldTheme( + margin: t < 0.5 ? margin : other.margin, + contentPadding: t < 0.5 ? contentPadding : other.contentPadding, + borderRadius: t < 0.5 ? borderRadius : other.borderRadius, + filled: t < 0.5 ? filled : other.filled, + ); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is RioTextFieldTheme && + const DeepCollectionEquality().equals(margin, other.margin) && + const DeepCollectionEquality() + .equals(contentPadding, other.contentPadding) && + const DeepCollectionEquality() + .equals(borderRadius, other.borderRadius) && + const DeepCollectionEquality().equals(filled, other.filled)); + } + + @override + int get hashCode { + return Object.hash( + runtimeType.hashCode, + const DeepCollectionEquality().hash(margin), + const DeepCollectionEquality().hash(contentPadding), + const DeepCollectionEquality().hash(borderRadius), + const DeepCollectionEquality().hash(filled), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'RioTextFieldTheme')) + ..add(DiagnosticsProperty('margin', margin)) + ..add(DiagnosticsProperty('contentPadding', contentPadding)) + ..add(DiagnosticsProperty('borderRadius', borderRadius)) + ..add(DiagnosticsProperty('filled', filled)); + } +} diff --git a/lib/src/theme.dart b/lib/src/theme.dart index 1feeb40..0475851 100644 --- a/lib/src/theme.dart +++ b/lib/src/theme.dart @@ -12,6 +12,7 @@ class RioTheme extends ThemeExtension required this.colorScheme, this.defaultBorderRadius = 10, this.buttonTheme = const RioButtonTheme(), + this.textFieldTheme = const RioTextFieldTheme(), }); @override @@ -21,6 +22,8 @@ class RioTheme extends ThemeExtension @override final RioButtonTheme buttonTheme; @override + final RioTextFieldTheme textFieldTheme; + @override Brightness get brightness => colorScheme.brightness; static RioTheme of(BuildContext context) { diff --git a/lib/src/theme.tailor.dart b/lib/src/theme.tailor.dart index ca4900a..ade77ce 100644 --- a/lib/src/theme.tailor.dart +++ b/lib/src/theme.tailor.dart @@ -13,6 +13,7 @@ mixin _$RioThemeTailorMixin double get defaultBorderRadius; RioColorScheme get colorScheme; RioButtonTheme get buttonTheme; + RioTextFieldTheme get textFieldTheme; Brightness get brightness; @override @@ -20,12 +21,14 @@ mixin _$RioThemeTailorMixin double? defaultBorderRadius, RioColorScheme? colorScheme, RioButtonTheme? buttonTheme, + RioTextFieldTheme? textFieldTheme, Brightness? brightness, }) { return RioTheme( defaultBorderRadius: defaultBorderRadius ?? this.defaultBorderRadius, colorScheme: colorScheme ?? this.colorScheme, buttonTheme: buttonTheme ?? this.buttonTheme, + textFieldTheme: textFieldTheme ?? this.textFieldTheme, ); } @@ -37,6 +40,8 @@ mixin _$RioThemeTailorMixin t < 0.5 ? defaultBorderRadius : other.defaultBorderRadius, colorScheme: colorScheme.lerp(other.colorScheme, t) as RioColorScheme, buttonTheme: buttonTheme.lerp(other.buttonTheme, t) as RioButtonTheme, + textFieldTheme: + textFieldTheme.lerp(other.textFieldTheme, t) as RioTextFieldTheme, ); } @@ -51,6 +56,8 @@ mixin _$RioThemeTailorMixin .equals(colorScheme, other.colorScheme) && const DeepCollectionEquality() .equals(buttonTheme, other.buttonTheme) && + const DeepCollectionEquality() + .equals(textFieldTheme, other.textFieldTheme) && const DeepCollectionEquality() .equals(brightness, other.brightness)); } @@ -62,6 +69,7 @@ mixin _$RioThemeTailorMixin const DeepCollectionEquality().hash(defaultBorderRadius), const DeepCollectionEquality().hash(colorScheme), const DeepCollectionEquality().hash(buttonTheme), + const DeepCollectionEquality().hash(textFieldTheme), const DeepCollectionEquality().hash(brightness), ); } @@ -74,6 +82,7 @@ mixin _$RioThemeTailorMixin ..add(DiagnosticsProperty('defaultBorderRadius', defaultBorderRadius)) ..add(DiagnosticsProperty('colorScheme', colorScheme)) ..add(DiagnosticsProperty('buttonTheme', buttonTheme)) + ..add(DiagnosticsProperty('textFieldTheme', textFieldTheme)) ..add(DiagnosticsProperty('brightness', brightness)); } } diff --git a/pubspec.yaml b/pubspec.yaml index 770fe2e..3ef5ac5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,16 +9,18 @@ environment: dependencies: flutter: sdk: flutter + freezed_annotation: ^2.4.1 loading_animation_widget: ^1.2.0+4 theme_tailor_annotation: ^2.0.2 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 + flutter_lints: ^3.0.1 theme_tailor: ^2.0.2 - build_runner: ^2.4.6 - widgetbook_generator: ^3.3.0 + build_runner: ^2.4.7 + widgetbook_generator: ^3.5.0 + freezed: ^2.4.5 flutter: