diff --git a/CHANGELOG.md b/CHANGELOG.md index 23922ef..9796e07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Feat - ToastSnackyBuilder is added +- GradientSnackyBuilder is added - Simplified the example app ## Breaking diff --git a/example/lib/main.dart b/example/lib/main.dart index b9c146d..1ba30be 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,10 +1,10 @@ import 'package:impaktfull_ui/impaktfull_ui.dart'; +import 'package:snacky_example/widget/example_snacky_configurator.dart'; import 'package:snacky_example/widget/snacky_example.dart'; const colorAccent = Color(0xFF7D64F2); const colorPrimary = Color(0xFF1A1A1A); -final _taostSnackyController = SnackyController(); void main() { runApp(const MyApp()); } @@ -15,24 +15,17 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return ImpaktfullThemeConfiguratorWidget( - child: SnackyConfiguratorWidget( - snackyBuilder: SimpleSnackyBuilder( - borderRadius: BorderRadius.circular(4), - ), - app: SnackyConfiguratorWidget( - snackyBuilder: const ToastSnackyBuilder(), - snackyController: _taostSnackyController, - app: MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: colorAccent), - useMaterial3: true, - ), - navigatorObservers: [ - SnackyNavigationObserver(), - ], - home: const HomeScreen(), + child: ExampleSnackyConfigurator( + child: MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: colorAccent), + useMaterial3: true, ), + navigatorObservers: [ + SnackyNavigationObserver(), + ], + home: const HomeScreen(), ), ), ); @@ -70,7 +63,18 @@ class HomeScreen extends StatelessWidget { MaterialPageRoute( builder: (context) => SnackyExampleScreen( title: 'Toast', - controller: _taostSnackyController, + controller: taostSnackyController, + ), + ), + ), + ), + ImpaktfullButton.primary( + label: 'Gradient', + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SnackyExampleScreen( + title: 'Gradient', + controller: gradientSnackyController, ), ), ), diff --git a/example/lib/widget/example_snacky_configurator.dart b/example/lib/widget/example_snacky_configurator.dart new file mode 100644 index 0000000..c15149d --- /dev/null +++ b/example/lib/widget/example_snacky_configurator.dart @@ -0,0 +1,31 @@ +import 'package:impaktfull_ui/impaktfull_ui.dart'; + +final taostSnackyController = SnackyController(); +final gradientSnackyController = SnackyController(); + +class ExampleSnackyConfigurator extends StatelessWidget { + final Widget child; + + const ExampleSnackyConfigurator({ + required this.child, + super.key, + }); + + @override + Widget build(BuildContext context) { + return SnackyConfiguratorWidget( + snackyBuilder: SimpleSnackyBuilder( + borderRadius: BorderRadius.circular(4), + ), + app: SnackyConfiguratorWidget( + snackyBuilder: const ToastSnackyBuilder(), + snackyController: taostSnackyController, + app: SnackyConfiguratorWidget( + snackyBuilder: const GradientSnackyBuilder(), + snackyController: gradientSnackyController, + app: child, + ), + ), + ); + } +} diff --git a/lib/snacky.dart b/lib/snacky.dart index 4e0d8fd..e8d17e9 100644 --- a/lib/snacky.dart +++ b/lib/snacky.dart @@ -1,3 +1,4 @@ +export 'src/builder/gradient_snacky_builder.dart'; export 'src/builder/simple_snacky_builder.dart'; export 'src/builder/toast_snacky_builder.dart'; export 'src/builder/snacky_builder.dart'; diff --git a/lib/src/builder/gradient_snacky_builder.dart b/lib/src/builder/gradient_snacky_builder.dart new file mode 100644 index 0000000..2022aee --- /dev/null +++ b/lib/src/builder/gradient_snacky_builder.dart @@ -0,0 +1,277 @@ +import 'package:flutter/material.dart'; +import 'package:snacky/src/builder/snacky_builder.dart'; +import 'package:snacky/src/controller/snacky_controller.dart'; +import 'package:snacky/src/model/cancelable_snacky.dart'; +import 'package:snacky/src/model/snacky.dart'; +import 'package:snacky/src/model/snacky_type.dart'; +import 'package:snacky/src/widget/base_snacky_widget.dart'; +import 'package:snacky/src/widget/touch_feedback.dart'; + +enum GradientSnackyTextType { + title, + subtitle, +} + +class GradientSnackyBuilder extends SnackyBuilder { + final BorderRadius borderRadius; + final EdgeInsets margin; + final EdgeInsets padding; + final Color Function(Snacky)? backgroundColorBuilder; + final Color Function(Snacky)? iconColorBuilder; + final IconData? Function(Snacky)? iconBuilder; + final BoxBorder Function(Snacky)? borderBuilder; + final TextStyle Function(Snacky, GradientSnackyTextType)? textStyleBuilder; + final bool disableInkwell; + + const GradientSnackyBuilder({ + this.backgroundColorBuilder, + this.iconBuilder, + this.iconColorBuilder, + this.borderBuilder, + this.textStyleBuilder, + this.margin = const EdgeInsets.all(16), + this.padding = const EdgeInsets.symmetric( + vertical: 16, + ), + this.disableInkwell = false, + this.borderRadius = const BorderRadius.all(Radius.circular(0)), + }); + + @override + Widget build( + BuildContext context, + CancelableSnacky cancelableSnacky, + SnackyController snackyController, + ) { + final snacky = cancelableSnacky.snacky; + final customBuilder = snacky.builder; + return BaseSnackyWidget( + cancelableSnacky: cancelableSnacky, + snackyController: snackyController, + margin: margin, + disableInkWell: disableInkwell, + borderRadius: borderRadius, + child: Builder( + builder: (context) { + if (customBuilder != null) { + return customBuilder(context, cancelableSnacky); + } + final backgroundColor = _getBackgroundColor(snacky); + return ClipRRect( + borderRadius: borderRadius, + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: backgroundColor, + border: _getBorder(snacky), + borderRadius: borderRadius, + boxShadow: const [ + BoxShadow( + color: Colors.black12, + offset: Offset(0, 2), + blurRadius: 4, + ), + ], + ), + child: Row( + children: [ + if (snacky.leadingWidgetBuilder != null) ...[ + snacky.leadingWidgetBuilder! + .call(context, cancelableSnacky), + const SizedBox(width: 8), + ] else ...[ + Builder(builder: (context) { + final leadingIcon = + _getLeaderWidget(snacky, backgroundColor); + if (leadingIcon == null) { + return const SizedBox(width: 8); + } + return Padding( + padding: const EdgeInsets.all(16), + child: leadingIcon, + ); + }), + ], + Expanded( + child: Padding( + padding: padding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + snacky.title, + style: _getTextStyle( + snacky, + GradientSnackyTextType.title, + ), + ), + if (snacky.subtitle != null) ...[ + const SizedBox(height: 4), + Text( + snacky.subtitle!, + style: _getTextStyle( + snacky, + GradientSnackyTextType.subtitle, + ), + ), + ], + if (snacky.bottomWidgetBuilder != null) ...[ + snacky.bottomWidgetBuilder!( + context, cancelableSnacky), + ], + ], + ), + ), + ), + if (snacky.trailingWidgetBuilder != null) ...[ + const SizedBox(width: 8), + snacky.trailingWidgetBuilder! + .call(context, cancelableSnacky), + ], + if (snacky.canBeClosed) ...[ + const SizedBox(width: 8), + TouchFeedback( + borderRadius: BorderRadius.circular(999), + onTap: () => cancelableSnacky.cancel(), + disableInkWell: disableInkwell, + child: Padding( + padding: const EdgeInsets.all(8), + child: Icon( + Icons.close, + color: _getTextStyle( + snacky, GradientSnackyTextType.title) + .color, + ), + ), + ), + const SizedBox(width: 8), + ] else if (snacky.onTap != null) ...[ + const SizedBox(width: 8), + Icon( + Icons.keyboard_arrow_right, + color: _getTextStyle(snacky, GradientSnackyTextType.title) + .color, + ), + const SizedBox(width: 16), + ] else ...[ + const SizedBox(width: 16), + ], + ], + ), + ), + ); + }, + ), + ); + } + + Color _getBackgroundColor(Snacky snacky) { + if (backgroundColorBuilder != null) { + return backgroundColorBuilder!.call(snacky); + } + return const Color(0xFF242c32); + } + + BoxBorder? _getBorder(Snacky snacky) { + if (borderBuilder != null) { + return borderBuilder!.call(snacky); + } + return null; + } + + TextStyle _getTextStyle(Snacky snacky, GradientSnackyTextType textType) { + if (textStyleBuilder != null) { + return textStyleBuilder!.call(snacky, textType); + } + switch (textType) { + case GradientSnackyTextType.title: + return const TextStyle( + color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold); + case GradientSnackyTextType.subtitle: + return const TextStyle(color: Colors.white, fontSize: 12); + } + } + + Color _getSnackyTypeColor(Snacky snacky) { + if (iconColorBuilder != null) { + return iconColorBuilder!.call(snacky); + } + switch (snacky.type) { + case SnackyType.success: + return Colors.green; + case SnackyType.error: + return Colors.red; + case SnackyType.warning: + return Colors.orange; + case SnackyType.info: + return Colors.blue; + case SnackyType.branded: + return const Color(0xFF7D64F2); + } + } + + IconData? _getIcon(Snacky snacky) { + if (iconBuilder != null) { + return iconBuilder!.call(snacky); + } + switch (snacky.type) { + case SnackyType.success: + return Icons.check_circle; + case SnackyType.error: + return Icons.cancel; + case SnackyType.warning: + return Icons.info; + case SnackyType.info: + return Icons.info; + case SnackyType.branded: + return Icons.info; + } + } + + Widget? _getLeaderWidget(Snacky snacky, Color background) { + final icon = _getIcon(snacky); + if (icon == null) return null; + final color = _getSnackyTypeColor(snacky); + return Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + Positioned( + left: -100, + child: Opacity( + opacity: 0.15, + child: Container( + width: 200, + height: 200, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + center: const Alignment(0.1, 0), + colors: [ + color, + color.withOpacity(0), + ], + stops: const [0, 0.8], + ), + ), + padding: const EdgeInsets.all(2), + ), + ), + ), + Container( + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.05), + shape: BoxShape.circle, + ), + padding: const EdgeInsets.all(4), + child: Icon( + icon, + color: color, + size: 24, + ), + ), + ], + ); + } +} diff --git a/lib/src/builder/simple_snacky_builder.dart b/lib/src/builder/simple_snacky_builder.dart index 7b03b64..bf1cfcf 100644 --- a/lib/src/builder/simple_snacky_builder.dart +++ b/lib/src/builder/simple_snacky_builder.dart @@ -16,13 +16,17 @@ class SimpleSnackyBuilder extends SnackyBuilder { final BorderRadius borderRadius; final EdgeInsets margin; final EdgeInsets padding; - final Color Function(Snacky)? colorBuilder; + final Color Function(Snacky)? backgroundColorBuilder; + final Color Function(Snacky)? iconColorBuilder; + final IconData? Function(Snacky)? iconBuilder; final BoxBorder Function(Snacky)? borderBuilder; final TextStyle Function(Snacky, SimpleSnackyTextType)? textStyleBuilder; final bool disableInkwell; const SimpleSnackyBuilder({ - this.colorBuilder, + this.backgroundColorBuilder, + this.iconColorBuilder, + this.iconBuilder, this.borderBuilder, this.textStyleBuilder, this.margin = const EdgeInsets.all(16), @@ -55,7 +59,7 @@ class SimpleSnackyBuilder extends SnackyBuilder { return Container( width: double.infinity, decoration: BoxDecoration( - color: _getColor(snacky), + color: _getBackgroundColor(snacky), border: _getBorder(snacky), borderRadius: borderRadius, boxShadow: const [ @@ -154,9 +158,9 @@ class SimpleSnackyBuilder extends SnackyBuilder { ); } - Color _getColor(Snacky snacky) { - if (colorBuilder != null) { - return colorBuilder!.call(snacky); + Color _getBackgroundColor(Snacky snacky) { + if (backgroundColorBuilder != null) { + return backgroundColorBuilder!.call(snacky); } return Colors.white; } @@ -172,6 +176,9 @@ class SimpleSnackyBuilder extends SnackyBuilder { } Color _getSnackyTypeColor(Snacky snacky) { + if (iconColorBuilder != null) { + return iconColorBuilder!.call(snacky); + } switch (snacky.type) { case SnackyType.success: return Colors.green; @@ -200,6 +207,9 @@ class SimpleSnackyBuilder extends SnackyBuilder { } IconData? _getIcon(Snacky snacky) { + if (iconBuilder != null) { + return iconBuilder!.call(snacky); + } switch (snacky.type) { case SnackyType.success: return Icons.check_circle_outline; diff --git a/lib/src/builder/toast_snacky_builder.dart b/lib/src/builder/toast_snacky_builder.dart index 83ffc04..5c89f57 100644 --- a/lib/src/builder/toast_snacky_builder.dart +++ b/lib/src/builder/toast_snacky_builder.dart @@ -14,12 +14,12 @@ class ToastSnackyBuilder extends SnackyBuilder { final BorderRadius borderRadius; final EdgeInsets margin; final EdgeInsets padding; - final Color Function(Snacky)? colorBuilder; + final Color Function(Snacky)? backgroundColorBuilder; final BoxBorder Function(Snacky)? borderBuilder; final TextStyle Function(Snacky, ToastSnackyTextType)? textStyleBuilder; const ToastSnackyBuilder({ - this.colorBuilder, + this.backgroundColorBuilder, this.borderBuilder, this.textStyleBuilder, this.margin = const EdgeInsets.all(16), @@ -52,7 +52,7 @@ class ToastSnackyBuilder extends SnackyBuilder { return Container( decoration: BoxDecoration( - color: const Color(0xFF404040), + color: _getBackgroundColor(snacky), borderRadius: borderRadius, boxShadow: const [ BoxShadow( @@ -91,6 +91,13 @@ class ToastSnackyBuilder extends SnackyBuilder { ); } + Color _getBackgroundColor(Snacky snacky) { + if (backgroundColorBuilder != null) { + return backgroundColorBuilder!.call(snacky); + } + return const Color(0xFF404040); + } + TextStyle _getTextStyle(Snacky snacky, ToastSnackyTextType textType) { if (textStyleBuilder != null) { return textStyleBuilder!.call(snacky, textType);